繁体   English   中英

在PHP中用Closure创建组路由(类似laravel)

[英]Create group route with Closure in PHP (similar laravel)

如何使用 Closure 按中间件或路由前缀对路由进行分组? 类似于 laravel(不像社区中的其他答案那样使用“使用”,而是使用 static 调用)。

我不知道如何在闭包内对所有这些调用进行分组。 我怎样才能做到这一点? 一些例子?

web.php(文件路径):

//web.php file
Router::middleware(['auth', 'okok'])->group(function(){
    Router::get('/teste', [TestController::class, 'test']);
    Router::get('/teste2', [TestController::class, 'test2']);
});

Router.php(路由器类):

<?php
namespace Core;

use Core\RouterHandler;
use Closure;

class Router
{
    protected static $routes = [];
    protected static $middlewares = [];
    protected static string $uriMethod;
    protected static string $trigger;

    /**
     * start
     * START THE ROUTER IN INDEX.PHP
     * @param  Router $router
     * @return void
     */
    public static function start()
    {
        $routerHandler = new RouterHandler(self::$routes);
        $routerHandler->run();
    }
    
    /**
     * constructByMethod
     * CONSTRUCT THE ROUTE OBJECT WHEN CALLED BY THE MAINS METHODS
     * @param  string $uri
     * @param  array $trigger
     * @param  string $uriMethod
     * @return object
     */
    private static function constructByMethod(string $uri, array $trigger, string $uriMethod): object
    {
        self::$uriMethod = $uriMethod;
        self::$trigger = $trigger[0].'::'.$trigger[1];

        self::$routes[$uriMethod][self::$trigger]['uri'] = $uri;
        self::$routes[$uriMethod][self::$trigger]['uriMethod'] = $uriMethod;
        self::$routes[$uriMethod][self::$trigger]['controller'] = $trigger[0];
        self::$routes[$uriMethod][self::$trigger]['action'] = $trigger[1];

        if (!empty(self::$middlewares)) {
            self::$routes[$uriMethod][self::$trigger]['middlewares'] = self::$middlewares;
        }

        self::$middlewares = [];
        
        return new static();
    }
    
    /**
     * get
     * MAIN METHOD TO CONSTRUCT GET ROUTE
     * @param  string $uri
     * @param  array $trigger
     * @return object
     */
    public static function get(string $uri, array $trigger): object 
    {
        self::constructByMethod($uri, $trigger, 'GET');
        return new static();
    }

    /**
     * post
     * MAIN METHOD TO CONSTRUCT POST ROUTE
     * @param  string $uri
     * @param  array $trigger
     * @return object
     */
    public static function post(string $uri, array $trigger): object
    {
        self::constructByMethod($uri, $trigger, 'POST');
        return new static();
    }
    
    /**
     * put
     * MAIN METHOD TO CONSTRUCT POST ROUTE
     * @param  string $uri
     * @param  array $trigger
     * @return object
     */
    public static function put(string $uri, array $trigger): object
    {
        self::constructByMethod($uri, $trigger, 'PUT');
        return new static();
    }

    /**
     * delete
     * MAIN METHOD TO CONSTRUCT DELETE ROUTE
     * @param  string $uri
     * @param  array $trigger
     * @return object
     */
    public static function delete(string $uri, array $trigger): object
    {
        self::constructByMethod($uri, $trigger, 'DELETE');
        return new static();
    }

    public static function group($callback)
    {
        $callback();
    }

    /**
     * name
     * ADD A NAME TO A ROUTE
     * @param  string $name
     * @return object
     */
    public static function name(string $name): object
    {
        self::$routes[self::$uriMethod][self::$trigger]['name'] = $name;
        return new static();
    }
    
    /**
     * middleware
     * ADD A GROUP OF MIDDLEWARE FOR A ROUTES
     * @param  array|null $middlewares
     * @return object
     */
    public static function middleware(array|null $middlewares): object
    {
        self::$middlewares = $middlewares;
        return new static();
    }
    
    /**
     * where
     * ADD A REGEX CONDITION FOR A VARIABLE PARAMETER IN ROUTE
     * @param  array $where
     * @return object
     */
    public static function where(array $where): object
    {
        self::$routes[self::$uriMethod][self::$trigger]['where'] = $where;
        return new static();
    }
}

之后怎么办?

    public static function group($callback)
    {
        $callback();
    }

尝试了所有方法,但我不明白如何使用闭包对路线进行分组。

我需要了解如何在这种特定情况下使用闭包。

首先,您需要使用方法Closure::call ,以便临时将组处理程序( function(){...} ) 绑定到路由器的实例

之后,您需要执行组处理程序 然后:

  • 其中的每个组也将被绑定和执行(如上所述),并且
  • 其中定义的每个路由都将添加到路由器。

下面是我的代码的一个简短示例,关于将组添加到路由集合,并带有详细注释(请参阅RouteCollection::group方法)。

请注意,我将组/路由添加到RouteCollection ,它进一步添加到我的Router 相反,您将它们直接添加到您的Router

在下面,我还发布了我的整个RouteCollection代码,以便更好地理解。

注意:您正在使用static方法/属性。 我建议根本不要使用任何东西。 相反,您应该使用依赖注入


短代码版本:

RouteCollectionInterface(简短版本,关于绑定和执行组):

<?php

namespace PajuranCodes\Router;

use Closure;

/**
 * An interface to a collection of routes.
 *
 * @author pajurancodes https://github.com/pajurancodes
 */
interface RouteCollectionInterface extends \Countable, \IteratorAggregate {

    /**
     * Add a group.
     *
     * @param string $pattern A group pattern.
     * @param Closure $handler A group handler.
     * @return static
     */
    public function group(string $pattern, Closure $handler): static;

}

RouteCollection(简短版本,关于绑定和执行组):

<?php

namespace PajuranCodes\Router;

use function array_pop;
use PajuranCodes\Router\{
    RouteCollectionInterface,
};
use Closure;

/**
 * A collection of routes.
 *
 * @author pajurancodes https://github.com/pajurancodes
 */
class RouteCollection implements RouteCollectionInterface {

    [...]

    /**
     * A list of group patterns as an indexed array.
     *
     * {@internal Each time a group handler is executed, the group 
     * pattern is pushed to this list. When a route is added to the 
     * routes list (inside the scope of a group handler), the route 
     * pattern is prefixed with the string formed by concatenating 
     * all group patterns saved in this list.}
     *
     * @var string[]
     */
    private array $groupPatterns = [];

    /**
     * @inheritDoc
     */
    public function group(string $pattern, Closure $handler): static {
        $this->addGroup($pattern, $handler);
        return $this;
    }

    /**
     * Add a group.
     *
     * @param string $pattern A group pattern.
     * @param Closure $handler A group handler.
     * @return static
     */
    private function addGroup(string $pattern, Closure $handler): static {
        $this->saveGroupPattern($pattern);
        $this->executeGroupHandler($handler);

        /*
         * Remove the last group pattern from the group patterns list. 
         * This step is performed only after all calls for adding 
         * groups/routes inside the scope of the current group 
         * handler have finished their processing.
         */
        $this->popLastGroupPattern();

        return $this;
    }

    /**
     * Save a group pattern.
     *
     * @param string $pattern A group pattern.
     * @return static
     */
    private function saveGroupPattern(string $pattern): static {
        $this->groupPatterns[] = $pattern;
        return $this;
    }

    /**
     * Execute a group handler.
     *
     * {@internal This method temporarily binds the given group handler to
     * the instance of the route collection - defined by the argument of 
     * Closure::call - and executes it. Inside the scope of the group handler, 
     * the route collection will be accessed using the keyword "$this".}
     * 
     * @link https://www.php.net/manual/en/closure.call.php Closure::call
     *
     * @param Closure $handler A group handler.
     * @return static The value returned by executing the group handler.
     */
    private function executeGroupHandler(Closure $handler): static {
        $handler->call($this);
        return $this;
    }

    /**
     * Pop and return the last group pattern in the list of group patterns.
     * 
     * The list will be shortened by one element.
     * 
     * @return string|null The last group pattern, or null if the list is empty.
     */
    private function popLastGroupPattern(): ?string {
        return array_pop($this->groupPatterns);
    }

    [...]

}

长代码版本:

RouteCollectionInterface(完整版):

<?php

namespace PajuranCodes\Router;

use Closure;
use PajuranCodes\Router\RouteInterface;

/**
 * An interface to a collection of routes.
 *
 * @author pajurancodes https://github.com/pajurancodes
 */
interface RouteCollectionInterface extends \Countable, \IteratorAggregate {

    /**
     * Add a group.
     *
     * @param string $pattern A group pattern.
     * @param Closure $handler A group handler.
     * @return static
     */
    public function group(string $pattern, Closure $handler): static;

    /**
     * Add a route for one or more HTTP methods.
     *
     * @param string|string[] $methods One or more HTTP methods.
     * @param string $pattern A route pattern.
     * @param string|array|object $handler A route handler.
     * @return RouteInterface The route.
     */
    public function route(
        string|array $methods,
        string $pattern,
        string|array|object $handler
    ): RouteInterface;

    /**
     * Add a route for all HTTP methods.
     *
     * @param string $pattern A route pattern.
     * @param string|array|object $handler A route handler.
     * @return RouteInterface The route.
     */
    public function routeForAllMethods(string $pattern, string|array|object $handler): RouteInterface;

    /**
     * Add a route for the HTTP method GET.
     *
     * @param string $pattern A route pattern.
     * @param string|array|object $handler A route handler.
     * @return RouteInterface The route.
     */
    public function get(string $pattern, string|array|object $handler): RouteInterface;

    /**
     * Add a route for the HTTP method HEAD.
     *
     * @param string $pattern A route pattern.
     * @param string|array|object $handler A route handler.
     * @return RouteInterface The route.
     */
    public function head(string $pattern, string|array|object $handler): RouteInterface;

    /**
     * Add a route for the HTTP method POST.
     *
     * @param string $pattern A route pattern.
     * @param string|array|object $handler A route handler.
     * @return RouteInterface The route.
     */
    public function post(string $pattern, string|array|object $handler): RouteInterface;

    /**
     * Add a route for the HTTP method PUT.
     *
     * @param string $pattern A route pattern.
     * @param string|array|object $handler A route handler.
     * @return RouteInterface The route.
     */
    public function put(string $pattern, string|array|object $handler): RouteInterface;

    /**
     * Add a route for the HTTP method DELETE.
     *
     * @param string $pattern A route pattern.
     * @param string|array|object $handler A route handler.
     * @return RouteInterface The route.
     */
    public function delete(string $pattern, string|array|object $handler): RouteInterface;

    /**
     * Add a route for the HTTP method CONNECT.
     *
     * @param string $pattern A route pattern.
     * @param string|array|object $handler A route handler.
     * @return RouteInterface The route.
     */
    public function connect(string $pattern, string|array|object $handler): RouteInterface;

    /**
     * Add a route for the HTTP method OPTIONS.
     *
     * @param string $pattern A route pattern.
     * @param string|array|object $handler A route handler.
     * @return RouteInterface The route.
     */
    public function options(string $pattern, string|array|object $handler): RouteInterface;

    /**
     * Add a route for the HTTP method TRACE.
     *
     * @param string $pattern A route pattern.
     * @param string|array|object $handler A route handler.
     * @return RouteInterface The route.
     */
    public function trace(string $pattern, string|array|object $handler): RouteInterface;

    /**
     * Add a route for the HTTP method PATCH.
     *
     * @param string $pattern A route pattern.
     * @param string|array|object $handler A route handler.
     * @return RouteInterface The route.
     */
    public function patch(string $pattern, string|array|object $handler): RouteInterface;

    /**
     * Get a route by id.
     *
     * @param string $id A route id.
     * @return RouteInterface The found route.
     * @throws \UnexpectedValueException No route found.
     */
    public function getRouteById(string $id): RouteInterface;

    /**
     * Get a route by name.
     *
     * @param string $name A route name.
     * @return RouteInterface The found route.
     * @throws \UnexpectedValueException No route found.
     */
    public function getRouteByName(string $name): RouteInterface;

    /**
     * Check if a route exists in the collection.
     *
     * @param string $id A route id.
     * @return bool True if the specified route id exists, or false otherwise.
     */
    public function exists(string $id): bool;

    /**
     * Remove a route from the collection.
     *
     * @param string $id A route Id.
     * @return static
     */
    public function remove(string $id): static;

    /**
     * Get all routes from the collection.
     * 
     * @return RouteInterface[] All routes in the collection.
     */
    public function all(): array;

    /**
     * Remove all routes from the collection.
     * 
     * @return static
     */
    public function clear(): static;

    /**
     * Check if the collection is empty.
     * 
     * @return bool True if the collection is empty, or false otherwise.
     */
    public function isEmpty(): bool;

    /**
     * Count the routes in the collection.
     *
     * @return int Number of routes in the collection.
     */
    public function count(): int;

    /**
     * Get an iterator to iterate through the collection.
     *
     * @return \Traversable The routes iterator.
     */
    public function getIterator(): \Traversable;
}

RouteCollection(完整版):

<?php

namespace PajuranCodes\Router;

use function count;
use function implode;
use function array_pop;
use function array_key_exists;
use PajuranCodes\Router\{
    Route,
    RouteInterface,
    RouteCollectionInterface,
};
use Closure;
use Fig\Http\Message\RequestMethodInterface as RequestMethod;

/**
 * A collection of routes.
 *
 * @author pajurancodes https://github.com/pajurancodes
 */
class RouteCollection implements RouteCollectionInterface {

    /**
     * A string to be concatenated to a counter in order to form a route id.
     * 
     * @var string
     */
    private const ROUTE_ID_PREFIX = 'route';

    /**
     * A list of allowed HTTP methods.
     * 
     * @link https://tools.ietf.org/html/rfc7231#section-4 4. Request Methods
     * @link https://www.iana.org/assignments/http-methods/http-methods.xhtml Hypertext Transfer Protocol (HTTP) Method Registry
     * @link https://tools.ietf.org/html/rfc5789 PATCH Method for HTTP
     * 
     * @var string[]
     */
    private const ROUTE_ALLOWED_METHODS = [
        RequestMethod::METHOD_GET,
        RequestMethod::METHOD_HEAD,
        RequestMethod::METHOD_POST,
        RequestMethod::METHOD_PUT,
        RequestMethod::METHOD_DELETE,
        RequestMethod::METHOD_CONNECT,
        RequestMethod::METHOD_OPTIONS,
        RequestMethod::METHOD_TRACE,
        RequestMethod::METHOD_PATCH,
    ];

    /**
     * A list of routes as an associative array.
     * 
     * The key of each list item is a route id 
     * built from a prefix string and a counter.
     * 
     * @var RouteInterface[]
     */
    private array $routes = [];

    /**
     * A list of group patterns as an indexed array.
     *
     * {@internal Each time a group handler is executed, the group 
     * pattern is pushed to this list. When a route is added to the 
     * routes list (inside the scope of a group handler), the route 
     * pattern is prefixed with the string formed by concatenating 
     * all group patterns saved in this list.}
     *
     * @var string[]
     */
    private array $groupPatterns = [];

    /**
     * A counter used to be prefixed with a given string in order to form a route id.
     * 
     * {@internal After a route is added to the 
     * routes list this counter is incremented.}
     *
     * @var int
     */
    private int $routeIdCounter = 0;

    /**
     * @inheritDoc
     */
    public function group(string $pattern, Closure $handler): static {
        $this->addGroup($pattern, $handler);
        return $this;
    }

    /**
     * Add a group.
     *
     * @param string $pattern A group pattern.
     * @param Closure $handler A group handler.
     * @return static
     */
    private function addGroup(string $pattern, Closure $handler): static {
        $this->saveGroupPattern($pattern);
        $this->executeGroupHandler($handler);

        /*
         * Remove the last group pattern from the group patterns list. 
         * This step is performed only after all calls for adding 
         * groups/routes inside the scope of the current group 
         * handler have finished their processing.
         */
        $this->popLastGroupPattern();

        return $this;
    }

    /**
     * Save a group pattern.
     *
     * @param string $pattern A group pattern.
     * @return static
     */
    private function saveGroupPattern(string $pattern): static {
        $this->groupPatterns[] = $pattern;
        return $this;
    }

    /**
     * Execute a group handler.
     *
     * {@internal This method temporarily binds the given group handler to
     * the instance of the route collection - defined by the argument of 
     * Closure::call - and executes it. Inside the scope of the group handler, 
     * the route collection will be accessed using the keyword "$this".}
     * 
     * @link https://www.php.net/manual/en/closure.call.php Closure::call
     *
     * @param Closure $handler A group handler.
     * @return static The value returned by executing the group handler.
     */
    private function executeGroupHandler(Closure $handler): static {
        $handler->call($this);
        return $this;
    }

    /**
     * Pop and return the last group pattern in the list of group patterns.
     * 
     * The list will be shortened by one element.
     * 
     * @return string|null The last group pattern, or null if the list is empty.
     */
    private function popLastGroupPattern(): ?string {
        return array_pop($this->groupPatterns);
    }

    /**
     * @inheritDoc
     */
    public function route(
        string|array $methods,
        string $pattern,
        string|array|object $handler
    ): RouteInterface {
        return $this->addRoute($methods, $pattern, $handler);
    }

    /**
     * @inheritDoc
     */
    public function routeForAllMethods(string $pattern, string|array|object $handler): RouteInterface {
        return $this->addRoute(self::ROUTE_ALLOWED_METHODS, $pattern, $handler);
    }

    /**
     * @inheritDoc
     */
    public function get(string $pattern, string|array|object $handler): RouteInterface {
        return $this->addRoute(RequestMethod::METHOD_GET, $pattern, $handler);
    }

    /**
     * @inheritDoc
     */
    public function head(string $pattern, string|array|object $handler): RouteInterface {
        return $this->addRoute(RequestMethod::METHOD_HEAD, $pattern, $handler);
    }

    /**
     * @inheritDoc
     */
    public function post(string $pattern, string|array|object $handler): RouteInterface {
        return $this->addRoute(RequestMethod::METHOD_POST, $pattern, $handler);
    }

    /**
     * @inheritDoc
     */
    public function put(string $pattern, string|array|object $handler): RouteInterface {
        return $this->addRoute(RequestMethod::METHOD_PUT, $pattern, $handler);
    }

    /**
     * @inheritDoc
     */
    public function delete(string $pattern, string|array|object $handler): RouteInterface {
        return $this->addRoute(RequestMethod::METHOD_DELETE, $pattern, $handler);
    }

    /**
     * @inheritDoc
     */
    public function connect(string $pattern, string|array|object $handler): RouteInterface {
        return $this->addRoute(RequestMethod::METHOD_CONNECT, $pattern, $handler);
    }

    /**
     * @inheritDoc
     */
    public function options(string $pattern, string|array|object $handler): RouteInterface {
        return $this->addRoute(RequestMethod::METHOD_OPTIONS, $pattern, $handler);
    }

    /**
     * @inheritDoc
     */
    public function trace(string $pattern, string|array|object $handler): RouteInterface {
        return $this->addRoute(RequestMethod::METHOD_TRACE, $pattern, $handler);
    }

    /**
     * @inheritDoc
     */
    public function patch(string $pattern, string|array|object $handler): RouteInterface {
        return $this->addRoute(RequestMethod::METHOD_PATCH, $pattern, $handler);
    }

    /**
     * Add a route.
     *
     * @param string|string[] $methods One or more HTTP methods.
     * @param string $pattern A route pattern.
     * @param string|array|object $handler A route handler.
     * @return RouteInterface The route.
     */
    private function addRoute(
        string|array $methods,
        string $pattern,
        string|array|object $handler
    ): RouteInterface {
        $this
            ->validateRouteMethods($methods)
            ->validateRouteHandler($handler)
        ;

        $prefixedPattern = $this->prefixRoutePatternWithGroupPatterns($pattern);

        $route = $this->createRoute($methods, $prefixedPattern, $handler);

        return $this->saveRoute($route);
    }

    /**
     * Validate a list of route methods.
     *
     * @param string|string[] $methods One or more HTTP methods.
     * @return static
     * @throws \InvalidArgumentException The list of HTTP methods is empty.
     */
    private function validateRouteMethods(string|array $methods): static {
        if (empty($methods)) {
            throw new \InvalidArgumentException('One or more HTTP methods must be provided.');
        }

        return $this;
    }

    /**
     * Validate a route handler.
     *
     * @param string|array|object $handler A route handler.
     * @return static
     * @throws \InvalidArgumentException An empty route handler.
     */
    private function validateRouteHandler(string|array|object $handler): static {
        if (empty($handler)) {
            throw new \InvalidArgumentException('The route handler can not be empty.');
        }

        return $this;
    }

    /**
     * Prefix a route pattern with the pattern formed by 
     * concatenating the list of group patterns in a single string.
     *
     * @param string $pattern A route pattern.
     * @return string The prefixed route pattern.
     */
    private function prefixRoutePatternWithGroupPatterns(string $pattern): string {
        return $this->getConcatenatedGroupPatterns() . $pattern;
    }

    /**
     * Get the pattern formed by concatenating the 
     * list of group patterns in a single string.
     *
     * @return string The resulted pattern.
     */
    private function getConcatenatedGroupPatterns(): string {
        return $this->groupPatterns ? implode('', $this->groupPatterns) : '';
    }

    /**
     * Create a route.
     *
     * @param string|string[] $methods One or more HTTP methods.
     * @param string $pattern A route pattern.
     * @param string|array|object $handler A route handler.
     * @return RouteInterface The route.
     */
    private function createRoute(
        string|array $methods,
        string $pattern,
        string|array|object $handler
    ): RouteInterface {
        return new Route($methods, $pattern, $handler);
    }

    /**
     * Save a route.
     * 
     * Before saving, an id is assigned to the route.
     *
     * @param RouteInterface $route A route.
     * @return RouteInterface The route.
     */
    private function saveRoute(RouteInterface $route): RouteInterface {
        $id = $this->buildRouteId();

        $route->setId($id);

        $this->routes[$id] = $route;

        $this->routeIdCounter++;

        return $route;
    }

    /**
     * Build a route id.
     * 
     * The id is built by concatenating the 
     * route id prefix and the route id counter.
     *
     * @return string The route id.
     */
    private function buildRouteId(): string {
        return self::ROUTE_ID_PREFIX . (string) $this->routeIdCounter;
    }

    /**
     * @inheritDoc
     */
    public function getRouteById(string $id): RouteInterface {
        if (!$this->exists($id)) {
            throw new \UnexpectedValueException(
                    'A route with the id "' . $id . '" could not be found.'
            );
        }

        return $this->routes[$id];
    }

    /**
     * @inheritDoc
     */
    public function getRouteByName(string $name): RouteInterface {
        foreach ($this->routes as $route) {
            if ($route->getName() === $name) {
                return $route;
            }
        }

        throw new \UnexpectedValueException(
                'A route with the name "' . $name . '" could not be found.'
        );
    }

    /**
     * @inheritDoc
     */
    public function exists(string $id): bool {
        return array_key_exists($id, $this->routes);
    }

    /**
     * @inheritDoc
     */
    public function remove(string $id): static {
        if ($this->exists($id)) {
            unset($this->routes[$id]);
        }

        return $this;
    }

    /**
     * @inheritDoc
     */
    public function all(): array {
        return $this->routes;
    }

    /**
     * @inheritDoc
     */
    public function clear(): static {
        $this->routes = [];
        return $this;
    }

    /**
     * @inheritDoc
     */
    public function isEmpty(): bool {
        return !($this->count() > 0);
    }

    /**
     * @inheritDoc
     */
    public function count(): int {
        return count($this->routes);
    }

    /**
     * @inheritDoc
     */
    public function getIterator(): \Traversable {
        return new \ArrayIterator($this->routes);
    }

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM