leocavalcante / siler

⚡ Flat-files and plain-old PHP functions rockin'on as a set of general purpose high-level abstractions.
https://siler.leocavalcante.dev
MIT License
1.12k stars 90 forks source link

Implement PSR-15 middlewares #75

Closed tonydspaniard closed 5 years ago

tonydspaniard commented 6 years ago

Hi @leocavalcante,

Is it possible to make use of psr-15 middlewares -https://github.com/middlewares/psr15-middlewares ? If so, can you make a brief example?

Highly interesting framework and concepts. Thanks for sharing.

leocavalcante commented 6 years ago

Hi @tonydspaniard

Sorry, no PSR-15 support out of the box. To get PSR-15 we need to support PSR-7 which fortunately is already supported thanks do zend-diactoros! I could make something at Diactoros namespace, what you think?

Maybe for the API: Diactoros\middleware(<PSR-15 compliant>)

Suggestions are welcome.

cc @IvanDelsinne some thoughts?

tonydspaniard commented 6 years ago

I could make something at Diactoros namespace, what you think?

It would be awesome.

mnavarrocarter commented 5 years ago

I would love to help with this. I've been wanting to try this library for a while, but unfortunately, middleware is a must for me before I use it. But, here I am!

The only problem is that I have zero functional programming experience. For looking at the code in Diactoros namespace, it looks like it does nothing else than creating new objects. However, middleware requires a middleware chain or pipe: a class that stores the stack of middlewares and it's able to play them one by one. Either that or explicitly wrapping the route function in a middleware function, and do that for each one of the routes. Or I don't have the enough knowledge of functional patterns to come up with an idea. I think the last one is most probable.

If you can guide me in an approach I can tackle this, I'll be really glad to implement it. :)

leocavalcante commented 5 years ago

We should probably go for zend-stratigility + zend-httphandlerrunner to play around with zend-diactoros.

Desirable API:


$request = Diactoros\request();

$pipe = Stratigility\pipeline();
$pipe(MiddlewareInterface);
$pipe(MiddlewareInterface);
$pipe(MiddlewareInterface);

$pipe2 = Stratigility\pipeline('non_default_name');
$pipe2(MiddlewareInterface);
$pipe2(MiddlewareInterface);
$pipe2(MiddlewareInterface);

HttpHandleRunner\emit(Stratigility\handle($request));
HttpHandleRunner\emit(Stratigility\handle($request, 'non_default_name'));

Maybe more concise:

Stratigility\pipe(MiddlewareInterface);
Stratigility\pipe(MiddlewareInterface);
Stratigility\pipe(MiddlewareInterface);

HttpHandleRunner\emit(Stratigility\handle(Diactoros\request()));

With proper uses it could be very simple and Siler-ish to read:

use function Siler\Diactoros\request;
use function Siler\Stratigility\pipe;
use function Siler\Stratigility\handle;
use function Siler\HttpHandleRunner\emit;

pipe(MiddlewareInterface);
pipe(MiddlewareInterface);
pipe(MiddlewareInterface);

emit(handle(request()));

Different pipe names as second argument:

pipe(MiddlewareInterface);
pipe(MiddlewareInterface);

pipe(MiddlewareInterface, 'debug');
pipe(MiddlewareInterface, 'debug');
pipe(MiddlewareInterface, 'debug');

env('APP_DEBUG') ? emit(handle(request(), 'debug')) : emit(handle(request()));

What you think?

State could be handled internally by Container, don't worry to be funcional-full...

leocavalcante commented 5 years ago

Hi @mnavarrocarter Saw you didn't forked it yet, so I'll be working on it, hope to have something merged by the weekend ;)

mnavarrocarter commented 5 years ago

Sorry, I was busy at work today, but all you said sound pretty cool. :)

Question, is Siler's container PSR-11 compliant?

mnavarrocarter commented 5 years ago

Another interesting thing to be able to achieve, is to pass middleware to routes and route groups. This is how is done, "a la Slim":

<?php

$app->group(function($app) {
    $app->get('/users/{id}', SomeHandler::class)->add(AnotherMiddleware::class); // This middleware affects only this route.
})->add(SomeMiddleware::class); // This gets resolved by the container. This middleware affects the whole group.

Since everything is globally accessible, I don't think it would be super hard. :)

leocavalcante commented 5 years ago

Question, is Siler's container PSR-11 compliant?

No, actually it's just a Singleton with an Array

Another interesting thing to be able to achieve, is to pass middleware to routes and route groups.

I think this 'll composable out-of-the-box, since Siler routes already accept a PSR-7 Request Message as argument. I'll be adding this on the example ;)

leocavalcante commented 5 years ago

@mnavarrocarter, just saw that Slim's Middlewares aren't PSR-15 complaint, am I right? Well, if so, its a +1 for Siler over it 😀 But, Slim's advantage is the onion architecture.

Here is it for Siler, the API, for now, looks like:

<?php

declare(strict_types=1);
require_once __DIR__.'/../../../vendor/autoload.php';

use Siler\Route;
use Siler\Diactoros;
use Siler\HttpHandlerRunner;
use Siler\Stratigility;
use Siler\Http\Request;

$userMiddleware = function ($request, $handler) {
    $token = Request\get('token');

    if (empty($token)) {
        return Diactoros\json('no user', 401);
    }

    $user = "get_user_by_token:$token";
    $request = $request->withAttribute('user', $user);

    return $handler->handle($request);
};

$homeHandler = function () {
    return Diactoros\json('welcome');
};

$adminHandler = function ($request) {
    return Diactoros\json(['user' => $request->getAttribute('user')]);
};

$secretHandler = function ($request) {
    return Diactoros\json(['user' => $request->getAttribute('user')]);
};

Stratigility\pipe($userMiddleware, 'auth');

$request = Diactoros\request();
$response = Route\match([
    Route\get('/', $homeHandler, $request),
    Route\get('/admin', Stratigility\process($request, 'auth')($adminHandler), $request),
    Route\get('/secret', Stratigility\process($request, 'auth')($secretHandler), $request),
    Diactoros\json('not found', 404),
]);

HttpHandlerRunner\sapi_emit($response);

What you think? @tonydspaniard, any thoughts? Almost a year later... sorry.

PS.: already working: https://github.com/leocavalcante/siler/pull/130/files#diff-c40d676d8de9c7e8a6e456540d4a401d

leocavalcante commented 5 years ago

https://github.com/leocavalcante/siler/blob/master/docs/http/psrs-middlewares-pipelines.md

agentd00nut commented 5 years ago

https://siler.leocavalcante.dev/psrs-and-middlewares-pipelines