Using named and GET parameters

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.

Getting ready

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 } ?>

How to do it...

  1. We start by adding the possibility to change the number of related articles through a 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'));
    }
    
  2. If we now browse to 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:
    How to do it...
  3. We will now use named parameters to pass a search engine-friendly version of the article title, even though it is not needed to show the article or its related content. Edit your 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'));
    
  4. Now edit the 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 } ?>
    
  5. If we hover over the links to the related articles, we will notice they include two new parameters: 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.

How it works...

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.

There's more...

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

action

If specified, the named parameter will be parsed only for the given action.

controller

If specified, the named parameter will be parsed only for the given controller.

match

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

default

If set to true, it will also load the named parameters needed for pagination to work. If you call Router::connectNamed() several times, this is only needed once, unless you set the reset option to true. Defaults to false.

greedy

If set to false, it will only parse the named parameters that are explicitly defined through a Router::connectNamed() call. Defaults to true.

reset

If set to true, it will wipe out any named parameters defined prior to this call. Defaults to false.

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.

See also

  • Working with route elements
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.129.90