Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for PSR-15 middlewares #2379

Closed
wants to merge 7 commits into from
Closed

Add support for PSR-15 middlewares #2379

wants to merge 7 commits into from

Conversation

bnf
Copy link
Contributor

@bnf bnf commented Jan 10, 2018

PSR-15 middlewares can be registered using the existing API:
App::add() and Routable::add()
Those methods are extended to accept objects that implement
the MiddlewareInterface (or class names that refer to classes
which implement the MiddlewareInterface).

We do not add new entry points for middleware registration, as the type
of the middleware can be detecting through the MiddlewareInterface
which all PSR-15 middlewares implement.

Both, classes that do not implement MiddlewareInterface, and closures will
be handled as single pass middleware – as before.
That means there's no support for closures with a PSR-15 single-pass
signature.

The detection of the middleware type is implemented
in the callable resolver. While that might look like
something which is out of scope for the CallableResolver, reasons are:

  1. The internal slim middleware stack stays at is – it handles callables only.
    (and in cannot be changed anyway as long double-pass middlewares
    need to be supported. Double-pass middlewares expect a Response object
    that needs to be passed along the middleware stack. Therefore we'd need to
    wrap PSR-15 middleware's in any case – which is what this change provides)
  2. the CallableResolver resolves (in terms for transforms) a callable
    from PSR-15 style middleware.

TODO: Add more tests for:

  • Adapter\SinglePassMiddleware
  • Routable with PSR-15 Middlewares

Resolves #2050

@coveralls
Copy link

Coverage Status

Coverage increased (+0.1%) to 95.044% when pulling f5ec341 on bnf:4x-psr-15-v2 into 1306570 on slimphp:4.x.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.1%) to 95.044% when pulling 8958174 on bnf:4x-psr-15-v2 into 1306570 on slimphp:4.x.

@geggleto geggleto added this to the 4.0 milestone Jan 10, 2018
@geggleto
Copy link
Member

Thanks :) I will take a good look at this today or tomorrow, I know akrabat is at a conference this week so he might be able to get around to it next.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.1%) to 95.044% when pulling 405bd08 on bnf:4x-psr-15-v2 into 1306570 on slimphp:4.x.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.1%) to 95.044% when pulling 0c45802 on bnf:4x-psr-15-v2 into 1306570 on slimphp:4.x.

@coveralls
Copy link

coveralls commented Jan 12, 2018

Coverage Status

Coverage increased (+0.05%) to 97.455% when pulling 950a541 on bnf:4x-psr-15-v2 into c39d69a on slimphp:4.x.


public function handle(ServerRequestInterface $request): ResponseInterface
{
return call_user_func($this->next, $request, $this->response);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since $this->next is `callable, and this PR requires PHP 7 and up, that means you can simplify the above to:

return ($this->next)($request, $this->response);

My understanding is that call_user_func is slower than PHP's native dereferencing in PHP 7, so the above will also be more performant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used call_user_func since that was used throughout the Slim codebase. I think those occurrences should be changed in one go. Will push a patch for that.

@@ -8,8 +8,10 @@
*/
namespace Slim;

use Interop\Http\Server\MiddlewareInterface;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a heads-up: PSR-15 passed today, though we're waiting to announce it until the packages are on Packagist — though that should be within the next few hours after I write this. As such, you'll be able to require psr/http-server-middleware instead of http-interop/http-server-middleware, and the above will become Psr\Http\Server\MiddlewareInterface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I pushed a patch for the switch to psr/http-server-middleware.

* and is used only inside of the Slim application; it is not visible
* to—and should not be used by—end users.
*/
final class SinglePassMiddleware implements RequestHandlerInterface

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of SinglePassMiddleware, may I suggest StandardsBasedMiddleware or PsrMiddleware here?

I've noted in communicating with ZF folks that many are not clear what is meant by "single-pass" and "double-pass", despite us having documented it. Having a name that indicates the type of middleware in terms of where you might find a definition helps.

Also, I'd add an @see annotation with a link to the accepted standard (we'll have that on the fig-standards repo shortly).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I personally prefer a name that describes the functionality. With PsrMiddleware it's not that clear whether the middleware is implementing PSR 15 or (as in our case) adapts a PSR-15 middleware.
Anyway, I've no strong objections against PsrMiddleware so i'll push a patch that renames that one (consistent naming across frameworks is probably not that bad).

@@ -87,13 +87,13 @@ public function getCallableResolver()
/**
* Prepend middleware to the middleware collection
*
* @param callable|string $callable The callback routine
* @param MiddlewareInterface|callable|string $middleware The middleware routine

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you imported this interface within this class file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed that one, will fix.

@bnf bnf changed the title [RFC] Add support for PSR-15 style middlewares Add support for PSR-15 middlewares Jan 23, 2018
.travis.yml Outdated
env: COMPOSER_ARGS='--prefer-lowest'
- php: nightly
allow_failures:
- php: hhvm
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just take hhvm out - we're not supporting it in 4.x

@akrabat
Copy link
Member

akrabat commented Sep 30, 2018

I've been thinking about this a bit. While I appreciate that it's easier to keep the internals the same as Slim 3, I'd like to explore if we can make PSR-15 the default internally and "wrap" the current signatures. Going forward, this makes more sense to me as we probably won't support the current signatures in Slim 5.

Thoughts?

@l0gicgate
Copy link
Member

@bnf are you moving forward with this PR? I'm curious as I will tackle it after my next couple of PRs if you aren't.

@l0gicgate l0gicgate mentioned this pull request Dec 21, 2018
1 task
@bnf
Copy link
Contributor Author

bnf commented Jan 24, 2019

Hi @akrabat, hi @l0gicgate,

Sorry, I missed your comments/thoughts. Therefore now my (very late) reaction:

I still think this is the right approach and actually considered it ready. Therefore I didn't do more work on this, because I was waiting for it to be merged. It's actually used in production as a backport for slim v3: https://packagist.org/packages/bnf/slim3-psr15
The main motivation for sticking with the old middleware dispatcher is the $response object. It needs to be handed over all the way from the first double-pass style middleware to the very last one.

I'd like to explore if we can make PSR-15 the default internally and "wrap" the current signatures

Going with a pure PSR-15 solution and adding a wrapper for double pass middlewares (as in #2555) results in (hard to detect) breaking behaviour, as the wrapper has to create a new $response object for every double pass middleware. That means upgrading to Slim v4 will be a bit of a pain. (IMO)
To illustrate that: The following code will no longer work with #2555, as the $response will not be passed, but rather silently dropped:

$response = $response->withStatus(403);
return $next($request, $response);

Existing middleware code that expect to be able to pass a modified $response to the $next middleware will fail with that change. I know that it's a technical limitation due to the changes in PSR-15.
For exactly that reason I decided to keep the existing dispatcher instead of creating a new pure PSR-15 one.
If you find that limitation of dropping $response to be ok, than please go ahead with #2555 otherwise I'd ask you to reconsider this approach.

Thanks, Benjamin

…s other than handle()

If 'FooBar:customMethod' is specified to be dispatched, the CallableResolver must not resolve
to the handle() method even though FooBar implements the RequestHandlerInterface.
It should only do that, if no method name is given.
… grasp

Do not invoke objects that don't need them, rather use early retuns.

No need to check is_callable for Closures or classes that implement
RequestHandlerInterface where instanceof is sufficient to return early.
PSR-15 middlewares can be registered using the existing API:
`App::add()` and `Routable::add()`
Those methods are extended to accept objects that implement
the MiddlewareInterface (or class names that refer to classes
which implement the MiddlewareInterface).

We do not add new entry points for middleware registration, as the type
of the middleware can be detecting through the MiddlewareInterface
which all PSR-15 middlewares implement.

Both, classes that do not implement MiddlewareInterface, and closures will
be handled as single pass middleware – as before.
That means there's no support for closures with a PSR-15 style single-pass
signature.

The detection of the middleware type is implemented
in the callable resolver. While that might look like
something which is out of scope for the CallableResolver, reasons are:
a) The internal slim middleware stack stays at is – it handles callables only.
   (and in cannot be changed anyway as long double-pass middlewares
   need to be supported. Double-pass middlewares expect a Response object
   that needs to be passed along the middleware stack. Therefore we'd need to
   wrap PSR-15 middleware's in any case – which is what this change provides)
b) the CallableResolver resolves (in terms for transforms) a callable
   from PSR-15 style middleware.

TODO: Add more tests for:
 - Adapter\PsrMiddleware
 - Routable with PSR-15 Middlewares

Resolves slimphp#2050
@l0gicgate
Copy link
Member

Closing this as we are moving forward with #2555. Thank you for your contribution @bnf

@l0gicgate l0gicgate closed this Feb 12, 2019
@akrabat
Copy link
Member

akrabat commented Feb 12, 2019

Closing this as we are moving forward with #2555. Thank you for your contribution @bnf

To this point, we only need one PR to get PSR-15 support into Slim 4.

However, the BC problems that @bnf highlights (and @l0gicgate also highlighted in Slack at some point in the past to me) do need to be explicitly discussed I think.

My initial reaction as @l0gicgate noted is that passing the response down the chain is bad, so my natural reaction is that I don't mind breaking it in Slim 4. However, is that the right call? I could definitely be wrong.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants