Home Web Development CakePHP 2 Application Cookbook

CakePHP 2 Application Cookbook

By James Watts , Cake Software Foundation, Inc.
books-svg-icon Book
eBook $32.99 $22.99
Print $54.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $32.99 $22.99
Print $54.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Lightning Introduction
About this book
Publication date:
August 2014
Publisher
Packt
Pages
346
ISBN
9781782160083

 

Chapter 1. Lightning Introduction

In this chapter, we will cover the following recipes:

  • Listing and viewing records

  • Adding and editing records

  • Deleting records

  • Adding a login

  • Including a plugin

 

Introduction


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:

 

Listing and viewing records


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.

Getting ready

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.

How to do it...

Perform the following steps:

  1. Define the pagination settings to sort the products by adding the following property to the ProductsController class:

    public $paginate = array(
      'limit' => 10
    );
  2. Add the following index() method in the ProductsController class:

    public function index() {
      $this->Product->recursive = -1;
      $this->set('products', $this->paginate());
    }
  3. 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>
  4. Returning to the ProductsController class, add the following view() method to it:

    public function view($id) {
      if (!($product = $this->Product->findById($id))) {
        throw new NotFoundException(__('Product not found'));
      }
      $this->set(compact('product'));
    }
  5. 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>
  6. Now, navigating to /products in your web browser will display a listing of the products, as shown in the following screenshot:

  7. 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:

How it works...

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!

See also

 

Adding and editing records


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.

Getting ready

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/.

How to do it...

Perform the following steps:

  1. Add the following add() method to the ProductsController 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'));
      }
    }
  2. Just below the add() method, also add an edit() 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;
      }
    }
  3. 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'));
  4. Introduce the following content in the add.ctp file:

    <?php echo $this->element('Products/form'); ?>
  5. 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'); ?>
  6. 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>
  7. 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:

  8. 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:

How it works...

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.

See also

 

Deleting records


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.

Getting ready

As with the previous recipe, we'll continue using the products table and also extend the ProductsController that we created.

How to do it...

Perform the following steps:

  1. Add the following delete() method to the ProductsController 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());
    }
  2. 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>

How it works...

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.

See also

 

Adding a login


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.

Getting ready

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.

How to do it...

Perform the following steps:

  1. Add the validation rules to the User model in app/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'
        )
      )
    );
  2. 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;
    }
  3. Locate the AppController.php file in app/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'
        )
      )
    );
  4. 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());
    }
  5. Locate the register.ctp file in app/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'));
  6. 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'));

How it works...

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.

See also

 

Including a plugin


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.

Getting ready

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:

https://github.com/cakephp/debug_kit/archive/master.zip

How to do it...

Perform the following steps:

  1. Add the following code to your bootstrap.php file, found in app/Config/:

    CakePlugin::load('DebugKit');
  2. Add the following code to your AppController in app/Controller/:

    public $components = array(
      'Session',
      'Auth',
      'DebugKit.Toolbar'
    );
  3. Open your core.php file in app/Config/, and check if the following configuration value is set to a value higher than 0:

    Configure::write('debug', 2);
  4. 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:

  5. Click on the icon to see various panels (shown in the following screenshots) with data related to the current request:

How it works...

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().

See also

About the Authors
  • James Watts

    James Watts is an experienced enterprise PHP architect, with a long track record in open source development. He is a core member of CakePHP and also handles community efforts, such as organizing CakeFest, the annual conference dedicated to the framework. He has also authored various technical specifications such as the Extended Hypertext Transfer Protocol (XHTTP) and the Documentation Markup Language (DocML). Professionally, he is the acting director of the Cake Development Corporation (CakeDC), the commercial entity behind the framework, where he works alongside Larry Masters, the founder of CakePHP.

    Browse publications by this author
  • Cake Software Foundation, Inc.
CakePHP 2 Application Cookbook
Unlock this book and the full library FREE for 7 days
Start now