Give all of your error messages a different layout

By default CakePHP will use the default.ctp layout to display any error messages.

In some cases this may not be desirable as your default.ctp might contain navigational and design elements, CSS and JS files, which really take away from the way the error message is displayed.

Usually an error message should be displayed in a clean and simple layout that only focuses on the error itself.

Thankfully, the remedy for this is pretty simple…

First, create a new layout and call it error.ctp (place in the app/views/layouts, of course).

Then add this to your App Controller:


function beforeRender () {  
       $this->_setErrorLayout(); 
}

function _setErrorLayout() {
    if($this->name == 'CakeError') {
        $this->layout = 'error';
    }
}

That’s all folks.

P.S. Note, this is different than modifying error message views. For instructions on that, please look at the helpful instructions in the actual error message.

Simplistic example of row-level access control with Auth, Security and App Model in CakePHP

Let me just preface this post, by saying that this is indeed a very much simplified example.
The main purpose here is not to provide a solution that will fit any application, but rather give a decent foundation to further expand your own solution.

The example is based on Auth (you should know a little about the Auth component), the Security component and a simple method in your App Model.

Let’s say we have some journaling app with Articles Controller, which has a “view” action.
We only want to allow access to view the journal articles to their owners, i.e. the users who’ve actually wrote the article (or journal entry).

To illustrate this further, I am going to attempt to access a URL such as:
http://www.example.com/articles/view/8

The goal is to ensure that article with ID = 8, actually belongs to me (AKA the currently logged in user), and if I start tampering with URL by changing the ID, the access to “view” should be denied (unless I happen to pick another article ID, which belongs to me).

In a typical setup we probably have an “articles” table with a “user_id” field, i.e. User hasMany Article.

With that in mind we can create an easy method in our App Model to check for user access:


function canAccess($userId = null, $primaryKey = null) {
        
        if($this->find('first', array('conditions' => array($this->alias.'.user_id' => $userId, $this->primaryKey => $primaryKey),
                                           'recursive' => -1))) {
            return true;        
        }
        
        return false;    
}

Update (05/13/2009): Some users pointed out that recursive = -1, might cause problems with following find() operations. I’ve updated the code to pass ‘recursive’ as part of the find(), which resets the value back to the original in the latest versions of cake. Either way, you don’t want to pull associated models on this simple check.
If you decide to reset recursive later on, or better yet, use Contanable behavior, this solution should work perfectly well.

The above function simply looks for a combination of Article.user_id and Article.id to ensure that the Article actually belongs to the logged-in user.

So how do we use this in the Articles controller?
Perhaps something like this:


function beforeFilter() {
   if (in_array($this->action, array('view'))) {
      if(!$this->Article->canAccess($this->Auth->user('id'), $this->params['pass'][0])) {
         $this->Security->blackHole($this);      
      }                  
   }
}

If the action is “view”, we pass the logged-in user ID $this->Auth->user('id') and the article ID $this->params['pass'][0] to check the access.
If no record with both ID’s is found, we “blackhole” the request, otherwise the access is allowed.

And there you have it, a very simple way to control row-level access to any action in your controller.
As mentioned, I’m hoping that this post will inspire some ideas and will let you expand further in your app.

P.S. Do not forget to include your Auth and Security components, and if you need to get some more info on both of them you can take a look at the CakePHP manual or even do a quick search on this blog.

More pondering about HABTM (let’s save new tags with a post)

There is a number of examples (some you can find even on this blog) on how to save a Post with a few Tags.
Most of them, so far, had shown how to easily save Tags when the ID’s are already known.

Now, we’ll take a look at how to save a Post and allow a user to enter a bunch of tags (hmmm… let’s say separated by a comma) and save it all together with very little hassle.

Let’s define some goals first:

  • We would like to allow the user to pick from a list of existing tags
  • We would like to allow the user to input a bunch of tags separated by comma
  • We need to ensure that we do not save already existing tags
  • If the user doesn’t select or input any tags, we should just go ahead and save the Post
  • If the user does something silly like input “some, some, some, tag”, we should only save two tags from the list

I think that should be good enough to get started…

First we’ll, build a simple a form add.ctp:


  echo $form->create();

  echo $form->inputs(array('name', 'body'));
  echo $form->input('Tag', array('multiple'=>'checkbox'));
  echo $form->input('Tag.new_tags');

  echo $form->end('Save');

As you see, we’ll have a bunch of checkboxes to allow the user to pick from existing tags, we’ll also allow a free-hand input for “new tags” $form->input('Tag.new_tags');

Now, let’s see our “add” action in the controller:


function add() {

   if (!empty($this->data)) {
      $this->Post->save($this->data);
   }

  $this->set('tags', $this->Post->Tag->find('list'));
}

Nothing out of the ordinary so far. Note that, $this->set('tags', $this->Post->Tag->find('list')); will let CakePHP automagically build a bunch of checkboxes from existing tags in the DB.

Now, let’s add a little something-something to our Post model:

   function beforeSave() {
        
        $tagIds = $this->_getTagIds();
          
        if(!empty($tagIds)) {
            if(!isset($this->data['Tag']['Tag'])) {
                $this->data['Tag']['Tag'] = $tagIds;    
            }
            else {
               foreach($tagIds as $tagId) {
               	    $this->data['Tag']['Tag'][] = $tagId;
               }
            }  
        }
                        
        return true;
    }
    
    function _getTagIds() {
        $tags = explode(',', $this->data['Tag']['new_tags']);
                
        if(Set::filter($tags)) {
            foreach($tags as $tag) {
                
                $tag = strtolower(trim($tag));
                
                $existingTag = $this->Tag->find('first', array('conditions' => array('Tag.name' => $tag)));
                
                if(!$existingTag) {
                    $this->Tag->create();
                    $this->Tag->saveField('name', $tag);
                    
                    $tagIds[] = $this->Tag->id;    
                }
                else {
                    $tagIds[] = $existingTag['Tag']['id'];
                }
            }
                       
            return array_unique($tagIds); 
        }
        
        return false;      
    }

Let’s examine what’s happening here…

In beforeSave() we prepare the array of Tag ID’s to be saved, by relying on CakePHP’s HABTM conventions.
We’ve also created a custom function _getTagIds() to process the input from the user from the “new_tags” field (remember our add.ctp?)

So, if the user did not provide any input we simply go ahead and save the Post (and possibly some selected tags), otherwise we check if the tag already exists and if so, grab the ID. If it does not exist we save it, and then grab the ID.

Once all that is accomplished, we go ahead and properly prepare $this->data['Tag']['Tag'] to save our Post and Tag relationship.

Of course, any questions and suggestions about the code are as always welcomed.

P.S. If you are completely lost about what’s going on here, I suggest you check out this post, and then come back to review the code in more detail.

A little tip about counterScope

One of many cool CakePHP’s features is the Counter Cache.

From the manual: “This function helps you cache the count of related data. Instead of counting the records manually via find(‘count’), the model itself tracks any addition/deleting towards the associated $hasMany model and increases/decreases a dedicated integer field within the parent model table.”

So if you have Forum with Posts, you can keep track of how many posts are there in a given forum by tracking this data in the “forums” table, which certainly speeds things up and takes a little load off your DB.

One feature that is not (yet) very well documented is the ability to provide a counterScope. It essentially allows you to specify a simple condition, which tells the model when to update (or when not to, depending on how you look at it) the counter value.

Using our Post model example, we can specify it like so:

class Post extends AppModel {
        var $name = 'Post';
        var $belongsTo = array('Forum'=>array(
                                              'counterCache'=>true,
                                              'counterScope'=>array('Post.deleted'=>0)));

    }

This means that counter will only be updated for posts that are active: 'deleted'=>0.

Also, it is implied in this example that you have some admin tool, which lets you do soft deletes of your posts (i.e. set the “deleted” field to 1)…

Something simple, like this:

function delete($id = null) {
   if($id) {
        $this->Post->id = $id;
        $this->Post->saveField('deleted', 1);
   }
}

Guess what? Cake is going to update your counter field in the forums table, when you deactivate a post in this manner.

Brilliant stuff, I tell ya…

Blend PHP and JavaScript in CakePHP

I guess it’s no big secret that you can easily serve JavaScript files with some PHP content, by using something like:

<script type="text/javascript" src="myscript.php"></script>

Let’s take a quick look at how this applies to CakePHP and what (hopefully) useful tricks you can accomplish by employing the following techniques…

First, we’d like to avoid any messy and ugly in-line JS and load all files by using the convenient $javascript->link() method.

We’ll create an empty view (views/users/test.ctp), for the sake of this demonstration and load some JS file:

$javascript->link('test_file.js.php', false);

Having the “.js.php” in the name of the file prevents cake from tacking on the “.js” extension by default, therefore our include in the head looks as we’d expect:

<script type="text/javascript" src="/js/test_file.js.php"></script>

Now, wouldn’t it be lovely to pass some variables from our Users Controller to this file?

Alright, let’s build a simple test() action:

function test() {
    $this->Session->write('User', 'My name is Charlie');                
}

Since we are actually serving up a PHP file, rather than a plain JS file… we can blend JS and PHP to make some interesting things happen.
Here’s our test_file.js.php:

<?php 
    session_name('CAKEPHP');
    session_start();
?>

alert('<?php echo $_SESSION&#91;'User'&#93;; ?>');

session_name('CAKEPHP'); is based on the session cookie name, which is set in config/core.php.
The rest of the file should pretty much self explanatory… we simply pop-up an alert box with “My name is Charlie”, which we’ve wrote to the session in the Users Controller above.
(Sorry, I couldn’t come up with a more boring example).

However, this opens up some interesting opportunities, such as dynamically setting values for your jQuery scripts, while maintaining very clean mark-up and avoiding any in-line JS.

P.S. AD7six pointed out that any .js file in your app/vendors/js/ can have PHP code in it, so you don’t need to use “.js.php” trick, however many IDE’s will give you a very quirky highlighting (to say the least) when you are editing PHP code in a .js file, but not the other way around. Either way you’ve got both options to play around with.