CakePHP:带有可选参数的路由

问题描述 投票:0回答:2

假设我有这个动作:

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

有更好的方法吗?

php routes cakephp cakephp-3.0
2个回答
2
投票

不支持

最好放弃将其定义为具有单独路由元素的单个路由的想法,路由编译器/匹配器和反向路由都不支持这种方式。

虽然您可以创建一个支持此功能的自定义路由类,但您必须完全重新实现路由编译,并且实现反向路由支持可能同样有趣 - 就我个人而言,我认为这不值得这么麻烦。

使用多条路线,或自定义路线类别

因此,要么编写多个路由(这是我最有可能做的),要么采用参数解析解决方案,就像您在此处展示的那样。

当选择后者时,最好在自定义路由类中完成,以使事情更加 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'
]

然而,这一切只是为了能够用一条路线来定义它吗?再说一次,我不确定这是否值得这么麻烦。

另请参阅


1
投票

在 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']

希望这对您有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.