CakePHP 1.2 Stable Released

It seems the good ole developers over at CakePHP have released an update to their 1.2 Final version, a now 1.2 Stable. As always I enjoy and appreciate how quickly the Cake team fixes, rewrites and optimizes the Cake engine. They have come a long way and with the help of the community, the Forge and the Bakery. The best thing is their use of Trac and the ability of the community to report bugs and even suggest features and code to be added. I have many ideas and have even written my own code that hopefully one day I can submit to Trac, and maybe perhaps join the team!

Anyways though, I highly recommend upgrading your Cake to the 1.2 Stable, simply because they found a security flaw with the Security and Auth Component. For more information on that flaw, the announcement, and the download, please click the following links:

Displaying form errors as a list in CakePHP

When a form is submitted in CakePHP, any errors that fail the model validation are displayed after each respective input field. But if you're like me and really dislike that structure and output, and would rather have your errors displayed in a list above the form (an example would be my contact form), then this is how you would achieve it. In the example we will be doing a login for the UsersController (do note that I am not using the AuthComponent, this is just a rough example). Lets begin by creating a file and placing it in your views/elements/errors.ctp folder.

<?php // views/elements/errors.ctp
if (!empty($errors)) { ?>
<div class="errors">
    <h3>There are <?php echo count($errors); ?> error(s) in your submission:</h3>
    <ul>
        <?php foreach ($errors as $field => $error) { ?>
        <li><?php echo $error; ?></li>
        <?php } ?>
    </ul>
</div>
<?php } ?>
Validation in the Model

For any form validation to work, we must declare a validation scheme within the respective model, in our case its the User model. Since this is a login form, the only validation we will need is for a username and password. The rules we will be applying is that both the username and password must not be empty. Once we have the validation setup, the next step is to setup the UsersController.

<?php
class User extends AppModel {
	public $validate = array(
		'username' => array(
			'notEmpty' => array(
				'rule' => 'notEmpty',
				'message' => 'Your username is required'
		)),
		'password' => array(
			'notEmpty' => array(
				'rule' => 'notEmpty',
				'message' => 'Your password is required'
		)),
	);
}
?>
Setting up the Controller

In the login action we will be using the RequestHandlerComponent to check for a form submission (this is a better approach then doing isset/!empty on $this->data). If a post is successful, we will save the form data to the User model to run it against the validation. Lastly, we need to be able to display the errors in the view, so we will set() them to $errors by calling the $this->User->validationErrors. The $errors variable is then passed to the errors.ctp element template, which in turn displays all the errors found.

<?php // controllers/users_controller.php
public function login() {
    if ($this->RequestHandler->isPost()) {
        $this->User->set($this->data);
        if ($this->User->validates()) {
        	if ($this->User->verifyLogin($this->data)) {
				// Process information
			}
        } else {
			// Didn't validate
		}
    }
    $this->set('errors', $this->User->validationErrors);
}
// models/user.php
public function verifyLogin($data) {
	$user = $this->find('first', array(
		'conditions' => array(
			'User.username' => $data['User']['username'],
			'User.password' => md5($data['User']['password'])
		)
	));
	if (!empty($user)) {
		return $user;
	} else {
		$this->invalidate('', 'No user was found with those login credentials');
	}
	return false;
} ?>

The two most important things to note is $validationErrors and the invalidate() method. The $validationErrors property is a list of all the error messages in the form for the corresponding input fields; it's called from each respective model. The method invalidate() is used to trigger a custom error that is not part of the $validate scheme for the model. Now lets look back at the controller where the verifyLogin() method is called. If that method fails (returns false), the login fails and no processing is made. On the other hand if the method returns true, lets continue and log them in.

Displaying my errors in the View

Now that we have the validation set, the errors template created and the form processing complete, the last task is to display the errors in the view. Since the controller is already setting the errors into the view, we just need to include the element, and to do that we will use the $this->element() method within the view. Just place this element in the location you want your errors to display.

<?php echo $this->element('errors', array('errors' => $errors)); ?>

Now if you have tested the form, you will notice that the errors are still showing up after the input field. To fix this, we have to disable the errors on the input by setting it to false.

<?php echo $form->input('username', array('error' => false)); ?>
Conclusion

This was a bit more lengthy then I expected, but I hope it enlightened you on how to use and display form errors. You also received a bit more knowledge on how the invalidate() and invalidFields() method work (hopefully ;)). If you have any questions please comment or send me an email, or take a gander at the CakePHP API and Cookbook.

Using CakePHP's Auth Component

This article pertains to CakePHP 1.2 only!

Since recently diving into CakePHP, I thought the best area to learn would be the user authentication and login. Luckily CakePHP comes bundled with an Authorization Component, and is literally the easiest thing to install. Below I will show you the basics of getting Auth working; do note that I was working in CakePHP 1.2 and PHP 5. To begin the following items must be implemented:

  • Database - A users table with the fields id, username and password (can be customized but recommended)
  • Controller - A users controller with a login and logout action
  • Model - A user model that correlates with the users table
Setting up AppController

The first thing we need to do is setup the AppController. We are placing the code in the AppController so that all child controllers inherit the AuthComponent. Begin by including the AuthComponent by binding it to the $components parameter. We will now create the beforeFilter() action that will hold all the Auth settings that will be passed to the child controllers.

<?php
class AppController extends Controller {
	public $components = array('Auth');
	public function beforeFilter() { 
		Security::setHash('md5');
		// Authenticate
		$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
		$this->Auth->loginRedirect = array('controller' => 'dashboard', 'action' => 'index');
		$this->Auth->loginError = 'No username and password was found with that combination.';
		$this->Auth->logoutRedirect = '/';
	}
} 
?>

In the beforeFilter() action above, you will notice 4 different variables being set. loginRedirect determines what controller/action pair to redirect to upon a successful login, where as logoutRedirect is the route to redirect to when logging out. loginAction is the controller/action in which your login form and processing will be taking place (its best to use CakePHPs defaults of /user/login/). For more information on these variables and additional variables, visit the corresponding cookbook page.

Setting up the UsersController

The next step is to create the UsersController and create the required actions. Within the login() action, there is no need to do any manual form processing or login, the auth system does that magically behind the scenes. You can literally leave this action empty, unless you wish to add a page title $this->pageTitle or validate it against the User model. Within the logout() action we run a redirect to the auth system, which logs us out and redirects to the logoutRedirect we specified in the AppController.

<?php
class UsersController extends AppController {
	public function login() {
		// No form processing needed, Auth does it automatically
	}
	public function logout() {
		$this->redirect($this->Auth->logout());
	}
	public function beforeFilter() {
		parent::beforeFilter();
		// Does not require being logged in
		$this->Auth->allow('signup', 'forgot');
		// If logged in, these pages require logout
		if ($this->Auth->user() && in_array($this->params['action'], array('signup', 'login'))) {
			$this->redirect('/');
		}
	}
}
?>

Now you may see some additional code; another beforeFilter()! This beforeFilter() is specific only to the current controller. It is extremely important to place parent::beforeFilter() in the action so that the parent beforeFilter() can initialize the AuthComponent. The allow() method takes arguments of actions (within the current controller) that do not require you to be logged in to view. Beneath that you will see some custom code that I have written. This code defines actions that require you to be logged out to view (it seems auth does not have anything for that). There are additional methods you can read about in the cookbook.

Creating the form

This is probably the easiest step of the process. Simply use Cake's built in HtmlHelper to build the form. Be sure to name the form after the model you are using for auth (e.g., User).

<?php 
echo $form->create('User', array('url' => array('controller' => 'users', 'action' => 'login')));
echo $form->input('username');
echo $form->input('password');
echo $form->submit('Login');
echo $form->end(); 
?>
Adding additional functionality

There are a couple ways to increase the functionality of the AuthComponent. Typically when you login, the user information in the database that matches the username/password combo is then stored in the AuthComponent. To retrieve the users information you would use the method $this->Auth->user(). A nice way of doing things is binding a $user variable to all views so you can access the users data.

// app_controller.php
public function beforeRender() {
    $this->set('user', $this->Auth->user());
    // In the views $user['User']['username'] would display the logged in users username
}
// controllers/users_controller.php
public function beforeRender() {
	parent::beforeRender();
}

If you would like to use a different model with the AuthComponent, you would add a $this->Auth->userModel variable in AppController::beforeFilter(). You can also add some additional conditions to the login query, for example, making sure a user is active.

$this->Auth->userModel = 'Member';
$this->Auth->userScope = array('User.status' => 'active');
Debugging problems

While working with the AuthComponent, I ran into one major problem. If you are using an old database that is ported over to use CakePHP and you did not use a password salt on the old passwords, be sure to disable Cake's salt (or leave it empty). I didn't realize this for nearly 2 days on why the passwords did not match and then it hit me, Cake's salted passwords will not match existing passwords. If this is a new database, no need to worry about the previous statements.

If for some reason you cannot have a field labeled username or password within your database, there are ways to set it to something else. You would do that by setting the $this->Auth->fields variables in AppController::beforeFilter(). For more information on this subject, visit the trouble shooting in the cookbook.

$this->Auth->fields = array('username' => 'email', 'password' => 'secretpass');
Conclusion

I hope this has been helpful to someone! I spent days debugging and installing this system and by doing so I learned the system in and out. If you have any questions be sure to leave a comment or drop a line by using the contact form. Thanks again for reading, come back for more tutorials!

Debugging the CakePHP Auth Component

Finally, I figured it out. For the past 2 days I have been messing with the CakePHP AuthComponent (A prebuilt user login and authentication system). The problem was that it would not log in, even though I was entering correct data from the database. I spent many hours and even posted on the Google CakePHP Discussion group. Even with their help it still would not work, but I found the problem!

CakePHP comes with a password salt feature that automatically salts and encrypts any index called "password" within an array. The problem was that the previous passwords in the database were not encrypted with a salt, so hence the passwords never matched! Once I made the Cake salt empty, it worked in all its glory. I will be posting up a tutorial this week on how to use the AuthComponent, for anyone who was as lost as me trying to get it working.