Giter Site home page Giter Site logo

sunrise-php / http-router Goto Github PK

View Code? Open in Web Editor NEW
158.0 4.0 10.0 1.15 MB

:tada: Release 2.0 is released! Very fast HTTP router for PHP 7.1+ based on PSR-7 and PSR-15 with support for annotations/attributes and OpenAPI (Swagger) Specification

License: MIT License

PHP 100.00%
php-library http http-router router psr-7 psr-15 middleware httprouter fastroute benchmark

http-router's People

Contributors

fenric avatar mekdrop avatar peter279k avatar renovate-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

http-router's Issues

Don't create the QueueableRequestHandler if the router/route doesn't contain middleware

problem here:

$handler = new QueueableRequestHandler($route);

example solution:

$route = $this->match($request);
$middlewares = $this->getMiddlewares();

if (empty($middlewares)) {
    return $route->handle($request);
}

$handler = new QueueableRequestHandler($route);
$handler->add(...$middlewares);

return $handler->handle($request);

second problem here:

$handler = new QueueableRequestHandler($this->requestHandler);

example solution:

if (empty($this->middlewares)) {
    return $this->requestHandler->handle($request);
}

$handler = new QueueableRequestHandler($this->requestHandler);
$handler->add(...$this->middlewares);

return $handler->handle($request);

Per-route "after" middleware not working

When I define an "after" middleware to a single route (i.e. it works with the chain's response object), and I have a router-wide middleware in place that returns a response, the per-route middleware is never invoked from the looks of it (or then I'm doing something stupid).

If I switch around the middleware stack injection order to RequestHandler in Router::handle() (so the per-route middleware is injected first, then Router-based middleware) things work as I would expect (https://github.com/sunrise-php/http-router/blob/master/src/Router.php#L137-L145).

Small example that fails on my end. Packages required are this router package, and zend/diactoros, along with the PSR interfaces.

<?php

use Psr\Http\Message\ResponseInterface as RI;
use Psr\Http\Message\ServerRequestInterface as SRI;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface as RHI;
use Sunrise\Http\Router\RouteCollection;
use Sunrise\Http\Router\Router;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\ServerRequestFactory;

include dirname(__DIR__) . '/vendor/autoload.php';

function callback_action(SRI $req) : RI
{
    return new HtmlResponse('foo bar');
}

class MyRouteMiddleware implements MiddlewareInterface
{
    // this method is never called
    // you can even call `die` inside it and nothing happens
    public function process(SRI $req, RHI $handler) : RI
    {
        $response = $handler->handle($req);

        return $response->withHeader('X-Foo', 'bar');
    }
}

class MyAppMiddleware implements MiddlewareInterface
{
    public function process(SRI $req, RHI $handler) : RI
    {
        // this just takes the route "handler" and calls it to return
        // a response see callback_action($req)
        $action_callable = $req->getAttribute('@route');

        return $action_callable($req);
    }
}

// first create a router, and add an "app-wide" middleware to it
$router = new Router();
$router->addMiddleware(new MyAppMiddleware());

// then create individual route, and add a per-route middleware to it
$routes = new RouteCollection();
$routes->get('\\callback_action', '/')
    ->addMiddleware(new MyRouteMiddleware());

$router->addRoutes($routes);

$request = ServerRequestFactory::fromGlobals();

// dispatch via the router
$response = $router->handle($request);

assert(trim($response->getBody()) === 'foo bar'); // this does not fail
assert($response->hasHeader('X-Foo')); // this fails

Please do say if I'm doing something wrong here, maybe I'm just blind form debugging this and am missing a stupid mistake somewhere.

Add support for group middleware

I would love to be able to do something similar to the following:

$router->group('/api', function ($group) {
    $group->get('api-home', '/')->addMiddleware(new ApiHomeAction());
}, [new MyApiMiddleware()]);

After which all routes that were defined inside the group receive the middleware definition.

I can probably subclass the route collector to accept the third parameter, but would this be a large enough of a usecase to be implemented into the package itself?

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Repository problems

These problems occurred while renovating this repository. View logs.

  • WARN: Unsupported composer value

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

circleci
.circleci/config.yml
  • cimg/php 7.1
  • cimg/php 7.2
  • cimg/php 7.3
  • cimg/php 7.4
  • cimg/php 8.0
  • cimg/php 8.1
composer
composer.json
  • php ^7.1|^8.0
  • fig/http-message-util ^1.1
  • psr/container ^1.0 || ^2.0
  • psr/http-message ^1.0
  • psr/http-server-handler ^1.0
  • psr/http-server-middleware ^1.0
  • psr/simple-cache ^1.0
  • phpunit/phpunit 7.5.20|9.5.0
  • sunrise/coding-standard 1.0.0
  • sunrise/http-factory 2.0.0
  • doctrine/annotations ^1.6
  • symfony/console ^5.4
  • symfony/event-dispatcher ^4.4

  • Check this box to trigger a request for Renovate to run again on this repository

Create a new Middleware annotation for route declaration in one line

#[Route('name', path: '/path', method: 'GET')]
#[Middleware(SomeMiddleware::class)]
#[Middleware(AnotherMiddleware::class)]

Otherwise, the following code hurts my eyes:

#[Route('name', path: '/path', method: 'GET', middlewares: [
    SomeMiddleware::class,
    AnotherMiddleware::class,
])]
#[SomeAttribute()]
#[AnotherAttribute()]

Gets a route holder

Need to create a logic to get a route holder, e.g.:

private function getRouteHolder(RouteInterface $route) : ReflectionClass|ReflectionMethod|null
{
    $handler = $route->getRequestHandler();
    if (!($handler instanceof CallableRequestHandler)) {
        return new ReflectionClass($handler);
    }

    $callback = $handler->getCallback();
    if (is_array($callback)) {
        return new ReflectionMethod(...$callback);
    }

    return null;
}

... such code can be part of a route or new logic...

Real cases:

  1. OpenAPI package requires a route's holder;
  2. Through an event can be created a logic for mapping request body to a DTO;
  3. etc.

Suggestion: lazy-load middleware using PSR-11

It would be good to lazy-load middleware.

$routes->addMiddleware(new Middleware1());  // Creates the middleware now.
$routes->addMiddleware(Middleware2::class); // Creates the middleware if/when it is needed.

The dispatcher can then use a PSR-11 container to create the middleware.

e.g.

if (is_string($middleware)) {
   $middleware = $this->psr11_container->get($middleware);
}

MethodNotAllowed is thrown when not needed and vice versa

According to the following code in Router::match

$routes = [];
foreach ($this->routes as $route) {
    foreach ($route->getMethods() as $method) {
        $routes[$method][] = $route;
    }
}

$method = $request->getMethod();
if (!isset($routes[$method])) {
    $errmsg = sprintf('The method "%s" is not allowed.', $method);

    throw new MethodNotAllowedException($errmsg, [
        'allowed' => array_keys($routes),
    ]);
}

MethodNotAllowedException is thrown depending on the list of HTTP Methods from all the routes bound to the Router instead of being based on URL.

This leads to the following cases when it seems like the router doesn't work as intended:

  1. On requesting a nonexistent route (path) one can get MethodNotAllowedException being throwpn (instead of RouteNotFoundException) if the requesting method is not found at any of registered routes.
  2. On requesting a registered route (path) using the method that is not supported by that route one can get no exception being thrown if that method is present anywhere else.

You can also check RFC to make sure that the current behavior is improper.

Add support for a bare group path

When I use the following:

$routes->group('/', function ($group) {
    $group->get('index', '/');
});

The resulting route will match //, not /.

I tried, and removing the / from either the group call, or the get call makes it work again, but for the sake of usability I would like to see the generated route collapse multiple / into a single /.

Flexible request handler definition for route

I tried to use this router without annotations/attributes.
All route definition methods require request handler to implement RequestHandlerInterface:

public function get(
    string $name,
    string $path,
    RequestHandlerInterface $requestHandler, // question about this parameter
    array $middlewares = [],
    array $attributes = []
) : RouteInterface {
  ...
}

But this is hard to use with invokable actions or controllers. Documentation shows simple example:

$collector = new RouteCollector();
$collector->get('home', '/', new HomeController());

Actions and controller constructors can hold dependencies and usage with initiating new action object is not comfortable. It can be solved with DI like so:

$collector->get('test', '/test', $container->get(\App\Action\TestAction::class));

but it is not cool too.

I suggest to extend route request handler type to accept callables, path to classes:

$collector->get('test', '/test', \App\Action\Article\UpdateAction::class);
$collector->get('test', '/test', [\App\Controller\ArticleController::class, 'update]);

Router: fixes and questions

  1. public function any(string $id, string $path) : RouteInterface

    $methods = (new \ReflectionClass(RequestMethodInterface::class))->getConstants();
  2. Why route_regex excluded from Route?
  3. Route::buildRegex() not chached compiled pattern - it's freeze executing
  4. Route::group() cant defines attributes in prefix + cant set methods by default for group routes.
  5. return $clone;

    Why route cloned? This attributes not contains in RouteCollection. Class doesnt have other methods to set attributes + cant define attributes by default
  6. $regex = \preg_replace_callback('/{(\w+)}/', function($match) use($patterns)

    '/{(\w+)}/' - \w contains locale symbols, pure - '/{([a-z0-9_]+)}/'
  7. '$#ui' URI attributes may be case sensitive, i vote to '$#uD'
  8. Path prefix/suffix not contains separately - setPath() rewrite all path
  9. $regex = \addcslashes($path, '.\+*?[^]$=!<>|:-#');

    add slashes in setters (addSuffix, addPrefix, setPath)
  10. $this->routes[] = $route;

    id property dont used as key part - cant overwrite route dublicates, may be set $routes as SplObjectStorage? route - key, id - value;
  11. if (\preg_match($regex, $request->getUri()->getPath(), $matches))

    in_array($request->getMethod(), $route->getMethods()) faster then preg_match($regex, $request->getUri()->getPath(), $matches). move up in_array
  12. Why RequestHandler::handle() call only one middleware? why $middlewareQueue is SplQueue? It`s prevents using them again for inner(server side) requests
  13. Cant find Route::getUri(array $attributes = []) : string or something like this - generate URI by route with attributes

Hard dependence on ResponseFactory

RequestHandler uses Sunrise\Http\Message\ResponseFactory. How about adding Psr\Http\Message\RequestFactoryInterface dependencies to the constructor of RequestHandler and Router classes?

Short version of URI generation

it might look like this:

$router->generateUri('some.route', 'foo');

$router->generateUri('some.route', 'foo', 'bar');

the current API shouldn't be broken...

Containers

How to implement containers? I’m looking in the readme for examples but i don’t get it work.

Can someone help me to integrate it in my controllers with annotations?

Too few arguments to function Cosmic\App\Controllers\Home\Index::index(), 1 passed

Trying to make a controller but I dont get it work with the responseInterfaces
Receiving Too few arguments to function Cosmic\App\Controllers\Home\Index::index(), 1 passed

`
namespace Cosmic\App\Controllers\Home;

use Sunrise\Http\Router\Annotation as Mapping;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;

class Index {

#[Mapping\Route('/', path: '/')]
public function index(Request $request, Response $response) {
    echo 1;
}

}
`

Remove "Team" section

As title.

Now this is the organization and we don't have this Team section anymore :).

How to response Json?

Im trying to figure out how to reponse Json.

I receive something like below and when I return it with json data the page is empty

Sunrise\Http\Message\Response Object ( [protocolVersion:protected] => 1.1 [headers:protected] => Array ( ) [body:protected] => Sunrise\Stream\Stream Object ( [resource:protected] => Resource id #11 ) [statusCode:protected] => 200 [reasonPhrase:protected] => OK )

`class Api extends BaseController {

#[Mapping\Route('/aa-migration', path: '/api/migrations/accessarea/dslam/{dslam}', middlewares: [
    JsonResponseMiddleware::class
])]
public function api(Request $request): Response
{
    $migration = AAMigrations::where('dslam', $request->getAttribute('dslam'))->get();

    return $this->respond(
        (new ResponseFactory)->createResponse(200),
        response()->setData($migration)
    );
}

}
?>`

` protected function respond(Response $response, CustomResponseInterface $customResponse): Response
{
$response->getBody()->write($customResponse->getJson());

    return $response
        ->withStatus(
            $customResponse
                ->getCode()
        )
        ->withHeader(
            'Content-Type',
            'application/json'
        );
}`

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.