
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:
- First, open your
core.php
file inapp/Config/
and make sure the following line is uncommented and has the following value:Configure::write('Routing.prefixes', array('inventory'));
- Create the following
index()
andview()
methods in yourBooksController
: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')); }
- In the
BooksController
class, add the followinginventory_stock()
andinventory_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; } }
- 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>
- 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>
- 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>
- 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')); ?>
- Now when we enabled the
inventory
routing prefix, we have to adjust the settings forAuthComponent
so that it correctly redirects to the non-prefixed methods ofUsersController
even from any prefixed method. Open the file namedapp/Controller/AppController.php
and adjust settings forAuthComponent
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' ) ), ... );
- 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 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.