ZF3:如何根据方法和路由路由到特定的controller/action?

ZF3: How to route to specific controller/action based on method and route?

在我模块的 module.config.php 中,我有这样的东西:

namespace Application;

return [
    //...
    // myroute1 will route to IndexController fooAction if the route is matching '/index/foo' but regardless of request method
    'myroute1' => [
        'type' => Zend\Router\Http\Literal::class,
        'options' => [
            'route'    => '/index/foo',
            'defaults' => [
                'controller' => Controller\IndexController::class,
                'action'     => 'foo',
            ],
        ],
    ],

    // myroute2 will route to IndexController fooAction if the route is request method is GET but regardless of requested route
    'myroute2' => [
        'type'    => Zend\Router\Http\Method::class,
        'options' => [
            'verb'     => 'get',
            'defaults' => [
                'controller'    => Controller\IndexController::class,
                'action'        => 'foo',
            ],
        ],
    ],
    //...
];

我想要达到的目标:

如何实现?

尝试将文字更改为 Zend\Mvc\Router\Http\Part 路由,然后将 HTTP 路由作为子路由放入!

看这里https://docs.zendframework.com/zend-router/routing/#http-route-types

给自己和其他人的注意事项,作为对 @delboy1978uk 的回答的补充说明。

我正在寻找的答案是这样的:

  • GET /index/foo => IndexController fooAction
  • POST /index/foo => IndexController barAction

所以 module.config.php 文件中的代码可以是这样的:

return [
    //...
    'myroute1' => [// The parent route will match the route "/index/foo"
        'type' => Zend\Router\Http\Literal::class,
        'options' => [
            'route'    => '/index/foo',
            'defaults' => [
                'controller' => Controller\IndexController::class,
                'action'     => 'foo',
            ],
        ],
        'may_terminate' => false,
        'child_routes' => [
            'myroute1get' => [// This child route will match GET request
                'type' => Method::class,
                'options' => [
                    'verb' => 'get',
                    'defaults' => [
                        'controller' => Controller\IndexController::class,
                        'action'     => 'foo'
                    ],
                ],
            ],
            'myroute1post' => [// This child route will match POST request
                'type' => Method::class,
                'options' => [
                    'verb' => 'post',
                    'defaults' => [
                        'controller' => Controller\IndexController::class,
                        'action'     => 'bar'
                    ],
                ],
            ]
        ],
    ],
    //...
];

我知道这是一个老话题,但我想与遇到此问题并且正在努力使用 zend 或 Laminas(我使用 Laminas)以及基于方法和本地化路由的路由的人分享我的答案。基本上,您应该能够将命名空间的“Laminas”替换为“Zend”。代码库非常相似。

首先:我无法使用@evilReiko 的解决方案 因为'may_terminate' => false, 总是为我抛出异常。当我将它设置为 true 时,子路由被忽略了......显然 :D

但是这张便条帮助我理解了一些正在发生的事情。我决定只实现一个自定义 class 来处理:URL 本地化和 method/action 路由。

我创建了一个新文件夹 Classes 并在 modules/Application 中添加了一个新文件 MethodSegment。 所以文件路径将是 modules/Application/Classes/MethodSegment.php.

<?php

namespace Application\Classes;

use Laminas\Router\Exception;
use Laminas\Stdlib\ArrayUtils;
use Laminas\Stdlib\RequestInterface as Request;
use Laminas\Router\Http\RouteMatch;
use Traversable;
    
/**
 * Method route.
 */
class MethodSegment extends \Laminas\Router\Http\Segment
{
    /**
     * associative array [method => action]
     *
     * @var array
     */
    protected $methodActions;

    /**
     * Default values - accessing $defaults from parent class Segment
     *
     * @var array
     */
    protected $defaults;

    /**
     * Create a new method route
     *
     * @param  string $route
     * @param  array  $constraints
     * @param  array  $defaults
     */
    public function __construct($route, array $constraints = [], array $defaults = [])
    {
        if(is_array($defaults['action']))
        {
            $this->methodActions = $defaults['action'];
            $defaults['action'] = array_values($defaults['action'])[0];
        }

        parent::__construct($route, $constraints, $defaults);
    }

    /**
     * factory(): defined by RouteInterface interface.
     *
     * @see    \Laminas\Router\RouteInterface::factory()
     *
     * @param  array|Traversable $options
     * @return Method
     * @throws Exception\InvalidArgumentException
     */
    public static function factory($options = [])
    {
        if ($options instanceof Traversable) {
            $options = ArrayUtils::iteratorToArray($options);
        } elseif (! is_array($options)) {
            throw new Exception\InvalidArgumentException(sprintf(
                '%s expects an array or Traversable set of options',
                __METHOD__
            ));
        }

        if (! isset($options['defaults'])) {
            $options['defaults'] = [];
        }

        return new static($options['route'] ?? null, $options['constraints'] ?? [], $options['defaults']);
    }

    /**
     * match(): defined by RouteInterface interface.
     *
     * @see    \Laminas\Router\RouteInterface::match()
     *
     * @return RouteMatch|null
     */
    public function match(Request $request, $pathOffset = null, array $options = [])
    {
        if (! method_exists($request, 'getMethod')) {
            return null;
        }

        $requestVerb = strtolower($request->getMethod());
        $verb = array_keys($this->methodActions);

        if (in_array($requestVerb, $verb)) {
            $this->defaults['action'] = $this->methodActions[$requestVerb];
            return parent::match($request, $pathOffset, $options);
        }

        return null;
    }
}

基本上我从 Laminas 方法 class 复制了代码并对其进行了增强,以便我可以传递一系列操作。

您可以像这样使用 MethodSegment:

use App\Routing\MethodSegment;
return [
    'router' => [
        'routes' => [
            'home' => [
                'type'    => MethodSegment::class,
                'options' => [
                    'route'    => /[:language/],
                    'constraints' => [...],
                    'defaults' => [
                        'controller' => Controller\IndexController::class,
                        'action'     => [
                            'get' => 'index',
                            'post' => 'postIndex', // e.g. form
                        ],
                    ],
                ],
            ],
      [...]

希望这对任何人都有帮助,IMO 子路由方法非常笨重。