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