CakePHP 2 Application Cookbook
上QQ阅读APP看书,第一时间看更新

Adding a prefix

Prefixes are additional values prepended to the URL, which allow you to clearly separate collections of actions in your controllers. This is typically used to rapidly create an admin area for an application.

In this recipe, we'll create an inventory management area to a books controller, where the standard actions will simply display details of books and the inventory area will be used to manage our books.

Getting ready

For this recipe, we'll need a table for our books, so create a table named books by using the following SQL statement:

CREATE TABLE books (
  id VARCHAR(36) NOT NULL,
  name VARCHAR(100),
  stock INT(4),
  created DATETIME,
  modified DATETIME,
  PRIMARY KEY(id)
);

We'll then need some sample data, so run the following SQL statement to insert some books:

INSERT INTO books (id, name, stock, created, modified)
VALUES
('635c460a-f230-4565-8378-7cae01314e03', 'CakePHP Application', 2, NOW(), NOW()),
('635c4638-c708-4171-985a-743901314e03', 'PHP for Beginners', 3, NOW(), NOW()),
('635c49d9-917c-4eab-854f-743801314e03', 'Server Administration', 0, NOW(), NOW());

We'll also need to create a BooksController. So, create a file named BooksController.php in app/Controller/ with the following content:

<?php
App::uses('AppController', 'Controller');

class BooksController extends AppController {

  public $helpers = array('Html', 'Form');

}

Then create a directory named Books/ in app/View/, and create the following files in that directory: index.ctp, view.ctp, inventory_stock.ctp, and inventory_edit.ctp.

How to do it...

Perform the following steps:

  1. First, open your core.php file in app/Config/ and make sure the following line is uncommented and has the following value:
    Configure::write('Routing.prefixes', array('inventory'));
  2. Create the following index() and view() methods in your BooksController:
    public function index() {
      $this->set('books', $this->Book->find('all', array(
        'conditions' => array(
          'Book.stock >' => 0
        )
      )));
    }
    
    public function view($id) {
      if (!($book = $this->Book->findById($id))) {
        throw new NotFoundException(__('Book not found'));
      }
      if ($book['Book']['stock'] < 1) {
        throw new CakeException(__('Book not in stock'));
      }
      $this->set(compact('book'));
    }
  3. In the BooksController class, add the following inventory_stock() and inventory_edit() methods:
    public function inventory_stock() {
      $this->set('books', $this->Book->find('all'));
    }
    
    public function inventory_edit($id) {
      $book = $this->Book->findById($id);
      if (!$book) {
        throw new NotFoundException(__('Book not found'));
      }
      if ($this->request->is('post')) {
        $this->Book->id = $id;
        if ($this->Book->save($this->request->data)) {
          $this->Session->setFlash(__('Book stock updated'));
          return $this->redirect(array(
            'prefix' => 'inventory',
            'action' => 'stock'
          ));
        }
        $this->Session->setFlash(__('Could not update book stock'));
      } else {
        $this->request->data = $book;
      }
    }
  4. Introduce the following content into your app/View/Books/index.ctp file:
    <h2><?php echo __('Books'); ?></h2>
    <table>
      <tr>
        <th><?php echo __('Name'); ?></th>
        <th><?php echo __('Created'); ?></th>
        <th><?php echo __('Modified'); ?></th>
      </tr>
      <?php foreach ($books as $book): ?>
        <tr>
          <td>
            <?php
            echo $this->Html->link($book['Book']['name'], array('controller' => 'books', 'action' => 'view', $book['Book']['id']));
            ?>
          </td>
          <td>
            <?php
            echo $this->Time->nice($book['Book']['created']);
            ?>
          </td>
          <td>
            <?php
            echo $this->Time->nice($book['Book']['modified']);
            ?>
          </td>
        </tr>
      <?php endforeach; ?>
    </table>
  5. Add the following content to your app/View/Books/view.ctp file:
    <h2><?php echo h($book['Book']['name']); ?></h2>
    <dl>
      <dt><?php echo __('Stock'); ?></dt>
      <dd><?php echo $book['Book']['stock']; ?></dd>
      <dt><?php echo __('Created'); ?></dt>
      <dd><?php echo $this->Time->nice($book['Book']['created']); ?></dd>
      <dt><?php echo __('Modified'); ?></dt>
      <dd><?php echo $this->Time->nice($book['Book']['modified']); ?></dd>
    </dl>
  6. Introduce the following content into your app/View/Books/inventory_stock.ctp file:
    <h2><?php echo __('Books Stock'); ?></h2>
    <table>
      <tr>
        <th><?php echo __('Name'); ?></th>
        <th><?php echo __('Stock'); ?></th>
        <th><?php echo __('Created'); ?></th>
      </tr>
      <?php foreach ($books as $book): ?>
        <tr>
          <td>
            <?php
            echo $this->Html->link($book['Book']['name'], array('prefix' => 'inventory', 'controller' => 'books', 'action' => 'edit', $book['Book']['id']));
            ?>
          </td>
          <td>
            <?php echo $book['Book']['stock']); ?>
          </td>
          <td>
            <?php
            echo $this->Time->nice($book['Book']['created']);
            ?>
          </td>
        </tr>
      <?php endforeach; ?>
    </table>
  7. Once more, with the following content into your app/View/Books/inventory_edit.ctp file.
    <h2><?php echo __('Edit Stock'); ?></h2>
    <p>
      <?php echo __('Book') . ':' . h($this->request->data('Book.name')); ?>
    </p>
    <?php
    echo $this->Form->create('Book');
    echo $this->Form->input('id');
    echo $this->Form->input('stock');
    echo $this->Form->end(__('Submit'));
    ?>
  8. Now when we enabled the inventory routing prefix, we have to adjust the settings for AuthComponent so that it correctly redirects to the non-prefixed methods of UsersController even from any prefixed method. Open the file named app/Controller/AppController.php and adjust settings for AuthComponent like this:
    public $components = array(
      ...
      'Auth' => array(
        'loginRedirect' => array(
          'inventory' => false,
          'controller' => 'products'
        ),
        'logoutRedirect' => array(
          'inventory' => false,
          'controller' => 'users',
          'action' => 'login'
        ),
        'loginAction' => array(
          'inventory' => false,
          'controller' => 'users',
          'action' => 'login'
        )
      ),
      ...
    );

    Note

    Note that we added inventory to all settings related to URLs and added the loginAction setting, as we're no longer going to use a default nonprefixed URL.

  9. Finally, navigate to /books/index, /books/view/635c460a-f230-4565-8378-7cae01314e03, /inventory/books/stock, or /inventory/books/edit/635c460a-f230-4565-8378-7cae01314e03 in your browser. The respective screenshots are shown as follows:
    How to do it...
    How to do it...
    How to do it...
    How to do it...

How it works...

In this recipe, we first set up the prefix settings for our routing. Specifically, we created a prefix named inventory. In this case, you could also define more prefixes by simply adding more values to the array of the Routing.prefixes configuration value.

We then created some index() and view() actions in our BooksController. These simply read the books available and show the details of a book, although the find('all') call of our index() method filters the results via the conditions option to not include any books with stock at 0 (out of stock). We also defined some inventory_stock() and inventory_edit() methods. You'll notice that these are prefixed with inventory_. This was intentional, as this is how the framework maps the router prefix with the action by convention. So, when calling any action of a controller prefixed with /inventory/ in the URL, the router will search for the requested action where the prefix is prepended with inventory_.

You probably also saw that while generating the URL for our links in our inventory management area, we included the prefix option in our call to the link() method on the Html helper. This tells the router to create a link using that prefix. Without it, the router would simply create a URL to a normal action on the controller.

Here, we simply prefixed some actions. However, you could easily extend this controller now so that it requires some special attribute of the currently logged-in user account in order to use the inventory actions.