CakePHP already offers a very useful set of default routes that allow any set of URL elements to be sent to the controller action as arguments. For example, a URL such as http://localhost/tags/view/cakephp
is interpreted as a call to the TagsController::view()
method, sending cakephp
as its first argument.
However, there are times when we need more flexibility when creating URLs with arguments, such as the ability to omit certain arguments or add others that may not have been specified in the method signature. Named
and GET
parameters allow us to have such flexibility, without losing the advantage of letting CakePHP deal with its automatic URL parsing.
To go through this recipe we need a sample table to work with. Create a table named categories
, using the following SQL statement:
CREATE TABLE `categories`( `id` INT UNSIGNED AUTO_INCREMENT NOT NULL, `name` VARCHAR(255) NOT NULL, PRIMARY KEY(`id`) );
Create a table named articles
, using the following SQL statement:
CREATE TABLE `articles`( `id` INT UNSIGNED AUTO_INCREMENT NOT NULL, `category_id` INT UNSIGNED NOT NULL, `title` VARCHAR(255) NOT NULL, `body` TEXT NOT NULL, PRIMARY KEY(`id`), KEY `category_id`(`category_id`), FOREIGN KEY `articles__categories`(`category_id`) REFERENCES `categories`(`id`) );
Add some sample data, using the following SQL statements:
INSERT INTO `categories`(`id`, `name`) VALUES (1, 'Frameworks'), (2, 'Databases'), INSERT INTO `articles`(`id`, `category_id`, `title`, `body`) VALUES (1, 1, 'Understanding Containable', 'Body of article'), (2, 1, 'Creating your first test case', 'Body of article'), (3, 1, 'Using bake to start an application', 'Body of article'), (4, 1, 'Creating your first helper', 'Body of article'), (5, 2, 'Adding indexes', 'Body of article'),
We proceed now to create the required model. Create the model, Article
, in a file named article.php
and place it in your app/models
folder, with the following contents:
<?php class Article extends AppModel { public $belongsTo = array( 'Category' ); } ?>
Create its appropriate controller, ArticlesController
, in a file named articles_controller.php
and place it in your app/controllers
folder, with the following contents:
<?php class ArticlesController extends AppController { public function view($id) { $article = $this->Article->find('first', array( 'conditions' => array('Article.id' => $id) )); if (empty($article)) { $this->cakeError('error404'), } $articles = $this->Article->find('all', array( 'conditions' => array( 'Category.id' => $article['Category']['id'], 'Article.id !=' => $article['Article']['id'] ), 'order' => 'RAND()' )); $this->set(compact('article', 'articles')); } } ?>
Create a folder named articles
in your app/views
folder, then create the view in a file named view.ctp
and place it in your app/views/articles
folder, with the following contents:
<h1><?php echo $article['Article']['title']; ?></h1> <p><?php echo $article['Article']['body']; ?></p> <?php if (!empty($articles)) { ?> <br /><p>Related articles:</p> <ul> <?php foreach($articles as $related) { ?> <li><?php echo $this->Html->link( $related['Article']['title'], array( 'action'=>'view', $related['Article']['id'] ) ); ?></li> <?php } ?> </ul> <?php } ?>
GET
parameter. Edit your app/controllers/articles_controller.php
file and make the following changes to the view()
method:public function view($id) { $article = $this->Article->find('first', array( 'conditions' => array('Article.id' => $id) )); if (empty($article)) { $this->cakeError('error404'), } $limit = !empty($this->params['url']['related']) ? $this->params['url']['related'] : 0; $articles = $this->Article->find('all', array( 'conditions' => array( 'Category.id' => $article['Category']['id'], 'Article.id !=' => $article['Article']['id'] ), 'order' => 'RAND()', 'limit' => $limit > 0 ? $limit : null )); $this->set(compact('article', 'articles', 'limit')); }
http://localhost/articles/view/1?related=2
we should see the article content, along with up to two related articles, as shown in the following screenshot: ArticlesController
class and add the following at the end of the view()
method:$slug = !empty($this->params['named']['title']) ? $this->params['named']['title'] : null; $categorySlug = !empty($this->params['named']['category']) ? $this->params['named']['category'] : null; $this->set(compact('slug', 'categorySlug'));
app/views/articles/view.ctp
file and make the following changes:<?php if (!empty($slug)) { ?> Slug: <?php echo $this->Html->clean($slug); ?><br /> <?php } ?> <?php if (!empty($categorySlug)) { ?> Category slug: <?php echo $this->Html->clean($categorySlug); ?><br /> <?php } ?> <h1><?php echo $article['Article']['title']; ?></h1> <p><?php echo $article['Article']['body']; ?></p> <?php if (!empty($articles)) { ?> <br /><p>Related articles:</p> <ul> <?php foreach($articles as $related) { ?> <li><?php echo $this->Html->link( $related['Article']['title'], array( 'action'=>'view', $related['Article']['id'], '?' => array('related' => $limit), 'category' => strtolower(Inflector::slug($related['Category']['name'])), 'title' => strtolower(Inflector::slug($related['Article']['title'])) ) ); ?></li> <?php } ?> </ul> <?php } ?>
category
and title
. An example generated URL could be http://localhost/articles/view/4/category:frameworks/title:creating_your_first_helper
. Clicking on this link would take us to the article page, which also shows the specified parameters.Both GET
and named
parameters work in a similar fashion, by being automatically available in our application code as an array. GET
parameters are available in $this->params['url']
, while named parameters are available in $this->params['named']
. Checking the existence of a parameter is as simple as verifying that one of these given arrays contains a value whose key is the wanted parameter.
Creating links that specify either named
or GET
parameters (or both) is done by also specifying an indexed array of parameters (where the key is the parameter name, and the value its value.) For GET
parameters, this array is set in the special ?
route index key, while for named parameters each parameter is specified as part of the actual array based URL.
We learnt how to specify named parameters just by setting a key => value
pair in the array-based URL. However, we may want to also specify which of the named parameters should actually be parsed, and to make sure they are only parsed when the value matches a certain regular expression.
As an example, we can define the title
named parameter for all actions in the articles
controller, so it is parsed only when it follows a certain regular expression, where title can only contain lower case letters, numbers, or the underscore sign. To do so, we add the following sentence to our app/config/routes.php
file:
Router::connectNamed( array('title' => array('match' => '^[a-z0-9_]+$', 'controller' => 'articles')), array('default' => true) );
The first argument is an array, indexed by parameter name, and whose value contains another array that may include any of the following settings, all of which are optional:
Setting |
Purpose |
---|---|
|
If specified, the named parameter will be parsed only for the given action. |
|
If specified, the named parameter will be parsed only for the given controller. |
|
A regular expression that will be used to see if the provided value matches the named parameter. If specified, the named parameter will be parsed only when the value matches the expression. |
The second argument to Router::connectNamed()
is an optional array of settings, which may include any of the following:
Setting |
Purpose |
---|---|
|
If set to |
|
If set to |
|
If set to |
To further understand the greedy
option, we could still allow the URL to include the category
and title
parameters, but may want to only parse the title
value. To do this, we would set greedy
to false
when defining the named parameter. That way, $this->params['named']
would only contain the value for title
, even when category
is specified in the requested URL. We also want to do this only for the view
action of the articles
controller:
Router::connectNamed( array('title' => array('match' => '^[a-z0-9_]+$', 'controller'=>'articles', 'action'=>'view')), array('greedy' => false) );
Notice how we had to specify the regular expression for the title
named parameter again, even though we specified it before. This is because we are configuring a named parameter whose name already exists, so our definition would override the previous one.
52.15.129.90