- CakePHP 2 Application Cookbook
- James Watts Jorge González
- 921字
- 2021-04-02 10:06:35
Custom route class
Although the base routing system provided with CakePHP will cover almost all cases, there may be times when you need to handle things which are slightly more complex. For this reason, the framework provides the option to define a custom route class, which processes more complicated scenarios.
In this recipe, we'll create a custom route class to handle news headlines for given year and month.
Getting ready
For this recipe, we'll need to create a custom route class to use in our routing. Therefore, create a file named HeadlineRoute.php
in app/Routing/Route/
with the following content:
<?php App::uses('CakeRoute', 'Routing/Route'); App::uses('ClassRegistry', 'Utility'); class HeadlineRoute extends CakeRoute { }
We'll then need some data to work with. So, create a table named headlines
using the following SQL statement:
CREATE TABLE headlines ( id INT NOT NULL AUTO_INCREMENT, title VARCHAR(50), year SMALLINT(2) UNSIGNED NOT NULL, month SMALLINT(2) UNSIGNED NOT NULL, PRIMARY KEY(id) );
After creating the table, run the following SQL statement to insert some headlines:
INSERT INTO headlines (title, year, month) VALUES ('CakePHP on top', '2013', '11'), ('CakeFest 2014', '2014', '08'), ('CakePHP going strong', '2014', '08');
We'll also need to create a file named HeadlinesController.php
in app/Controller/
and add the following content to it:
<?php App::uses('AppController', 'Controller'); class HeadlinesController extends AppController { }
Finally, create a listing.ctp
file in app/View/Headlines/
for our view.
How to do it...
Perform the following steps:
- Add the following code to the
routes.php
file inapp/Config/
:App::uses('HeadlineRoute', 'Routing/Route'); Router::connect('/:year/:month', array( 'controller' => 'headlines', 'action' => 'listing' ), array( 'year' => '[0-9]{4}', 'month' => '[0-1]{0,1}[0-9]{1}', 'routeClass' => 'HeadlineRoute' ));
- Edit the
HeadlineRoute.php
file inapp/Routing/Route/
and add the followingparse()
method to it:public function parse($url) { $params = parent::parse($url); if (empty($params)) { return false; } $cacheKey = $params['year'] . '-' . $params['month']; $headlines = Cache::remember($cacheKey, function () use ($params) { return ClassRegistry::init('Headline')->find('all', array( 'conditions' => array( 'Headline.year' => (int)$params['year'], 'Headline.month' => (int)$params['month'] ) )); }); if (!empty($headlines)) { return $params; } return false; }
- Define the following
listing()
method in theHeadlinesController.php
file:public function listing() { $year = $this->request->params['year']; $month = $this->request->params['month']; $headlines = Cache::read($year . '-' . $month); $this->set(compact('year', 'month', 'headlines')); }
- Add the following content to the
listing.ctp
file:<h2><?php echo __('Headlines for %s/%s', $year, $month); ?></h2> <?php $list = Hash::extract($headlines, '{n}.Headline.title'); echo $this->Html->nestedList($list);
- Finally, navigate to
/2014/08
in your browser to see the headlines for that month. This is shown in the following screenshot:
How it works...
For this recipe, we first created a custom route class to handle our routing logic. This class was created in a file named HeadlineRoute.php
, which is located in app/Routing/Route/
. There is no strict convention for the naming of route classes and they don't need to align with any controller or model, but it's best to name them wisely so that it's evident what their function entails. In these calls, we defined a parse()
method, which processes the route. Note that the HeadlineRoute
is only actioned when the /:year/:month
template we defined matches. In this case, we also added some validation to our route, making sure that the year and month are valid for our request. The routeClass
then defines that we want to use our custom route class for these routes. This is where our parse()
method comes into play.
For our routing logic, we first resolve the parent::parse()
call to obtain the underlying routing, and then check that we're in a valid route. We then build a cache key from the year
and month
values passed from our template in the request. Then, we use this key to call Cache::remember()
, passing it an anonymous function that initializes the Headline
model by calling the ClassRegistry::init()
method and returns the headlines based on the values provided from our route. This greatly improves our application performance, as we will have the results cached for subsequent requests for the same year and month. If there are headlines, we return the $params
array to signal that our route class has matched successfully. To the contrary, we return false
, which means the route class did not resolve a possible resource.
When the route is successfully matched, we had defined in our configuration array for the request to be routed to the listing()
action of the HeadlinesController
. In this method, we only used the year
and month
values provided in the CakeRequest::$params
array to construct our cache key and read the headlines our custom route class previously stored for us. Then, we just set those values as view variables using the set()
method of our controller and used the compact()
function to generate the key => value
array from our variables.
Our view is just as simple. We created a header by using the $year
and $month
values passed via the URL, and we also processed the headlines to generate a list. For this, we first used the Hash::extract()
method to parse our array of records to return an array of headline titles, and then we used the nestedList()
method of the Html
helper to build our HTML list.
There's more...
So far, we've successfully set up the logic to resolve the URL to a collection of headlines. However, it would now be ideal to also define the logic for reverse routing. This is where we generate a link based on the values of a headline.
For this, we want to be able to simply create a URL from the unique ID of a headline to generate the required path for the month's headlines. For this, we'll add a match()
method to our HeadlineRoute
class with the following logic:
public function match($url) { if ($url['controller'] === 'headlines' && $url['action'] === 'listing' && !empty($url[0])) { $headline = ClassRegistry::init('Headline')->find('first', array( 'conditions' => array( 'id' => $url[0] ) )); if (!empty($headline)) { $url['year'] = $headline['Headline']['year']; $url['month'] = $headline['Headline']['month']; unset($url[0]); return parent::match($url); } } return false; }
In the preceding code, we've first checked that the URL configuration specified the controller
as Headline
and the action
as listing
, with the ID of the headline. We then instantiate the Headline
model via ClassRegistry::init()
method, using the ID provided. If a record is found, we set the year
and month
keys in the URL configuration and remove the ID. Finally, we call the parent::match()
method to complete the translation of the route. To see how it works, add the following line to the app/View/Headlines/listing.ctp
file:
debug(Router::url(array('controller' => 'headlines', 'action' => 'listing', 1)));
It will output matching the /2013/11
URL.
See also
- The View caching recipe from Chapter 10, View Templates.
- Python Text Processing with NLTK 2.0 Cookbook: LITE
- ASP.NET jQuery Cookbook
- Object/Oriented JavaScript
- AI繪畫與攝影實戰108招:ChatGPT+Midjourney+文心一格
- Illustrator CC 2018 基礎與實戰教程(全彩版)
- Photoshop CC 平面設計
- UG NX 11中文版基礎教程
- Animate核心應用案例教程:Animate 2020(全彩慕課版)
- Maya 2022從新手到高手
- Python Multimedia
- Cinema 4D電商美工與視覺設計案例教程
- 零基礎學After Effects CC 2018(全視頻教學版)
- 物品編碼標識
- Adobe Flash動畫設計與制作標準實訓教程(CS5修訂版)
- 電商美工設計手冊