假设我有这个动作:
public function index_by_date($year = NULL, $month = NULL, $day = NULL) {
if($year && $month && $day) {
//Posts of day
}
elseif($year && $month) {
//Posts of month
}
elseif($year) {
//Posts of year
}
else {
//Exception...
}
}
这显示了一天的帖子(2016年6月11日的所有帖子):
mysite.com/posts/2016/06/11
这显示了整整一个月的帖子(2016 年 6 月的所有帖子):
mysite.com/posts/2016/06
这显示了全年的帖子(2016 年的所有帖子):
mysite.com/posts/2016
我想这已经很清楚了。
现在我想为所有这些情况编写一条路线。那么,第二个和第三个参数应该是可选的。
我尝试过类似的方法,但这不起作用:
$routes->connect('/posts/:year/:month/:day',
['controller' => 'Posts', 'action' => 'index_by_date'],
[
'year' => '[12][0-9]{3}',
'month' => '(0[1-9]|1[012])?',
'day' => '(0[1-9]|[12][0-9]|3[01])?',
'pass' => ['year', 'month', 'day']
]
);
怎么办?
编辑 目前,这是我能做的最好的事情:
路线:
$routes->connect('/posts/:date',
['controller' => 'Posts', 'action' => 'index_by_date'],
[
'date' => '\d{4}(\/\d{2}(\/\d{2})?)?',
'pass' => ['date']
]
);
行动:
public function index_by_date($date = NULL) {
list($year, $month, $day) = array_merge(explode('/', $date), [NULL, NULL, NULL]);
if($year && $month && $day) {
//Posts of day
}
elseif($year && $month) {
//Posts of month
}
elseif($year) {
//Posts of year
}
else {
//Exception...
}
}
有更好的方法吗?
最好放弃将其定义为具有单独路由元素的单个路由的想法,路由编译器/匹配器和反向路由都不支持这种方式。
虽然您可以创建一个支持此功能的自定义路由类,但您必须完全重新实现路由编译,并且实现反向路由支持可能同样有趣 - 就我个人而言,我认为这不值得这么麻烦。
因此,要么编写多个路由(这是我最有可能做的),要么采用参数解析解决方案,就像您在此处展示的那样。
当选择后者时,最好在自定义路由类中完成,以使事情更加 DRY。但仍然存在一个缺点,即在构建 URL 时,您需要将日期部分作为单个字符串传递,除非您还实现了一些自定义匹配,例如检查是否
year
、month
、day
键存在,并构建一个新的 URL 数组来匹配。
我现在有太多的空闲时间,这样的解析/匹配通常可能很有趣,所以这里有一个快速而肮脏的例子:
<?php
namespace App\Routing\Route;
use Cake\Routing\Route\Route;
class CompactDateRoute extends Route
{
public function parse($url)
{
$params = parent::parse($url);
if (!$params) {
return false;
}
$params['pass'] = explode('/', $params['pass'][0]) + [null, null, null];
return $params;
}
public function match(array $url, array $context = [])
{
$dateParts = [];
foreach (['year', 'month', 'day'] as $key) {
if (!isset($url[$key])) {
break;
}
$dateParts[] = $url[$key];
unset($url[$key]);
}
if ($dateParts) {
$date = implode('/', $dateParts);
return parent::match($url + compact('date'), $context);
}
return false;
}
}
解析部分应该是非常不言自明的,它只是将单个日期字符串解析为单独的参数,然后将其作为单独的参数传递给控制器操作。请注意使用
+
而不是 array_merge()
,后者会 append null
值!
匹配部分也不是太复杂,它只是检查 URL 数组中的日期部分键,从中构建一个可以与您的路线匹配的紧凑日期值(
year/month?/day?
),并与之匹配,即构建一个来自数组的 URL,例如
[
'controller' => 'Posts',
'action' => 'index_by_date',
'year' => '2014',
'month' => '12',
'day' => '04'
]
基本上等于匹配
[
'controller' => 'Posts',
'action' => 'index_by_date',
'date' => '2014/12/04'
]
然而,这一切只是为了能够用一条路线来定义它吗?再说一次,我不确定这是否值得这么麻烦。
在 CakePHP 3 中,您可以将 '_name' 选项添加到 $routes->connect() 并分隔三个不同的路由。
完整日期,routes.php:
$routes->connect('/posts/:year/:month/:day',
['controller' => 'Posts', 'action' => 'index_by_date'],
[
'year' => '[12][0-9]{3}',
'month' => '(0[1-9]|1[012])?',
'day' => '(0[1-9]|[12][0-9]|3[01])?',
'pass' => ['year', 'month', 'day'],
'_name' => 'postsFull'
]
);
使用(通话中):
['_name' => 'postsFull', 'year' => '2016', 'month' => '06', 'day' => '11']
年月,routes.php:
$routes->connect('/posts/:year/:month',
['controller' => 'Posts', 'action' => 'index_by_date'],
[
'year' => '[12][0-9]{3}',
'month' => '(0[1-9]|1[012])?',
'pass' => ['year', 'month'],
'_name' => 'postsYearMonth'
]
);
使用(通话中):
['_name' => 'postsYearMonth', 'year' => '2016', 'month' => '06']
仅一年,routes.php:
$routes->connect('/posts/:year',
['controller' => 'Posts', 'action' => 'index_by_date'],
[
'year' => '[12][0-9]{3}',
'pass' => ['year'],
'_name' => 'postsYear'
]
);
使用(通话中):
['_name' => 'postsYear', 'year' => '2016']
希望这对您有帮助。