【问题标题】:CakePHP: route with optional parametersCakePHP:带有可选参数的路由
【发布时间】:2016-10-12 04:54:43
【问题描述】:

假设我有这个动作:

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

有没有更好的方法来做到这一点?

【问题讨论】:

    标签: cakephp routes cakephp-3.0


    【解决方案1】:

    不支持

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

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

    使用多个路由,或自定义路由类

    所以要么编写多个路由,这是我最可能做的,要么像你在这里展示的那样使用参数解析解决方案。

    选择后者时,最好在自定义路由类中完成,以使事情更加干燥。但是仍然存在缺点,即在构建 URL 时需要将日期部分作为单个字符串传递,除非您还实现一些自定义匹配,例如检查 yearmonthday 是否键存在,并构建一个新的 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(),后者将追加 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'
    ]
    

    但是,这一切仅仅是为了能够用一条路线定义它吗?同样,我不确定这是否值得。

    另见

    【讨论】:

      【解决方案2】:

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

      希望对你有帮助。

      【讨论】:

        猜你喜欢
        • 2011-01-09
        • 2023-03-03
        • 1970-01-01
        • 2013-09-10
        • 1970-01-01
        • 2018-04-22
        • 2013-10-13
        • 2016-08-01
        相关资源
        最近更新 更多