Help with cake controllers - cakephp

We had an outsourced engineer work on a quick feature DELETING items listed in our database. He says that the code is difficult because the "controller" is missing. Is there a pre-loaded controller for every function like that in cake, or is it weird that he is expecting a controller to be there for a feature we didn't have yet.

There is a generic AppController, but that's more of an abstract class in practice (you generally derive your other controllers from that).
It's not that weird at all that he's expecting a controller -- after all, you won't be able to call methods in the models (which is how I'm guessing you're doing delete) unless you have a point of control to call them from. In this case, the point of control is the controller.
So you can just create a controller. Here's a template to start from:
class SomeController extends AppController {
function delete() {
$this->Some->delete();
}
}
Then access /somes/delete (remember, URLs are generally /controller/action).
Now, he could be talking about the Cake Bake CLI app. That will take your DB tables, and walk you through an initial basic setup for your app. Generally it creates a basic skeleton for CRUD actions.
Either way, you need to create a controller (manually, or via Bake).

When you use the Cake bake function, it'll create all the controllers for you. When you don't use it, you'll need to create them manually. It makes no sense to make all the controllers at the begin, just make them when you really gonna write them would be good.

If you do not have a controller in CakePHP when you visit a page (http://www.youraddress.com/Newfeature) you receive a missing controller error:
Error: NewfeatureController could not be found.
Error: Create the class NewfeatureController below in file: app\controllers\newfeature_controller.php
You cannot get or delete data from the database without controllers - Understanding Model-View-Controller. You do not need the controller only for static pages in CakePHP.

Related

CakePHP UnitTest a method which calls another model

I'm writing a variety of Model Tests in CakePHP (PHPUnit)
In TravisCI, I get something like: "Base table or view not found: 1146 Table 'test.events'
In Cake's test runner I get an assertion failure.
The problem I am having is there are methods in my ModelClasses that I am trying to test which call other models with App::uses. For example:
Method on User model:
public function getOtherData() {
App::uses('Event', 'Model');
$this->Event = new Event;
return $this->Event->find('all');
}
And the test:
public function testGetOtherData() {
$result = $this->User->getOtherData();
$this->assertTrue(!empty($result));
}
Note the above example is just that. An example, simplified to show the problem. I understand that the above example has better 'cake' ways of doing it.
Also, I am using defining required fixtures and they work just fine. (I know this by another method in the model which uses a join in the find, instead of App::Uses())
EDIT:
The code when run works, BUT the UnitTest is looking for the other models data (When using App::uses) in the default database, and not the test database. Why doesn't it use the test database? Am I missing something?
LAST NOTE
Using App::uses() and then instantiating the class will work at runtime. But during testing it will fail, as it attempts to use the default database connection, instead of the test database connection.
Per the selected answer, rather than using App::uses, Cakes built in class registry, ClassRegistry::init('Model', true);, you can include a Model from inside another model method.
It's not generally a good idea to instantiate an object in the middle of your functions using the new statement. This is why -- there's no way to block or redirect that call. Also, it's not necessarily easy to get the right parameters to the object's constructor when it's in the middle of another function, so it's best to keep that code separate.
The right way to do this is to use a different method call to get your object. If you use Cake's ClassRegistry::init() to create model objects, they should use the test database.
If you need to create other non-Cake objects, it's best to create them using some other function, e.g. $this->fetchMeOneOThemEventThingies(). Then, during testing, you can mock out that function and have it return something else. Or, you could use some other DI container like pimple, which will take the same role as Cake's ClassRegistry.
If you need a mock model object for testing, be sure to pass the appropriate arguments to the model's constructor as the third parameter to getMock(), or it may use the production database.

Loading CakePHP Helpers

This is a multi part question.
Background:
I'm building my first site using CakePHP, and I like it so far. I've got a DB setup, initial data loaded, and a few models, views, and controllers to interface with the data.
I've created a globally accessible function to create Add/Edit/Delete type image links in various areas. It will be used across multiple views, so I need it accessible, essentially, everywhere. The function is defined in /app/config/bootstrap.php. I was hoping to use the HTML Helper's $html->image() and $html->link() methods to facilitate this, but they're not available in bootstrap.php and I'm not sure how to load/access the HTML Helper where I've defined my function.
Questions:
1) Is this a reasonable/idiomatic place to define a function of this sort?
2) If this isn't the correct place to define the function, where should I define it?
3) If this is the correct place to define the function, how can I go about loading various CakePHP helpers?
Again, I am new to CakePHP, so please let me know if my question is unclear, and forgive my ignorance. I've read/searched through a fair amount of the CakePHP documentation and while I can find plenty of references to loading helpers within Controllers via App::import(...); or $helpers = array(...);, I do not seem to have access to the App object and the $helpers member is specific to the AppController class, I assume. I assume I'm going about this incorrectly, so please help me understand the Cake way of accomplishing this.
No, that is not the correct place for such a function (or more accurately, it goes against the MVC paradigm). A better approach would be to create your own helper for the function. Depending on the complexity of the links you could also use elements.
As a rule of thumb only functions that are completely independent of anything else in the app should be in bootstrap.php, and even most of those would often be better somewhere else.

CakePHP: making a controller function not accessible

I have an admin controller that I would like to utilize functions in other controllers (these functions do not represent pages that someone would load in their browser), but it cannot utilize those functions because the functions in the other controllers are private. They are private because I don't want the public to access them. Is there a way to make a controller function not accesible to the public without making the function private or protected?
public function __blah(){
// function that can't be accessed from outside, but can be called from other functions
}
Based on what I've read in the comment of the answer Piotr gave you:
You don't use an admin controller. You want to use admin prefixes:
http://book.cakephp.org/view/950/Prefix-Routing
And authentication:
http://book.cakephp.org/view/1250/Authentication
If you call - and thats how your comment sounds like - one controller from another you're doing something totally wrong in an MVC framework. If it should be re-usable code it should go into components if it's about admin action use the prefix routing and admin_* methods, auth component and protected methods for what you call "helper" methods.
Yes.
You have a lot of information in the CakePHP Book about ACL (access control list) and that is exactly what you're looking for.
Or you may use Auth component.
I see three possible solutions (they can also be combined):
The first solution is to move the code you want to reuse to components (as mentioned by burzum).
The second solution depends on your code. It's possible that you do stuff in the controller which should be done in the model. In this case, move the respective code to the model.
The third solution is to put the code you want to reuse into plain old PHP classes and load them as vendor files.

Calendar throughout CakePHP site, but CakeBook says to not use a Model in a Component

I have a small calendar widget-type thing on many pages throughout my site. The goal is for it to retrieve events from X category that fall between Y and Z dates.
My assumption (I'm new to CakePHP) was that I should create a component, and have it do the query. Something like this:
class CalendarComponent extends Object {
var $uses = array('Event');
function getEvents($category = null, $date = null, $limit = null) {
$events = $this->Event->find('list', //conditions to get correct events
return $events;
}
}
BUT, according to this CakeBook page:
To access/use a model in a component
is not generally recommended
So - where would I store this logic / model call if not in a component? I've admittedly not used a component yet (or not created one anyway) due to lack of really understanding how I should use them - any snippet of advice on this is also VERY welcome.
Great question in my opinion and I imagine one that comes up quite often. I was actually dealing with a similar problem where I wanted some site-wide data gathering or functionality shoved into a component.
The first thing to keep in mind:
The book is a guideline.
These 'rules' aren't rules. If there's a good reason for breaking the rule and you understand why the rule is being broken then break the damn thing! Cake itself breaks this rule quite often.
Core components that require/use models:
Acl
Auth
Sessions (fairly positive you can save session data to a model)
So, clearly there are use cases where you need to use a model inside a component. How do you do it though?
Well, there's a couple different ways. What I wound up going with? Something like this:
<?php
ModelLoadingComponent extends Object {
public function startup($controller) {
$controller->loadModel('Model');
$this->Model = $controller->Model;
}
}
?>
That's it! Your component is now setup to use $this->Model...just like you would in a controller.
Edit:
Sorry, to clarify: no, you don't have to setup a new component to load models. I was showing an example for how you could load a component in any model. The startup function I used is a component-specific callback, there's a whole slew of them http://book.cakephp.org/view/998/MVC-Class-Access-Within-Components. These callback methods make components a lot easier to work with. I highly recommend looking at this part of the components tutorial if nothing else.
If you were working inside an AppController object you could call $this->loadModel() but we aren't working an AppController! We're working with a component, really an Object. There is no Object::loadModel() so we have to come up with a different way to get that model. This is where $controller comes into play in our startup callback.
When the startup method is invoked by Cake it will pass the $controller object it's working with on this dispatch as the first parameter. With this we're able to access controller methods...like loadModel().
Why do we do it this way?
Well, we could use ClassRegistry::init('Model') in each of our component methods that need to use the model. If you have 10 methods in your component and only 1 of them uses the model this might work. However, what if you have 10 methods in your component and all 10 of them use the model? Well, you'd be calling ClassRegistry::init('Model') 10 times! That's a lot of overhead when what you really want is just 1 model object. With this method the component is creating one model object. The one we create in startup.
I hope this clarifies your questions and provides some insight into why I use this method for models in components.
Edit: Added a code clarification after I did some experimenting.
I think writing a component is overkill in this case and it would be cleaner to put the getEvents method into the Event model.

Is using the RequestHandlerComponent in a model possible?

I'm new to PHP and decided to use the cakePHP framework to help me get started.
I can't figure out one thing though, I want to call methods on the RequestHandlerComponent class to update a users last used IP address and other information, I figured the best place to put this would be in the beforeSave() method on the User model.
I can't figure out how to call the getClientIP method though.
The normal code that would otherwise go in the controller doesn't work. Is there another way to call this class if you're in the model and not the controller?
Class Level:
var $components = array('RequestHandler');
And in the function:
$this->data['User']['lastActiveIP'] = $this->RequestHandler->getClientIP();
Gives:
Undefined property: User::$RequestHandler
Call to a member function getClientIP() on a non-object
Components, by design, aren't available to models (without bypassing MVC convention - which you can do, of course). If you chose to force it to be available, look into ClassRegistry::init(). A better solution, I think, would be to employ the RequestHandler component in your controller (where it's meant to be used), set the lastActiveIp value in the controller (exactly as you've shown in your own example code) and pass the entire data array along to the model.
Now your component is being used where it should be and the model gets to remain ignorant about where it gets its data. At the risk of oversimplification, all the model should know is what to do with the data once it arrives; let the controller worry about collecting and packaging the data.
In addition to Rob's answer, maybe it's enough to put a bit of code together yourself that uses the general env('REMOTE_ADDR') or similar variables. Look at the RequestHandler code, it's not doing anything terrifically complicated.
You may even be able to call the component statically, which is slightly better than instantiating it in the model (still in violation of MVC though). Untested, but should work:
App::import('Component', 'RequestHandler');
RequestHandlerComponent::getClientIp();

Resources