jerowork/route-attribute-provider

Define routes by PHP8 attributes

0.7.0 2024-05-28 13:07 UTC

This package is auto-updated.

Last update: 2024-10-31 00:32:31 UTC


README

Build Status Coverage Status Quality Score Software License Packagist Version PHP Version

Define routes by PHP8 attributes.

Installation

Install via Composer:

$ composer require jerowork/route-attribute-provider

Configuration

In order to use route attributes, pick any of the existing framework implementations or create a custom one.

Instantiate RouteAttributeConfigurator somewhere close to the construction of your application, e.g. in your front controller (or ideally register in your PSR-11 container).

Basic configuration:

use Jerowork\RouteAttributeProvider\RouteAttributeConfigurator;

// ...

$routeConfigurator = new RouteAttributeConfigurator(
    new CustomRouteProvider($router)  // Implementation of your choice
);

$routeConfigurator
    ->addDirectory(sprintf('%s/src/Infrastructure/Api/Http/Action', __DIR__))
    ->configure();

// ...

Extended configuration:

use Jerowork\FileClassReflector\FileFinder\RegexIterator\RegexIteratorFileFinder;
use Jerowork\FileClassReflector\NikicParser\NikicParserClassReflectorFactory;
use Jerowork\RouteAttributeProvider\RouteAttributeConfigurator;
use Jerowork\RouteAttributeProvider\RouteLoader\ClassReflector\ClassReflectorRouteLoader;
use PhpParser\NodeTraverser;
use PhpParser\ParserFactory;
// ...

// All parts of the configurator can be replaced with a custom implementation
$routeConfigurator = new RouteAttributeConfigurator(
    new CustomRouteProvider($router), // Implementation of your choice
    new ClassReflectorRouteLoader(
        new NikicParserClassReflectorFactory(
            new RegexIteratorFileFinder(),
            (new ParserFactory())->create(ParserFactory::PREFER_PHP7),
            new NodeTraverser()
        )
    )
);

// Multiple directories can be defined
$routeConfigurator
    ->addDirectory(
        sprintf('%s/src/Infrastructure/Api/Http/Action', __DIR__),
        sprintf('%s/src/Other/Controller', __DIR__)
    )
    ->configure();

// ...

Existing implementations

Or check packagist for any other implementations.

Custom implementation

Create a custom implementation by using RouteAttributeProviderInterface.

A (fictive) custom implementation:

use Jerowork\RouteAttributeProvider\Api\Route;
use Jerowork\RouteAttributeProvider\RouteAttributeProviderInterface;

final class CustomRouteProvider implements RouteAttributeProviderInterface
{
    private SomeRouter $router;
    
    public function __construct(SomeRouter $router)
    {
        $this->router = $router;
    }

    public function configure(string $className,string $methodName, Route $route) : void
    {
        // Register rule at your router
        $rule = $this->router->addRule(
            $route->getMethods(),
            $route->getPattern(),
            $className.':'.$methodName,
            $route->getName()
        );

        // Register optional middleware
        foreach ($route->getMiddleware() as $middleware) {
            $rule->addMiddleware($middleware);
        }
    }
}

Cache

By default, caching of route attributes is disabled. This is fine for a development environment.

However, for a production environment you should use a more efficient way of route attribute loading. Therefore you can use any PSR-16 cache implementation.

use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Psr16Cache;

// ...

// Enable route attribute caching with any PSR-16 implementation (e.g. symfony/cache)
$routeConfigurator->enableCache(new Psr16Cache(new ApcuAdapter()));

// ...

Note: Any PSR-6 cache implementation can be used too, by using Symfony's PSR-6 to PSR-16 adapter.

Usage

A route can be defined via PHP8 attributes.

Minimalist example:

use Jerowork\RouteAttributeProvider\Api\Route;
use Psr\Http\Message\ResponseInterface as ServerRequest;
use Psr\Http\Message\ServerRequestInterface as Response;

final class RootAction
{
    #[Route('/root')]
    public function __invoke(ServerRequest $request, Response $response) : Response
    {
        return $response;
    }
}

Extended example:

use Jerowork\RouteAttributeProvider\Api\RequestMethod;
use Jerowork\RouteAttributeProvider\Api\Route;
use Psr\Http\Message\ResponseInterface as ServerRequest;
use Psr\Http\Message\ServerRequestInterface as Response;

final class RootAction
{
    #[Route('/root', method: RequestMethod::GET, name: 'root', middleware: SomeMiddleware::class)]
    public function __invoke(ServerRequest $request, Response $response) : Response
    {
        return $response;
    }
}

Full-fledged example:

use Jerowork\RouteAttributeProvider\Api\RequestMethod;
use Jerowork\RouteAttributeProvider\Api\Route;
use Psr\Http\Message\ResponseInterface as ServerRequest;
use Psr\Http\Message\ServerRequestInterface as Response;

final class RootAction
{
    #[Route('/root',
        method: [RequestMethod::GET, RequestMethod::POST],
        name: 'root',
        middleware: [
            SomeMiddleware::class,
            AnotherMiddleware::class,
        ],
        host: 'localhost',
        schemes: ['http', 'https'],
        httpPort: 8888,
        httpsPort: 9999,
        options: ['strategy' => 'something']
    )]
    #[Route('/second-route',
        method: RequestMethod::DELETE,
        name: 'second-route'
    )]
    public function __invoke(ServerRequest $request, Response $response) : Response
    {
        return $response;
    }
}