Editing multiple records with saveAll()

Another interesting topic was brought up on IRC channel today…

(By the way if you don’t know about the awesome CakePHP IRC channel, you really should visit, it is a great place to get help and learn a thing or two… irc://irc.freenode.net/cakephp)

Anyways, moving on… How do we edit multiple records at once by using the good ol’ saveAll() method?

One immediate problem is that when we do $this->SomeModel->find('all');, the data array is formatted like this:

Array
(
    [0] => Array
        (
            [Profile] => Array
                (
                    [id] => 21
                    [name] => bob 3                    
                    [created] => 2008-10-27 13:01:30                   
                )

        )

    [1] => Array
        (
            [Profile] => Array
                (
                    [id] => 20                    
                    [name] => larry 5                    
                    [created] => 2008-10-27 13:01:30                    
                )

        )

However, we know that in order for saveAll() to work correctly the data should be formatted like this:


[Profile] => Array
        (
            [21] => Array
                (
                    [id] => 21                    
                    [name] => bob 3                    
                    [created] => 2008-10-27 13:01:30
                    
                )

            [20] => Array
                (
                    [id] => 20                    
                    [name] => larry 5                    
                    [created] => 2008-10-27 13:01:30                    
                )

Notice the difference of the key placement and the array structure?

Alright, let’s examine a super-easy way to get what we need by using the lovely Set class, and namely the Set::combine() method.

First, we build our edit action keeping in mind that we need the data in a form to follow a specific format for saveAll():

function edit() {
   if(!empty($this->data)) {
      $this->Profile->saveAll($this->data['Profile']);
   }
   else {
      $this->data['Profile'] = Set::combine($this->Profile->find('all'), '{n}.Profile.id', '{n}.Profile');
   }
}

We use Set::combine() to easily reformat the return of find('all') (to look just like an example array above) and, of course, assign it to $this->data, which is going to be used by the Form Helper.

Now, all we need to do is build our form (let’s make something really simple):

    echo $form->create('Profile', array('action'=>'edit'));
    
    foreach($this->data['Profile'] as $key => $value) {
        echo $form->input('Profile.'.$key.'.name');
        echo $form->input('Profile.'.$key.'.id');
    }
    
    echo $form->end('Save All Profiles');

This form allows us to rename multiple Profiles at once. It is built from our $this->data array and follows exactly the right format, which allows the saveAll() method to work correctly.

You lucky Mac users…

I came across this really awesome looking product today, called ModelBaker.
It is a nice GUI environment (and then some), which allows one to easily and intuitively build web apps on top of the CakePHP platform. I only had a chance to look at the screen casts so far, but from the initial looks it certainly looks very impressive.

Now, I just wish we had something similar for other OS’s… or, perhaps, better yet a web-based tool, which would allow one to build excellent apps right in the browser.

At any rate, just wanted to spread the word and share this goody with everyone. Now, I’ve got some serious thinking to do about the potential of such a tool :)

How to validate HABTM data…

Update: Since writing of this article the 'rule' => 'multiple' has been implemented in cake core.
It is much more convenient to use for HABTM validation.
You may consider this post deprecated and only to be used for historical purposes or if you are working with an older code-base.

So we’ve got our Post and Tag models (Post hasAndBelongsToMany Tag).

We are about to save a new Post, but there is a need to ensure that a user selects at least one Tag…

Let’s see how we can accomplish such validation…

First, let’s build the form for saving our Post and some Tags.


echo $form->create('Post', array('action'=>'add'));
echo $form->input('title');
echo $form->input('post');
echo $form->input('Tag', array('multiple'=>'checkbox'));
echo $form->end('Add post');

Pretty generic stuff… In our controller we’ll get a list of tags and it will be automagically assigned to our ‘Tag’ input and turned into a bunch of checkboxes representing each Tag.
After submitting the form the Tag data will be sent back as an array of id’s.

Now, let’s add a validation rule to our Tag model:


class Tag extends AppModel {
	
	var $name = 'Tag';
	
	var $hasAndBelongsToMany = 'Post';
        
        var $validate = array('Tag'=>array('rule'=>'checkTags'));
	
	function checkTags() {
	   if(!empty($this->data['Tag']['Tag'])) {
	       return true;
	   }
	
	   return false;
	}
}

As you see, we are using a custom method checkTags() to ensure that at least one Tag has been selected. It might appear that we could use ‘notEmpty’ rule, but unfotunatelly it doesn’t work with arrays of data so we have to use our own validation method.

Last, but not least, we create an add() action in our Posts Controller:

function add() {
	   	    
	    if(!empty($this->data)) {
	        $this->Post->Tag->set($this->data);
	        if($this->Post->Tag->validates()) {
	            $this->Post->save($this->data);
	        }
	    }
	    
	    $this->set('tags', $this->Post->Tag->find('list', array('fields'=>array('id', 'tag'))));
 	}

Just to clarify…
First, we validate the Tag model, by using the data from the form to ensure that at least one Tag was selected. If so, we save the Post and the relevant Tags.
Also, as you see, we use find(‘list’) to build our list of Tags for the view by relying on some good CakePHP magic.

Demystifying Auth features in CakePHP 1.2

This is actually a quick follow-up to the tutorial I’ve posed recently, which had a few simple, but not very obvious Auth component techniques.

I’d like to cover them in more detail here…

Let’s take a look at the app_controller.php again:

class AppController extends Controller {
    var $components = array('Auth');

    function beforeFilter() {
        $this->Auth->allow('display');
        $this->Auth->loginRedirect = array('controller'=>'users', 'action'=>'index');
    }
}

$this->Auth->allow('display'); tells the Auth component to allow a special action called ‘display’. This action is actually part of the Pages Controller, which you can find in the cake’s core. The main purpose of this controller is to serve static pages. You can take a look at the API to see how it works exactly, but one thing to keep in mind is that ‘display’ action is responsible for the… well… display of your static pages. If not allowed, by default, Auth will restrict access to your homepage or ‘About us’ page, for example. This effect is probably not desired in many cases.

$this->Auth->loginRedirect = array('controller'=>'users', 'action'=>'index'); tells Auth component where to redirect the user after the login. By default Auth would redirect to the main page (root) of your site, or previously visited page… depending on the situation.

Next, in the Users Controller you’ll notice the following:

if($this->action == 'add') {
  $this->Auth->authenticate = $this->User;
}

$this->Auth->authenticate = $this->User; tells the Auth component that it should use the User Model object to override the way hashPasswords() is handled. In other words, if there is a hashPasswords() method in the $this->User object, it will be used instead of the default hashPasswords() method in the Auth controller. As you see, this override only applies during the ‘add’ action.

Why bother?

Well, you might have noticed that by default Auth will hash the password before the validation. This creates a few problems. First of all you can’t apply ‘minLength’ and ‘notEmpty’ rules correctly, since validation method will get the hashed value (which is always long and not empty) rather than the actual value the user typed-in. Secondly, if there are errors in the form, a hashed value will be populated back in the password field.

By making a simple override in the User Model, the little problems and hacks can be avoided:

function hashPasswords($data, $enforce=false) {
   if($enforce && isset($this->data[$this->alias]['password'])) {
               if(!empty($this->data[$this->alias]['password'])) {
                   $this->data[$this->alias]['password'] = Security::hash($this->data[$this->alias]['password'], null, true);
                }
            }  

        return $data;
    }  

    function beforeSave() {
         $this->hashPasswords(null, true);  

         return true;
    }

The benefits are simple, but worthwhile:

  • When building the view I don’t need to be concerned about how the form field name is going to affect my model or controller
  • I can use the field ‘password’ (no need to use some other field name, such as ‘passwd’) to apply my validation rules, as I normally would
  • I do not believe in clearing out the ‘password’ field in case of a form error. There is no need to punish the user, who’ve made a mistake in the ’email’ field by also making him/her re-type the ‘password’
  • You don’t need any additional code in the controller to re-assign/unset virtual ‘password’ fields

To explain the above code, hashPasswords() will not perform the hashing unless the $enforce flag is true, which i will set manually to true in beforeSave().

Hopefully this clarifies some things and gives you some more ideas for the further tweaking of Auth…

Introduction to CakePHP features (build an app in less than 15 minutes)

I would like to showcase the awesome power of CakePHP by providing an introductory tutorial, which is going to cover some basics as well as slightly more advanced features such as the Auth (user authorization) component.

The goal of this tutorial is to setup a working application, which is going to have: user registration, login/logout functionality, user homepage, basic access control, data (form) validation and a flexible base to expand upon. Oh yeah, we’ll get all that done in just under 15 minutes and with only about 120 lines of code…

I assume that at this point you’ve got CakePHP installed and working. It would also be helpful to know the basic concepts and possibly have tried the blog tutorial.

The steps I take to build the app may not be very intuitive for a beginner, but I do provide explanations of each feature/step and hopefully at the end you’ll see how everything comes together.

Let’s begin…

We need a users table in our database to hold the User data, here’s a sample for MySQL:

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(200) NOT NULL,
  `email` varchar(200) NOT NULL,
  `username` varchar(200) NOT NULL,
  `password` varchar(200) NOT NULL,
  `status` tinyint(1) NOT NULL default '1',
  `company_id` int(11) NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

Next, we’ll create a system-wide controller, otherwise known as App Controller. Think of it as a global controller, whose properties and methods are accessible to all other controllers of the application.

Go into your main app directory (something like /home/teknoid/cake/app) and create a file named app_controller.php there.

app_controller.php
This is what it should look like:

class AppController extends Controller {    
    var $components = array('Auth');
       
    function beforeFilter() {        
        $this->Auth->allow('display');
        $this->Auth->loginRedirect = array('controller'=>'users', 'action'=>'index'); 
    }
}

Let’s take a look at what we did here…
We know that our app is going to need user authentication, therefore we include CakePHP’s built-in component with var $components = array('Auth');
We then create a special function beforeFilter(), which is going to be executed before all other actions in our application, so this is a pretty good time to tell the Auth component about some things (settings) that it should be aware of.
For example, we allow a special action called ‘display’ $this->Auth->allow('display') and we also tell the Auth component that, by default, upon login a user should be redirected to the ‘index’ action (or their homepage) $this->Auth->loginRedirect = array('controller'=>'users', 'action'=>'index');.
I do not want to go into much detail about the specifics of this setup, but overall this is a pretty basic and generic way to set-up the authentication in a CakePHP application.

Next, we are going to build our Users Controller.
Go into app/controllers and create a file named users_controller.php

users_controller.php
This is what it should look like:

class UsersController extends AppController {
        
        var $name = 'Users';
        var $helpers = array('Time');
         
        function beforeFilter() {
           parent::beforeFilter();
           
           if($this->action == 'add') {
               $this->Auth->authenticate = $this->User; 
           }
                    
           $this->Auth->allow('add'); 
        }
        
        function index() {
            $this->set('user', $this->User->find('first', 
                                                             array(
                                                                'conditions'=>array(
                                                                     'User.id'=>$this->Auth->user('id')))));
        }   
        
        function add() {
            if(!empty($this->data)) {                
                $this->User->save($this->data);
            }
        }   
         
        function login() {
            
        }   
         
        function logout() {        
            $this->redirect($this->Auth->logout());    
        }     
       
    }

Here’s what we did…
We’ve included the built-in Time Helper var $helpers = array('Time');, which basically helps to format (into something human readable) the date and time fields from the database… amongst other things. Later we’ll use it to format the date/time information on our user’s homepage.

You’ve noticed that we have another beforeFilter() function as we did in the App Controller. In this example we inherit our parent (App Controller) functionality by using parent::beforeFilter() and then we specify some additional settings for Auth that will be specific to the Users Controller.

Before we procede, I’d like to point out that Auth component will automatically hash your passwords with sha1() + a security salt you’ve setup during configuration/installation of CakePHP. Be sure to make the password column in your DB long enough to account for the hashed value (plain text passwords will not be stored).

This line $this->Auth->authenticate = $this->User; might be a little unusual…
The quick explanation, as to why I did that, is because Auth component allows us to override the way it handles some things, which offers great flexibility. Personally, I had a little beef with the way Auth handles password hashing, but as you can see it only bothered me during the ‘add’ action if($this->action == 'add'), in which case I provided my own authentication object (the User model). This is going to become a lot more clear later on (just keep it in mind for now).

To keep things secure from the get-go, we have to be very explicit about what actions we allow the user to access without having to login first. By default Auth is going to redirect the user to the login form regardless of what page one tries to access. Of course it would be really sad for the user, if we didn’t tell Auth that ‘add’ action can be accessed without login $this->Auth->allow('add');.
It is important to point out that Auth will assume that ‘login’ and ‘logout’ are always accessible and you shouldn’t specify those in the list of allowed actions.

Alright, our basic setup is complete and now we’ve added some actions into our Users Controller.

index() This is going to be our user’s homepage. Remember how we told Auth that upon login the user should be redirected $this->Auth->loginRedirect = array('controller'=>'users', 'action'=>'index');? Well, now we’ve actually got that part taken care of. The action is really simple, it finds all the information about our logged in user and sets it ready for the view.

add() No mystery here, this is our user registration action and it should be quite obvious as to what it does.

login() It is this easy to get the basic login functionality working… i.e. you don’t even need to write any code. Auth is smart enough to do the work for you. Of course you can easily change the way Auth does things by extending this action beyond the basics.

logout() Once the user clicks a “Log out” link he/she will be redirected to the default location, which happens to be back to the login form. Good enough for this example.

Now that we’ve got some of our actions finished up, let’s create the views for each one (a view is the actual page that the user is going to see in the browser).

Go to app/views/ and create a directory named ‘users’. Then in that directory add a view file for each of the actions (except logout, since it doesn’t need one).

index.ctp – our homepage

<?php echo $html->link('Log out', array('controller'=>'users', 'action'=>'logout')); ?>

<div>
    Hello, <?php echo $user&#91;'User'&#93;&#91;'name'&#93;; ?>
</div>

<div>
    Your account was created <?php echo $time->niceShort($user['User']['created']); ?>
</div>

This is a very simple view page, but it does show how you can access the various user information in the view. If you look back to our index() action in the Users Controller, you’ll see that we set a $user variable, which contains an array of user data and now we can echo it in the view.
Remember that Time Helper we’ve included earlier in our Users Controller? Well, here we’re using it to transform our database DATETIME field’s value, into a nice, human readable output by using: echo $time->niceShort($user['User']['created']);

login.ctp – our login page

if  ($session->check('Message.auth')) $session->flash('auth');

    echo $form->create('User', array('action' => 'login'));
    echo $form->input('username');
    echo $form->input('password');
    echo $form->end('Login');

Even though we don’t need any code for the login() action we still need to setup a basic form to display to the user. Nothing fascinating here, just note that first line if ($session->check('Message.auth')) $session->flash('auth');, which allows us to display any messages triggered by the Auth component.

add.ctp – our registration page

    echo $form->create();
    echo $form->input('name');
    echo $form->input('email');
    echo $form->input('username');
    echo $form->input('password');
    echo $form->input('password_confirm', array('type'=>'password'));
    echo $form->end('Register');

Here we have setup a basic form for the registration. Again there is really nothing fancy here… just a basic form, which will be brilliantly built by the Form Helper.

If you haven’t tried already, go ahead now:

Try to access http://yourlocalhost/users/shouldntAccessThis
– Auth component should redirect you to the login form, since we’ve never specified that ‘shouldntAccessThis’ is allowed.

Try to access http://yourlocalhost/users/add
– You should see a registration form

So far so good, but we’ve got to setup some validation rules for our registration form…
For this we create our User Model.

In app/models/ create the file user.php

user.php
This is what it should look like:

class User extends AppModel {
        
        var $name = 'User';
        
        var $validate = array(
            'name'=>array(
                          'rule'=>array('minLength', 4),
                          'message'=>'Name has to be at least four characters'),
                          
            'email'=>array(
                          'rule'=>array('email'),
                          'message'=>'Please enter a valid email'),
                          
            'username'=>array(
                              'Username has to be at least four characters'=>array(
                                  'rule'=>array('minLength', 4)
                              ),
                              'This username is already taken, please try another'=>array(
                                  'rule'=>'isUnique'
                              )),
                              
            'password'=>array(
                              'Password cannot be empty'=>array(
                                   'rule'=>array('notEmpty')
                                ),
                              'Password must be at least four characters'=>array(
                                   'rule'=>array('minLength', 4)
                                ),
                              'Passwords must match'=>array(
                                   'rule'=>array('passwordCompare', 'password_confirm')
                              ))
        );
        
        function passwordCompare($data, $fieldTwo) {   
                  
            if($data['password'] != $this->data[$this->alias][$fieldTwo]) {
                 $this->invalidate($fieldTwo, 'Passwords must match');
                 return false;
            } 
            
            return true;   
        }
         
        
        function hashPasswords($data, $enforce=false) {            
                     
           if($enforce && isset($this->data[$this->alias]['password'])) {
               if(!empty($this->data[$this->alias]['password'])) {
                   $this->data[$this->alias]['password'] = Security::hash($this->data[$this->alias]['password'], null, true);
               }
           }
           
           return $data; 
        }
                
        function beforeSave() {
            $this->hashPasswords(null, true);
            
            return true;    
        }        
    }

So this is the big guy. The User Model handles our validation logic and also overrides some of the default Auth component functionality. Remember how I said that we’ll use the User model to override default Auth behavior $this->Auth->authenticate = $this->User; during the ‘add’ action? Well here I have built my own hashPasswords() method, which overrides the Auth component’s default one. Without going into a long winded explanation I’ll just say that I prefer to hash the passwords just before saving the data (hence the little beforeSave() method) because it makes my life easier, and it also shows how easily Auth can be extended to behave the way you want it to.

Let’s take a look at passwordCompare(). This is my custom validation method to compare ‘password’ and ‘password_confirm’ fields (look back to our add.ctp view).
$this->invalidate($fieldTwo, 'Passwords must match'); will cause the ‘password_confirm’ field to be marked as invalid in addition to the ‘password’ field. This is nice if you’ve got some CSS that, perhaps, puts a red border on the input box for the invalid field (notice that we’ve also set the error message).
Another trick (I’m not quite sure honestly if it’s a documented feature) is to use really nice, human readable names for a given rule. This is in case you use multiple rules per field as we do for our ‘password’ field. This way, in case of an error, the arbitrary rule name will be used for the error message.

Go ahead, try it out with some invalid and then valid data… If all goes well, you should see an SQL debug showing the inserted user data, which confirms that your form is working correctly.

And there you have it… A complete, functional app in about 10-12 minutes.

P.S. I have posted a follow-up to this tutorial, which covers some of the Auth features used here in more detail.

CakePHP + MySQL + Tinyint(1) = Confusion

Just a quick note to point out that CakePHP fakes a BOOLEAN field in MySQL by using tinyint(1). MySQL doesn’t have a native support for BOOLEAN.

Therefore if you attempt to save some value other than 0 or 1, CakePHP will not allow you to do that (instead it will just save 1). The easiest way to get around this is to make your field tinyint(2), as one option.

FYI, supporting ticket: https://trac.cakephp.org/ticket/3903

PHP5 and tiny foreach() improvement

While this issue is certainly not specific to CakePHP, I felt it was worthwhile to point out since we are often dealing with data arrays and foreach() loops to modify that data.

Consider the following example:

function afterFind($results, $primary=false) {		
    	if($primary == true) {		   
    	  foreach($results as $key => $value) {    	      
    	      $results[$key][$this->alias]['my_field'] = $this->modifyField($value[$this->alias]['my_field']);    	      
    	  }
    	}	
    		
    	return $results;
}

While this works just fine for most cases, the main issue is that $value in the above example is actually a copy of the array’s content.

In PHP5 we have the option to reference the array elements of $results, by using: “$results as &$value”.

So the modified code for PHP5 would look like:

function afterFind($results, $primary=false) {		
    	if($primary == true) {		   
    	  foreach($results as &$value) {    	     
    	      $value[$this->alias]['my_field'] = $this->modifyField($value[$this->alias]['my_field']); 
    	  }
    	}	
    	unset($value);	
    	
        return $results;
}

The obvious improvement is that we are not creating any extra copies of our data. Sure, it may not cause any serious issues, but if making additional copies of the data can be easily avoided it probably should be. Secondly, it makes the code cleaner and more concise, which is always a good thing.

P.S. It is always a good idea to unset() the reference in case any further processing might accidentally change the value of our $results array.

A few words about media views (sending binary files to the user)

Media views is one really nice feature of CakePHP which doesn’t get much attention, so I figured to briefly cover it here. Actually the manual lists pretty much all you need to know about media views, but there are a couple of things that I’d like to point out. First, take a look at the brief example and more detailed explanation here: http://book.cakephp.org/view/489/Media-Views

One might ask what’s the point of using ‘id’ and ‘name’? Well, the ‘name’ key allows you to change the file name as it will be displayed to the user (in a “save” dialog, for example), it is not the actual name of the file on the server or at least it doesn’t have to be. For example, if you have a reporting tool that generates some files and their name on the server happens to be 329569364658875.zip, you can modify such ugly name on the fly by specifying: 'name' => 'user_friendly_file_name'.

Also, remember that ‘path’ is relative to your app directory and requires a trailing DS (directory separator). Therefore if you have a path like /home/bob/cake/app/files_for_download, you specify:
'path'=>'files_for_download'.DS

And just to reiterate what the manual says, media views allow you to perform authentication before delivering content and you can deliver files which are not accessible from webroot to prevent direct linking.

P.S. Thanks to skiedr and Tarique Sani who’ve pointed out that you can now cache the files generated by media views, which ultimately means that images, etc. can be fetched from the browser cache if available.