CakePHP is a web framework for rapid application development (RAD), which admittedly covers a wide range of areas and possibilities. However, at its core, it provides a solid architecture for the CRUD (create/read/update/delete) interface.
This chapter is a set of quick-start recipes to dive head first into using the framework and build out a simple CRUD around product management.
If you want to try the code examples on your own, make sure that you have CakePHP 2.5.2 installed and configured to use a database—you should see something like this:

To begin, we'll need a way to view the products available and also allow the option to select and view any one of those products.
In this recipe, we'll create a listing of products as well as a page where we can view the details of a single product.
To go through this recipe, we'll first need a table of data to work with. So, create a table named products
using the following SQL statement:
CREATE TABLE products ( id VARCHAR(36) NOT NULL, name VARCHAR(100), details TEXT, available TINYINT(1) UNSIGNED DEFAULT 1, created DATETIME, modified DATETIME, PRIMARY KEY(id) );
We'll then need some sample data to test with, so now run this SQL statement to insert some products:
INSERT INTO products (id, name, details, available, created, modified) VALUES ('535c460a-f230-4565-8378-7cae01314e03', 'Cake', 'Yummy and sweet', 1, NOW(), NOW()), ('535c4638-c708-4171-985a-743901314e03', 'Cookie', 'Browsers love cookies', 1, NOW(), NOW()), ('535c49d9-917c-4eab-854f-743801314e03', 'Helper', 'Helping you all the way', 1, NOW(), NOW());
Before we begin, we'll also need to create ProductsController
. To do so, create a file named ProductsController.php
in app/Controller/
and add the following content:
<?php App::uses('AppController', 'Controller'); class ProductsController extends AppController { public $helpers = array('Html', 'Form'); public $components = array('Session', 'Paginator'); }
Now, create a directory named Products/
in app/View/
. Then, in this directory, create one file named index.ctp
and another named view.ctp
.
Perform the following steps:
Define the pagination settings to sort the products by adding the following property to the
ProductsController
class:public $paginate = array( 'limit' => 10 );
Add the following
index()
method in theProductsController
class:public function index() { $this->Product->recursive = -1; $this->set('products', $this->paginate()); }
Introduce the following content in the
index.ctp
file that we created:<h2><?php echo __('Products'); ?></h2> <table> <tr> <th><?php echo $this->Paginator->sort('id'); ?></th> <th><?php echo $this->Paginator->sort('name'); ?></th> <th><?php echo $this->Paginator->sort('created'); ?></th> </tr> <?php foreach ($products as $product): ?> <tr> <td><?php echo $product['Product']['id']; ?></td> <td> <?php echo $this->Html->link($product['Product']['name'], array('controller' => 'products', 'action' => 'view', $product['Product']['id'])); ?> </td> <td><?php echo $this->Time->nice($product['Product']['created']); ?></td> </tr> <?php endforeach; ?> </table> <div> <?php echo $this->Paginator->counter(array('format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}'))); ?> </div> <div> <?php echo $this->Paginator->prev(__('< previous'), array(), null, array('class' => 'prev disabled')); echo $this->Paginator->numbers(array('separator' => '')); echo $this->Paginator->next(__('next >'), array(), null, array('class' => 'next disabled')); ?> </div>
Returning to the
ProductsController
class, add the followingview()
method to it:public function view($id) { if (!($product = $this->Product->findById($id))) { throw new NotFoundException(__('Product not found')); } $this->set(compact('product')); }
Introduce the following content in the
view.ctp
file:<h2><?php echo h($product['Product']['name']); ?></h2> <p> <?php echo h($product['Product']['details']); ?> </p> <dl> <dt><?php echo __('Available'); ?></dt> <dd><?php echo __((bool)$product['Product']['available'] ? 'Yes' : 'No'); ?></dd> <dt><?php echo __('Created'); ?></dt> <dd><?php echo $this->Time->nice($product['Product']['created']); ?></dd> <dt><?php echo __('Modified'); ?></dt> <dd><?php echo $this->Time->nice($product['Product']['modified']); ?></dd> </dl>
Now, navigating to
/products
in your web browser will display a listing of the products, as shown in the following screenshot:Clicking on one of the product names in the listing will redirect you to a detailed view of the product, as shown in the following screenshot:
We started by defining the pagination setting in our ProductsController
class, which defines how the results are treated when returning them via the Paginator
component (previously defined in the $components
property of the controller). Pagination is a powerful feature of CakePHP, which extends well beyond simply defining the number of results or sort order.
We then added an index()
method to our ProductsController
class, which returns the listing of products. You'll first notice that we accessed a $Product
property on the controller. This is the model that we are acting against to read from our table in the database. We didn't create a file or class for this model, as we're taking full advantage of the framework's ability to determine the aspects of our application through convention. Here, as our controller is called ProductsController
(in plural), it automatically assumes a Product
(in singular) model. Then, in turn, this Product
model assumes a products
table in our database. This alone is a prime example of how CakePHP can speed up development by making use of these conventions.
You'll also notice that in our ProductsController::index()
method, we set the $recursive
property of the Product
model to -1
. This is to tell our model that we're not interested in resolving any associations on it. Associations are other models that are related to this one. This is another powerful aspect of CakePHP. It allows you to determine how models are related to each other, allowing the framework to dynamically generate those links so that you can return results with the relations already mapped out for you. We then called the paginate()
method to handle the resolving of the results via the Paginator
component.
Tip
It's common practice to set the $recursive
property of all models to -1
by default. This saves heavy queries where associations are resolved to return the related models, when it may not be necessary for the query at hand. This can be done via the AppModel
class, which all models extend, or via an intermediate class that you may be using in your application.
We had also defined a view($id)
method, which is used to resolve a single product and display its details. First, you probably noticed that our method receives an $id
argument. By default, CakePHP treats the arguments in methods for actions as parts of the URL. So, if we have a product with an ID of 123, the URL would be /products/view/123
. In this case, as our argument doesn't have a default value, in its absence from the URL, the framework would return an error page, which states that an argument was required. You will also notice that our IDs in the products
table aren't sequential numbers in this case. This is because we defined our id
field as VARCHAR(36)
. When doing this, CakePHP will use a Universally Unique Identifier (UUID) instead of an auto_increment
value.
Tip
To use a UUID instead of a sequential ID, you can use either CHAR(36)
or BINARY(36)
. Here, we used VARCHAR(36)
, but note that it can be less performant than BINARY(36)
due to collation.
The use of UUID versus a sequential ID is usually preferred due to obfuscation, where it's harder to guess a string of 36 characters, but also more importantly, if you use database partitioning, replication, or any other means of distributing or clustering your data.
We then used the findById()
method on the Product
model to return a product by it's ID (the one passed to the action). This method is actually a magic method. Just as you can return a record by its ID, by changing the method to findByAvailable()
. For example, you would be able to get all records that have the given value for the available
field in the table. These methods are very useful to easily perform queries on the associated table without having to define the methods in question.
We also threw NotFoundException
for the cases in which a product isn't found for the given ID. This exception is HTTP aware, so it results in an error page if thrown from an action.
Finally, we used the set()
method to assign the result to a variable in the view. Here we're using the compact()
function in PHP, which converts the given variable names into an associative array, where the key is the variable name, and the value is the variable's value. In this case, this provides a $product
variable with the results array in the view. You'll find this function useful to rapidly assign variables for your views.
We also created our views using HTML, making use of the Paginator
, Html
, and Time
helpers. You may have noticed that the usage of TimeHelper
was not declared in the $helpers
property of our ProductsController
. This is because CakePHP is able to find and instantiate helpers from the core or the application automatically, when it's used in the view for the first time. Then, the sort()
method on the Paginator
helper helps you create links, which, when clicked on, toggle the sorting of the results by that field. Likewise, the counter()
, prev()
, numbers()
, and next()
methods create the paging controls for the table of products.
You will also notice the structure of the array that we assigned from our controller. This is the common structure of results returned by a model. This can vary slightly, depending on the type of find()
performed (in this case, all
), but the typical structure would be as follows (using the real data from our products
table here):
Array ( [0] => Array ( [Product] => Array ( [id] => 535c460a-f230-4565-8378-7cae01314e03 [name] => Cake [details] => Yummy and sweet [available] => true [created] => 2014-06-12 15:55:32 [modified] => 2014-06-12 15:55:32 ) ) [1] => Array ( [Product] => Array ( [id] => 535c4638-c708-4171-985a-743901314e03 [name] => Cookie [details] => Browsers love cookies [available] => true [created] => 2014-06-12 15:55:33 [modified] => 2014-06-12 15:55:33 ) ) [2] => Array ( [Product] => Array ( [id] => 535c49d9-917c-4eab-854f-743801314e03 [name] => Helper [details] => Helping you all the way [available] => true [created] => 2014-06-12 15:55:34 [modified] => 2014-06-12 15:55:34 ) ) )
We also used the link()
method on the Html
helper, which provides us with the ability to perform reverse routing to generate the link to the desired controller and action, with arguments if applicable. Here, the absence of a controller assumes the current controller, in this case, products
.
Finally, you may have seen that we used the __()
function when writing text in our views. This function is used to handle translations and internationalization of your application. When using this function, if you were to provide your application in various languages, you would only need to handle the translation of your content and would have no need to revise and modify the code in your views.
There are other variations of this function, such as __d()
and __n()
, which allow you to enhance how you handle the translations. Even if you have no initial intention of providing your application in multiple languages, it's always recommended that you use these functions. You never know, using CakePHP might enable you to create a world class application, which is offered to millions of users around the globe!
A complete detail of the conventions in CakePHP can be found at http://book.cakephp.org/2.0/en/getting-started/cakephp-conventions.html
A complete overview of the pagination options available can be found at http://book.cakephp.org/2.0/en/core-libraries/components/pagination.html
Note
Note that multiple components can be defined in a controller by simply defining them in the array of the
$components
property. More information on Components in CakePHP can be found here: http://book.cakephp.org/2.0/en/controllers/components.htmlAdditional information on reading data from Models can be found at http://book.cakephp.org/2.0/en/models/retrieving-your-data.html
For more details on Helpers in CakePHP, see the documentation at http//book.cakephp.org/2.0/en/views/helpers.html
The Translations recipe from Chapter 10, View Templates
While listing and viewing records is handy, the ability to create and edit records allows you to build up and maintain your data.
In this recipe, we'll create actions to both add new products and edit the existing ones in our database.
For this recipe, we'll continue using the products
table from the previous recipe. We'll also extend the ProductsController
that was created.
For the views, we'll add add.ctp
and edit.ctp
files to our app/View/Products/
directory, and also a form.ctp
file in the Products/
directory that we'll create in app/View/Elements/
.
Perform the following steps:
Add the following
add()
method to theProductsController
class:public function add() { if ($this->request->is('post')) { $this->Product->create(); if ($this->Product->save($this->request->data)) { $this->Session->setFlash(__('New product created')); return $this->redirect(array('action' => 'index')); } $this->Session->setFlash(__('Could not create product')); } }
Just below the
add()
method, also add anedit()
method:public function edit($id) { $product = $this->Product->findById($id); if (!$product) { throw new NotFoundException(__('Product not found')); } if ($this->request->is('post')) { $this->Product->id = $id; if ($this->Product->save($this->request->data)) { $this->Session->setFlash(__('Product updated')); return $this->redirect(array('action' => 'index')); } $this->Session->setFlash(__('Could not update product')); } else { $this->request->data = $product; } }
Introduce the following content in the
form.ctp
element file:<?php echo $this->Form->create('Product'); echo $this->Form->inputs(); echo $this->Form->end(__('Submit'));
Introduce the following content in the
add.ctp
file:<?php echo $this->element('Products/form'); ?>
The
edit.ctp
file will also take the same content, but the header text will be changed to the following code:<?php echo $this->element('Products/form'); ?>
Return to the
index.ctp
file, and change it's content to the following:<h2><?php echo __('Products'); ?></h2> <div> <?php echo $this->Html->link(__('Add new product'), array('action' => 'add')); ?> </div> <table> <tr> <th><?php echo $this->Paginator->sort('id'); ?></th> <th><?php echo $this->Paginator->sort('name'); ?></th> <th><?php echo $this->Paginator->sort('created'); ?></th> <th><?php echo __('Actions'); ?></th> </tr> <?php foreach ($products as $product): ?> <tr> <td><?php echo $product['Product']['id']; ?></td> <td><?php echo $this->Html->link($product['Product']['name'], array('action' => 'view', $product['Product']['id'])); ?></td> <td><?php echo $this->Time->nice($product['Product']['created']); ?></td> <td><?php echo $this->Html->link(__('Edit'), array('action' => 'edit', $product['Product']['id'])); ?></td> </tr> <?php endforeach; ?> </table> <div> <?php echo $this->Paginator->counter(array('format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}'))); ?> </div> <div> <?php echo $this->Paginator->prev(__('< previous'), array(), null, array('class' => 'prev disabled')); echo $this->Paginator->numbers(array('separator' => '')); echo $this->Paginator->next(__('next >'), array(), null, array('class' => 'next disabled')); ?> </div>
Navigate to
/products
in your web browser, and click one of the Edit links to modify a product. The following screenshot shows the screen that will appear:Return to
/products
, and click on the Add new product link to create a new product. The following screenshot shows the screen that will appear:
Here, we extended our previous recipe by adding some extra methods to create new products and edit the existing ones. The add()
method first checks whether the current request has been made using the HTTP POST
method. If that's the case, we call the create()
method on the Product
model. This doesn't create a new record yet, but instead it prepares our model object for a new record to be created. We then call the save()
method, passing the data provided in the request to it. The framework handles this internally through the Form
helper, which we'll see in a moment. The condition checks whether the save is successful (here, a new record is created), and if so, it calls the setFlash()
method on our Session
component to register a success message to be displayed on the page that follows. We do the same in the event that the record could not be saved, and it provided a failure message. We then wrap up the method by redirecting the request to our index()
action.
For the edit()
method, we first check that the product for the given ID actually exists using the findById()
method on the Product
model. See the previous recipe, Listing and viewing records, for details on finding records. If the product doesn't exist, a NotFoundException
is thrown; this is rendered as an error page. As with the add()
method, we first check that the request was made via POST
. However, instead of calling the create()
method on our Product
model, we set the $id
property with the $id
argument passed to our action. We then follow the same process of calling the save()
method with the request data, as well as setting the result messages for the view and redirecting the request.
We finalize our edit action by populating the request data if it does not exist so that when you visit the form for editing, it's populated with the existing values from the products
table.
For our views, we've taken the initiative to use an element. These are reusable sections of our views, which allow us to segment and organize our visual interface and cut down on duplicate code. Here, we've done this to avoid declaring the same form twice and reuse the same one instead. The framework is able to distinguish between the two (adding a record and editing a record) by the presence of an ID. In which case, it assumes that we're editing a record instead of creating one. In this file, we use the Form
helper to generate a new form using the create()
method and passing the name of the model to it, it will act against. We also called the inputs()
method to create the required inputs based on the table schema and then called the end()
method to complete the form.
For our add.ctp
and edit.ctp
view files, we included our element using the element()
method, passing it the location of our form.ctp
file. You'll notice that we created our element in the Products/
directory, as the form is intended for a product. We could change the contents of our element to receive the model name via an element parameter, making it more dynamic and, therefore, reusable even further. Finally, we updated the index.ctp
view to include an Edit option using the link()
method from the Html
helper.
You'll also see that we passed the ID of each product to this method in our foreach()
statement, thus generating a link for each product with its unique ID as part of the URL. We also added a link to "add a new product", which redirects you to the new add()
action to create a new record in the products
table.
You can read more on working with the session in CakePHP at http://book.cakephp.org/2.0/en/core-libraries/components/sessions.html
More details on saving data in Models can be found at http://book.cakephp.org/2.0/en/models/saving-your-data.html
For a complete overview of the Form helper in CakePHP, go to http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html
The
Html
helper in CakePHP will become a common tool used in all of your views; read more about it at http://book.cakephp.org/2.0/en/core-libraries/helpers/html.htmlThe Listing and viewing records recipe
Just as we can list and view records, as well as edit and create new ones, you'll also want to be familiar with how to delete records using CakePHP.
In this recipe, we'll create an action that allows us to delete records from our table.
As with the previous recipe, we'll continue using the products
table and also extend the ProductsController
that we created.
Perform the following steps:
Add the following
delete()
method to theProductsController
class:public function delete($id) { if (!$this->request->is('post')) { throw new MethodNotAllowedException(); } if ($this->Product->delete($id)) { $this->Session->setFlash(__('Product removed: %s', $id)); return $this->redirect(array('action' => 'index')); } $this->Session->setFlash(__('Could not remove product')); return $this->redirect($this->referer()); }
Change the content of the
index.ctp
file to the following:<h2><?php echo __('Products'); ?></h2> <div> <?php echo $this->Html->link(__('Add new product'), array('action' => 'add')); ?> </div> <table> <tr> <th><?php echo $this->Paginator->sort('id'); ?></th> <th><?php echo $this->Paginator->sort('name'); ?></th> <th><?php echo $this->Paginator->sort('created'); ?></th> <th><?php echo __('Actions'); ?></th> </tr> <?php foreach ($products as $product): ?> <tr> <td><?php echo $product['Product']['id']; ?></td> <td><?php echo $this->Html->link($product['Product']['name'], array('action' => 'view', $product['Product']['id'])); ?></td> <td><?php echo $this->Time->nice($product['Product']['created']); ?></td> <td> <?php echo $this->Html->link(__('Edit'), array('action' => 'edit', $product['Product']['id'])); echo $this->Form->postLink(__('Delete'), array('action' => 'delete', $product['Product']['id']), array('confirm' => 'Delete this product?')); ?> </td> </tr> <?php endforeach; ?> </table> <div> <?php echo $this->Paginator->counter(array('format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}'))); ?> </div> <div> <?php echo $this->Paginator->prev(__('< previous'), array(), null, array('class' => 'prev disabled')); echo $this->Paginator->numbers(array('separator' => '')); echo $this->Paginator->next(__('next >'), array(), null, array('class' => 'next disabled')); ?> </div>
In this recipe, we added a delete()
method to our ProductsController
class. This first checks whether the HTTP method used to call the action was POST
. In the event it wasn't, we throw a MethodNotAllowedException
exception, which is presented as an error page when it is thrown from an action. This is to protect our application against attempts to delete data via a simple URL. If the HTTP method is correct, we proceed to call the delete()
method on the Product
model, passing to it the ID of the record to delete. If it is successful, we call the setFlash()
method on our Session
component with a message confirming the removal or with a failure message if the delete()
method fails. Finally, we redirect the request to the index()
action if the record was deleted successfully—if not, then we are redirecting the user back to the URL where the delete()
method was called from. It may also be worth mentioning that the delete()
action itself doesn't have a view associated with it, as nothing is displayed to the user as part of the request.
We then returned to the index.ctp
view to add an option to delete a product using the postLink()
method of the Form
helper. This creates a form with a link that allows us to use POST
when clicking on the link which submits the form. You will notice that this takes a third argument, which is an array with a confirm
key. This prompts the user to confirm the action before submitting the request to delete the record. This is always recommended when dealing with actions that delete records, in case of a mistake on the user's behalf.
More details on deleting data in your Models can be found at http://book.cakephp.org/2.0/en/models/deleting-data.html
The Adding and editing records recipe
It's not going to be long before you need to control access to certain areas of your application.
In this recipe, we'll look at adding a basic authentication layer to our existing products section, with a login view to enter your credentials.
For this recipe, we'll need a table for our users. Create a table named users
, using the following SQL statement:
CREATE TABLE users ( id VARCHAR(36) NOT NULL, username VARCHAR(20), password VARCHAR(100), created DATETIME, modified DATETIME, PRIMARY KEY(id) );
We'll then create a User.php
file in app/Model/
, which will have the following content:
<?php App::uses('AppModel', 'Model'); App::uses('SimplePasswordHasher', 'Controller/Component/Auth'); class User extends AppModel { }
We'll also need a UsersController.php
file in app/Controller/
with the following content:
<?php App::uses('AppController', 'Controller'); class UsersController extends AppController { }
Finally, also create a Users
directory in app/View/
, and create register.ctp
and login.ctp
files in the new directory.
Perform the following steps:
Add the validation rules to the
User
model inapp/Model/User.php
with the following$validate
property:public $validate = array( 'username' => array( 'required' => array( 'rule' => 'notEmpty', 'message' => 'Please enter a username' ) ), 'password' => array( 'required' => array( 'rule' => 'notEmpty', 'message' => 'Please enter a password' ) ) );
In the same class, add this
beforeSave()
method:public function beforeSave($options = array()) { if (!parent::beforeSave($options)) { return false; } if (isset($this->data[$this->alias]['password'])) { $hasher = new SimplePasswordHasher(); $this->data[$this->alias]['password'] = $hasher->hash($this->data[$this->alias]['password']); } return true; }
Locate the
AppController.php
file inapp/Controller/
, and add the following$components
property to the same class:public $components = array( 'Session', 'Auth' => array( 'loginRedirect' => array('controller' => 'products'), 'logoutRedirect' => array( 'controller' => 'users', 'action' => 'login' ) ) );
Open the
UsersController.php
file and add the following methods:public function beforeFilter() { parent::beforeFilter(); $this->Auth->allow(); } public function register() { if ($this->request->is('post')) { $this->User->create(); if ($this->User->save($this->request->data)) { $this->Session->setFlash(__('New user registered')); return $this->redirect(array('action' => 'login')); } $this->Session->setFlash(__('Could not register user')); } } public function login() { if ($this->request->is('post')) { if ($this->Auth->login()) { return $this->redirect($this->Auth->redirectUrl()); } $this->Session->setFlash(__('Incorrect username or password')); } } public function logout() { return $this->redirect($this->Auth->logout()); }
Locate the
register.ctp
file inapp/View/Users/
and introduce the following content:<h2><?php echo __('Register'); ?></h2> <?php echo $this->Form->create('User'); echo $this->Form->inputs(); echo $this->Form->end(__('Register'));
In the same directory, open the
login.ctp
file, and add the following content:<h2><?php echo __('Login'); ?></h2> <?php echo $this->Session->flash('auth'); echo $this->Form->create('User'); echo $this->Form->inputs(array( 'username', 'password', 'legend' => __('Login, please') )); echo $this->Form->end(__('Sign In'));
For this recipe, we first included a $validate
property in our User
model. This array is used to define the validation rules applied when creating or modifying records. Here, we simply defined the username
and password
fields as required, not allowing an empty value and specifying some custom messages to be returned by the model if the fields fail to validate correctly. There are plenty more validation rules such as alphaNumeric
, minLength
, between
, date
, and email
. You can also create your own rules for custom validation.
After setting up our validation, we also added a beforeSave()
method. This is one of the callback methods available on all models, to hook into a certain point of the process. These are beforeFind()
, afterFind()
, beforeSave()
, afterSave()
, beforeDelete()
, afterDelete()
, beforeValidate()
, afterValidate()
, and onError()
. In our method, we added some logic to process the password and generated a hash value before saving it to our users
table. This way, we store a representation of the password, instead of the password itself. This is very important as you don't want anyone viewing the actual passwords in your database.
The SimplePasswordHasher
class helps us here by providing an easy API to quickly generate hashes of any value. You will also notice our use of $this->alias
. This is the recommended method of referring to the model, to allow for extensions or aliasing of a model without impacting the internal logic.
We then added the Auth
component to the $components
array of AppController
, with some global settings. This controller acts as a base controller for your application, so behavior or functionality that needs to be propagated to your entire application should be added in this class. Here, we defined the loginRedirect
and logoutRedirect
settings, which define the controller and action to redirect to in each case (after login or after logout). Where the action is not defined, index
will be assumed by default.
After that, we proceeded to add some methods to our UsersController
. The first of these is the beforeFilter()
method. This is one of the callback methods available on all controllers, which include beforeFilter()
, afterFilter()
, beforeRender()
, and beforeRedirect()
. Here, we first called parent::beforeFilter()
to make sure that we included any logic that has been defined by AppController
. We then called the allow()
method on the Auth
component to allow access to the methods in the controller. Here, you could also pass some method names to only allow certain action, or also use the deny()
method to explicitly deny some actions and require login.
After that, we added a register()
method, using the same logic to create a record as we did in the ProductsController
from a previous recipe, in order to allow the creation of new users. Here, we've only used a simple example, without contemplating any postregistration checks, such as a token via e-mail for confirmation. You can easily include this and many more features using a plugin, such as the CakeDC Users plugin, found at https://github.com/CakeDC/users.
We also included both the login()
and logout()
methods, which use the API exposed by the Auth
component to process the user login. As we're following convention by creating a users
table with the username
and password
fields, we take advantage of the framework's ability to configure most of the sign-in process for us. In our login action, we first called the login()
method on the Auth
component, which internally checks the data passed in the request. If this is successful, we redirect the user using the redirect()
method from the Auth
component, which takes the target we previously defined in the loginRedirect
setting. If the process were to fail, we stay in the same action but use the setFlash()
method from the Session
component to display a message to the user that the sign in was unsuccessful. The logout()
method is even easier, simply calling the logout()
method on the Auth
component and redirecting the user to that location. Here, as with the login()
method, we use the logoutRedirect
setting we had previously defined. As you can see so far, authentication in CakePHP is really a piece of cake.
We then went on to create our views to register a new user and sign in. In our first view, register.ctp
, we used the create()
method of the Form
helper to create a form for the User
model. We then used the inputs()
methods to render the required inputs and finally called end()
, passing our text for the submit button. In our login.ctp
view, we did almost the same; except here, we also enlisted required inputs, added custom form legend, and called the flash()
method on the Session
helper, passing auth
as the argument to it. This renders a location to collect the flash messages sent from the Auth
component, such as the message we display when the login fails. The use of the auth
value allows you to easily manage the messages being collected, in case you'd like them to be displayed differently or in different locations of your view.
If you go to /products
in your browser now, you will be automatically redirected to the login page, and the default message for unauthorized access will be displayed, as shown in the following screenshot:

Of course, to log in, you'll have to register a new user account first by going to the /users/register
URL in your browser.
Read more on data validation in CakePHP at http://book.cakephp.org/2.0/en/models/data-validation.html
More details on callback methods in Models can be found at http://book.cakephp.org/2.0/en/models/callback-methods.html
More settings available for the Auth component can be found at http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html
The Authentication API recipe in Chapter 4, API Strategies
Chapter 5, Using Authentication
One of the greatest benefits of using CakePHP is its extensibility. Using plugins, you can extend and enhance the core functionality, which provides you with even more cake than you bargained for!
In this recipe, we'll look at loading a plugin using DebugKit, the official development and debugging tool for the framework, as an example.
We'll need to have DebugKit included in the application file structure before we begin. There are a couple of ways to do this.
Git clone or submodule
If you use Git, you can create a clone of the code by executing the following command from your app/
directory on the command line:
$ git clone https://github.com/cakephp/debug_kit.git Plugin/DebugKit
If you already have your application under version control using Git, you can also add it as a submodule. Assuming that the base of your repository is the app/
folder, you would run the following command:
$ git submodule add https://github.com/cakephp/debug_kit.git app/Plugin/DebugKit
Using Composer
If you're using Composer as your dependency manager, you can simply include DebugKit as a plugin by adding the following code to your composer.json
file:
{ "require": { "cakephp/debug_kit": "2.2.*" } }
After updating your composer.json
file, simply update your application's dependencies using Composer.
Downloading files
An alternative method is to simply download the files and include them manually in your application from the following location:
Perform the following steps:
Add the following code to your
bootstrap.php
file, found inapp/Config/
:CakePlugin::load('DebugKit');
Add the following code to your
AppController
inapp/Controller/
:public $components = array( 'Session', 'Auth', 'DebugKit.Toolbar' );
Open your
core.php
file inapp/Config/
, and check if the following configuration value is set to a value higher than0
:Configure::write('debug', 2);
Now, load your application in your browser, and you'll find an icon in the top right-hand corner of the screen, as shown in the following screenshot:
Click on the icon to see various panels (shown in the following screenshots) with data related to the current request:
When we call CakePlugin::load()
, we request the framework to load the plugin by the given name, which also accepts an optional second argument, which is the configuration array for the plugin. We had already included the DebugKit/
directory in app/Plugin/
, which is where plugins are located. You can change the location of where the plugins are loaded from using the path
option in the plugin configuration array.
After loading the plugin, we included the Toolbar
component in our AppController
so it's available in all applications. This is a component included with the DebugKit plugin, which allows us to display a toolbar in the browser, thus providing a range of panels to introspect the application, the current request, SQL queries, and more. You'll notice that we included both the Session
and Auth
components here as well to propagate them in our application. The plugin itself is also extensible, allowing additional panels to be added. Once our plugin was loaded and the Toolbar
component available, we made sure that the debug mode was enabled in our core.php
file; we then navigated to the application in the browser to view the loaded toolbar.
Plugins may also load their own bootstrap.php
and routes.php
files to set up configurations and define routing settings. However, when a plugin is loaded, you must specify to load these configurations and settings. For example, if you had a Reports
plugin that used these, you would load the plugin using the plugin configuration array as follows:
CakePlugin::load('Reports', array( 'bootstrap' => true, 'routes' => true ));
You can also load all plugins in your app/Plugin/
directory at once by calling CakePlugin::loadAll()
.
For a complete overview of plugins in CakePHP, go to http://book.cakephp.org/2.0/en/plugins.html
The The Search plugin recipe in Chapter 7, Search and Pagination
The Generating a PDF and The AssetCompress plugin recipes in Chapter 10, View Templates