Accessing User Sessions From Models (or Anywhere) In CakePHP REVEALED!

Posted by Matt on Mon, Oct 06 2008

The latest version of this code can be found at http://github.com/mcurry/cakephp_static_user/

"How do I access the logged in user in a model?" I've seen this question a bunch of times. I've tried numerous solutions, but never really liking any of them. The guys over at debuggable.com have hinted at a User::get('id') method twice, but never revealed the details.

Early Attempts

This question has shown up on the Google Group. Nate (one of the Cake developers) responded:

There are two right ways of doing this. One is to make the session a formal part of your domain model, i.e. model it. This allows it to interact with other models at the model level.

I think I get what Nate is saying here. Make a Session model without a table, set relationships to this model in any other models that may use it (or do I just call it statically?) and (somehow) set the data from your session to the model in app_controller. Assuming I'm understanding this correctly (there's a good chance I'm not), I wasn't crazy about this approach. How do you get the session data to the model? How do you set up the relationships when one of the models is tableless?

Nate's other approach was to set the session/user as a variable in the model with this code in the app_controller:

  function beforeFilter() {
        $this->{$this->modelClass}->setUserState($this->Auth->user());
   }

I used this approach for awhile, but had issues trying to access the user in cases where the model wasn't the "primary" one. The code above only sets the user state for the one model class, not any related models. You could loop through all relationships recursively to make sure the data is set down the line, but ehhh...

What I'm Using Now

Here's what I have. It's not perfect, but it's the best solution I've come up with so far. I liked the "User::get()" syntax used in the debuggable.com posts, so I worked from there. In your User model:

httpc://github.com/mcurry/cakephp/raw/2b78e2882718cc8a94b4e092897d3fec90ff5510/snippets/static_user/user.php

In AppController, beforeFilter method (assuming you are using the Auth component):

App::import('Model', 'User');
User::store($this->Auth->user());

How It Works

To get the currently logged in user's id:

User::get('id');

To get the currently logged in user's username (assuming you have a field "username" in your users table):

User::get('username');

Any other fields in your user table can be retrieved in the same manner. Also if you store related model data in your user session it can be retrieved:

User::get('Model.fieldname');

Issues

To get the "User::get()" syntax to work this block has to be placed in the User model, but it feels slightly misplaced there.

Uses

This has code has come in really handy. I use it a behavior with a beforeSave callback to set the created by user id and modified user id. Likewise you can have a behavior that automatically filters rows for index pages to only show records appropriate for the logged in user. It also works in anywhere in your Cake app. In views you can use this instead of $session->read('Auth.User.id').

Update

I missed that Cake already has a Model::set method. To prevent the conflict the method to set the user has been changed to "store". I also added an App::import in the AppController->beforeFilter to make sure the user model is already included.

Posted in GitHub

9 Comments

GreyCells said on Oct 24, 2008
Here's another approach:

class SessionViewBehavior extends ModelBehavior {

function __validateSessionKeys($path) {
if (is_string($path) && preg_match("/^[ 0-9a-zA-Z._-]*$/", $path)) {
return $path;
}
return false;
}

function sessionCheck(&$model, $path) {
$var = $this->__validateSessionKeys($path);
if (empty($var)) {
return false;
}
$result = Set::extract($_SESSION, $var);
return isset($result);
}

function sessionRead(&$model, $name = null) {
if (is_null($name)) {
return null; //$this->__returnSessionVars();
}
if (empty($name)) {
return false;
}
$result = Set::extract($_SESSION, $name);
if (!is_null($result)) {
return $result;
}
return null;
}

}

Not quite DRY, but as I tend to think of the session as 'just another data store', then I'm comfortable with it being a behaviour.
Matt said on Oct 24, 2008
Hey GreyCells,
The concern I always had with the approach you're using is accessing $_SESSION directly in the model/behavior. I've done it before, but always felt dirty afterwards :)

Plus, I think (never actually tried), if you use Cake's DB sessions then $_SESSION may not have the information you're expecting. Admittedly, this probably isn't a factor for most people.
GreyCells said on Oct 26, 2008
Hi Matt

The access to $_SESSION is identical to that in SessionComponent (hence not very DRY). Also, I'm only accessing the Auth.* parts (after the login redirect), so timing hasn't (yet) been an issue.

Unless cake starts using a real model for Session (or a datasource), then we're a bit stuck between a rock and a hard place - I'm not particularly comfortable with any of the approaches I've seen so far (even mine :)
Matt said on Oct 27, 2008
Updates to this code snippet will be posted at http://github.com/mcurry/cakephp/tree/master/snippets/static_user
Danilo said on Nov 30, 2008
Thanks. Good solutions.
Karl said on Dec 23, 2008
Thanks for this snippet, I'm using it in my app to display a "currently logged in as: , Logout" link. Very handy.
Elmer said on Jan 28, 2009
Hi Matt,
I'm currently using the following snippets (don't know if they're the 'right' way, but they work):

In AppController->beforeFilter() place this line:

$this->set('Auth', $this->Auth->user());

Note the parenthesis after user!

This makes the User model available in both views and controllers. To access the User data from a view, use:
echo 'Logged in as . ' $Auth['User']['username'];

To access the data from a controller, use:
$user = $this->Auth->user();
$name = $this->Auth->user()['User']['username']
wargoth said on Mar 16, 2009
My simple solution:
In User model declare static var:
class User extends AppModel {
...
var $name = 'User';
static $auth;
...
}

In AppController:
// App::import('Model', 'User');
function beforeFilter() {
$auth = $this->Auth->user();
$this->set(compact('user'));
User::$auth = $auth;
}

Now it accessible both in views ($auth var) and models (User::$auth var)
Jason Rose said on Mar 05, 2010
Greetings.

I recently came across a situation where I needed to delete session data in an afterSave callback within a behavior (and no, the situation didn't allow this to be done within a controller).

Here's what I did to accomplish such a task.

$CakeSession =& new CakeSession(null, false);
$CakeSession->del('Module.key');

Add new comment