I want to create a Behavior to automagically populate a 'created_by' field when a record gets saved with the id of the logged in user, much like timestamp does with 'created'. However, apparently you can get $this->Auth->user('id') everywhere except in the Model.
I am having to resort to updating the request data in the controller's add() method before the save() call, which works but it's not DRY. I'd like to have bake add the behavior in a model's table class for any entity that has a 'created_by' field.
Any ideas how I could do this in a Behavior, the way Cake intended?
A possibility is to use the Events System. I personaly did this by using a Component and a Behavior that communicate through the Model.beforeSave event. Basically the component is responsible to add a new listener that passes a function to the behavior allowing to retrieve the authenticated user id.
You can have a look at the code of both classes here: UserLinkComponent, UserLinkBehavior
and to use them:
Controller:
$this->loadComponent('Alaxos.UserLink');
Model\Table:
$this->addBehavior('Alaxos.UserLink');
The Blame behavior does what you need. You can either use it or study it's code to implement your own.
Related
Working with cakePHP this is my situation:
I have Users and Orders. Orders are created by Users. Only the user that created the Order is allowed to edit it. (admins can also, but I don't think that is important).
I am using the standard Auth component and have an isAuthorized function in my OrdersController that checks if the user is logged in and stops users from performing actions that they are not allowed to perform.
I want to make a decision on whether or not the user can perform the action based on the params passed and the data that comes out of the database. i.e. does the user own the order they are trying to edit? I am currently checking inside each action if this is the case.
Is there a way that I can trigger the same workflow that is triggered by returning false from isAuthorized? maybe throwing an Exception?
I don't want to do these finer checks inside the isAuthorized function, because it will require ugly methods of accessing the passed params, and duplication of data retrieval. How does cakePHP expect me to handle this?
(I have more complicated checks to make in other controllers)
This is what you're looking for:
http://book.cakephp.org/2.0/en/tutorials-and-examples/blog-auth-example/auth.html#authorization-who-s-allowed-to-access-what
overriding the AppController’s isAuthorized() call and internally
checking if the parent class is already authorizing the user. If he isn’t, then just allow him to access the add action, and conditionally access
edit and delete.
Hope this helps
There are a few ways to get this to work. I have a simple example outlined here:
http://nuts-and-bolts-of-cakephp.com/2009/04/22/simplistic-example-of-row-level-access-control-with-auth-security-and-app-model-in-cakephp/
It should give you an idea of how to handle this in general, and then you can build on top of that as one approach.
Is there a function like unloadModel in cakePHP that should be called to unload a model that was loaded using loadModel() function?
I found an unload method,
http://api20.cakephp.org/file/Cake/Model/BehaviorCollection.php#method-BehaviorCollectionunload
But it seems to be used for Behavior. Im new to cake. Is there a function like that or does it get automatically unloaded when the called action loses scope?
One more doubt; is using loadModel against MVC's normal conventions? Does it have any adverse effects?
You do not need to unload your model. If you're going to use the model throughout the entire Controller, then use the $uses variable:
public $uses = array('MyModel', 'AnotherModel');
If you're going to just use it in a specific action(s), use loadModel:
$this->loadModel('MyModel');
That's it - no unloading necessary.
And no, it's not against MVC imo and I have seen no adverse effects.
It's VERY common to load a model. Example - most of my projects require a few "homepages" that have greatly-varying data from nearly ever model. In that case, I create a "DashboardsController", which doesn't even have a table - then I load each model when I need to access it's data. (Or with $uses if I'm going to use it's data in all the actions).
no, behaviors and models are two different things.
behaviors add functionality through hooks. Meaning: they alter the way other methods in models work. So if you want to geocode your data automatically, you use a geocoder behavior. Or if you want your results to be decrypted upon find, you add the decrypt behavior.
So there you NEED the option to detach/unload behaviors because you might not want this functionality there at some point.
Models are just access to the database or provide wrapper methods. They don't have to be removed in order for the rest of the site to function as they do not alter the way other methods work.
loadModel is just a way to dynamically load models that are not automatically related. its totally fine to do that from controller actions where you need those models.
I have MyModel that, whenever it is created or updated, I want to insert a row into my_model_changes (MyModelChange) that is a log of all changes.
The problem is that I need MyModelChange to be able to save the current user's ID, so that it stores who authorised the change to MyModel.
I know the model shouldn't be able to access the Auth session in MVC. However, every change to MyModel must be logged, and so I don't want to leave it up to people to remember to do this in a controller every time they modify the model, because there is a risk it will be forgotten or not done properly, in which case the log won't be complete. Instead, I want to use MyModel::afterSave() so that it is automated and will always happen properly.
So how do I get MyModel to find the Auth user's ID?
Maybe my solution is not the most elegant way but I used it in my app and it worked. Before saving in the controller I extend $this->data with the user_id like this:
$this->data['MyModel']['user_id']=$this->Auth->user('id');
so I can use it in my model afterSave method. I hope it could help.
Is it not possible to do what you're wanting to do in the app_controller.php's beforeFilter? That way you don't have to set anything in the other controllers, except the parent::beforeFilter() ofcourse if you override it.
But if it's really needed, you can put this in your Model to access session data:
App::import('Component', 'SessionComponent');
$Session = new SessionComponent();
I have a log feature in my project that tracks all changes to a couple of different models. Say, whenever a user or an admin edits an account. Or when a user creates a new record. All those actions get recorded in the log table.
I would like to have the log model retrieve the model name and action name by itself in a beforeSave action when called to save a new record. I call it now from a controller action making the save like this:
$this->Log->save(array(
'user'=>$this->Auth->user('id'),
'model'=>$this->name,
'action'=>$this->action));
I would love to be able to shorten it down to:
$this->Log->save();
I found alkerman's wonderful LogableBehavior and it is working like a charm. No need to reinvent the wheel.
I think I'd be inclined to either write a method on app_controller such as
saveLog()
which calls
$this->Log->save($uma_array)
or simplify your existing construct by doing
$this->Log->save($this)
and untangling the data inside the method.
EDIT:
Of course, the best way to do this would be to use observable behaviour using teknoid's pattern: http://nuts-and-bolts-of-cakephp.com/2009/08/10/observer-pattern-the-cakephp-way/
It's not that difficult to implement.
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();