Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Modify middleware listener to use stratigility pipe #217

Conversation

asgrim
Copy link
Contributor

@asgrim asgrim commented Feb 19, 2017

Taking issue #196 into play, this PR proposes to update the MiddlewareListener to use Stratigility to allow piped middleware. This means the middleware param can become an array and everything is pushed into the MiddlewarePipe... as let's be fair, only allowing just a single middleware is only marginally useful.

For this to work, zendframework/zend-stratigility needs to become a requirement for those using MiddlewareListener (I added it as require-dev for now), so could be seen as a BC break potentially; so happy to negotiate on how we'd like this added to composer.json. Otherwise, I believe (pending CI results) all the other tests still pass though.

@asgrim
Copy link
Contributor Author

asgrim commented Feb 19, 2017

Apparently I didn't have an up to date develop branch >.<

@asgrim asgrim force-pushed the modify-middleware-listener-to-use-stratigility-pipe branch from 0e0a650 to 2f947e8 Compare February 19, 2017 12:39
@asgrim
Copy link
Contributor Author

asgrim commented Feb 19, 2017

Rebased correctly now \o/

$psr7Request,
$psr7ResponsePrototype,
function (PsrServerRequestInterface $request, PsrResponseInterface $response) {
throw new ReachedFinalHandlerException(
Copy link
Member

Choose a reason for hiding this comment

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

@weierophinney are you OK with moving this to a static named constructor instead? Asking because consistency...

Copy link
Member

Choose a reason for hiding this comment

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

I am, but I'm not convinced it's needed, as there's a static message with no dynamic substitutions, and no additional behavior.

Copy link
Member

Choose a reason for hiding this comment

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

One potential refactor I see here is to instead define ReachedFinalHandlerException as follows:

class ReachedFinalHandlerException extends RuntimeException
{
    public static function createFinalHandler()
    {
        return new self('Reached the final handler for middleware pipe - check the pipe configuration');
    }

    public function __invoke()
    {
        throw $this;
    }
}

This would then allow you to do the following above:

$return = $pipe($psr7Request, $psr7Response, ReachedFinalHandlerException::createFinalHandler());

Not sure if that's abusing inheritance and SRP, though. 😁

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yikes, I'm not really sure about the exception itself being the final handler, although I gotta say it's quite clever... does feel a bit wrong. I'll just refactor to named constructor for now and if you want to change it to be the actual final handler, I'm happy to :)


$middlewareName = 'noMiddlewarePiped';
$middlewareToBePiped = null;
foreach ($middlewaresToBePiped as $middlewareToBePiped) {
Copy link
Member

Choose a reason for hiding this comment

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

Segregate to a private method.

Copy link
Member

Choose a reason for hiding this comment

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

Probably should just return $pipe in the private method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

$pipe = new MiddlewarePipe();
$pipe->setResponsePrototype($psr7ResponsePrototype);

$middlewaresToBePiped = !is_array($middleware) ? [$middleware] : $middleware;
Copy link
Member

Choose a reason for hiding this comment

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

Don't negate: invert the ternary please

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

$middlewareName = 'noMiddlewarePiped';
$middlewareToBePiped = null;
foreach ($middlewaresToBePiped as $middlewareToBePiped) {
$middlewareName = is_string($middlewareToBePiped) ? $middlewareToBePiped : get_class($middlewareToBePiped);
Copy link
Member

Choose a reason for hiding this comment

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

Because get_class() is stupid this check is insufficient

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Didn't change this from the original code...

foreach ($middlewaresToBePiped as $middlewareToBePiped) {
$middlewareName = is_string($middlewareToBePiped) ? $middlewareToBePiped : get_class($middlewareToBePiped);

if (is_string($middlewareToBePiped) && $serviceManager->has($middlewareToBePiped)) {
Copy link
Member

Choose a reason for hiding this comment

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

is_string() check is duplicate

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure what to do about it; it's just the flexibility around using a string as the middleware (load from container), or a callable (not ideal, but allowed)

if (is_string($middlewareToBePiped) && $serviceManager->has($middlewareToBePiped)) {
$middlewareToBePiped = $serviceManager->get($middlewareToBePiped);
}
if (! is_callable($middlewareToBePiped)) {
Copy link
Member

Choose a reason for hiding this comment

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

Should probably also NOT be a string?

Copy link
Member

Choose a reason for hiding this comment

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

An example is an incompatible function name being passed in.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If it's a string, it won't be a string any more. Also just following same logic that was already there...

@@ -75,7 +99,9 @@ public function onDispatch(MvcEvent $event)
$event->setName(MvcEvent::EVENT_DISPATCH_ERROR);
$event->setError($application::ERROR_EXCEPTION);
$event->setController($middlewareName);
$event->setControllerClass(get_class($middleware));
if (null !== $middlewareToBePiped) {
$event->setControllerClass(get_class($middlewareToBePiped));
Copy link
Member

Choose a reason for hiding this comment

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

You always assume that the last piped middleware is the "responding" one here... Probably needs documenting/commenting

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed these as there's no longer just one middleware, so doesn't make sense. I guess this causes a definite BC break which is kinda rubbish though?

$middlewaresToBePiped = !is_array($middleware) ? [$middleware] : $middleware;

$middlewareName = 'noMiddlewarePiped';
$middlewareToBePiped = null;
Copy link
Member

Choose a reason for hiding this comment

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

Can an empty pipeline be considered valid?

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 don't see why not; you'll just reach the final handler anyway...

@asgrim
Copy link
Contributor Author

asgrim commented Feb 25, 2017

@Ocramius some changes applied as requested.

@asgrim
Copy link
Contributor Author

asgrim commented Feb 25, 2017

shakes fist at PHP 5.6 support

@asgrim asgrim removed their assignment Mar 6, 2017
composer.json Outdated
@@ -27,7 +27,8 @@
"zendframework/zend-json": "^2.6.1 || ^3.0",
"zendframework/zend-psr7bridge": "^0.2",
"fabpot/php-cs-fixer": "1.7.*",
"phpunit/phpunit": "^4.5"
"phpunit/phpunit": "^4.5",
"zendframework/zend-stratigility": "^2.0"
Copy link
Member

Choose a reason for hiding this comment

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

This should likely be ^2.0.1, but I can take care of that during merge.

$psr7Request,
$psr7ResponsePrototype,
function (PsrServerRequestInterface $request, PsrResponseInterface $response) {
throw new ReachedFinalHandlerException(
Copy link
Member

Choose a reason for hiding this comment

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

I am, but I'm not convinced it's needed, as there's a static message with no dynamic substitutions, and no additional behavior.

@@ -74,8 +97,6 @@ public function onDispatch(MvcEvent $event)
if ($caughtException !== null) {
$event->setName(MvcEvent::EVENT_DISPATCH_ERROR);
$event->setError($application::ERROR_EXCEPTION);
$event->setController($middlewareName);
$event->setControllerClass(get_class($middleware));
Copy link
Member

Choose a reason for hiding this comment

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

Why are these lines removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is potentially no longer just "one" middleware name... how do we decide which is the failed one? First? Last? IIRC I don't think there's any way (outside of the exception bubble here)

Copy link
Member

Choose a reason for hiding this comment

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

Ah, right - that makes sense, then.

if (is_string($middlewareToBePiped) && $serviceLocator->has($middlewareToBePiped)) {
$middlewareToBePiped = $serviceLocator->get($middlewareToBePiped);
}
if (! is_callable($middlewareToBePiped)) {
Copy link
Member

Choose a reason for hiding this comment

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

This needs to change to be:

if (! $middlewareToBePiped instanceof \Http\Interop\ServerMiddleware\MiddlewareInterface
    && ! is_callable($middlewareToBePiped)
)

as zend-stratigility 2.0 allows either of those types. Testing only for is_callable() means that http-interop middleware cannot be used!

Copy link
Member

Choose a reason for hiding this comment

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

Once you make those changes, you'll also need to add tests for piping http-interop middleware.

$psr7Request,
$psr7ResponsePrototype,
function (PsrServerRequestInterface $request, PsrResponseInterface $response) {
throw new ReachedFinalHandlerException(
Copy link
Member

Choose a reason for hiding this comment

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

One potential refactor I see here is to instead define ReachedFinalHandlerException as follows:

class ReachedFinalHandlerException extends RuntimeException
{
    public static function createFinalHandler()
    {
        return new self('Reached the final handler for middleware pipe - check the pipe configuration');
    }

    public function __invoke()
    {
        throw $this;
    }
}

This would then allow you to do the following above:

$return = $pipe($psr7Request, $psr7Response, ReachedFinalHandlerException::createFinalHandler());

Not sure if that's abusing inheritance and SRP, though. 😁

@weierophinney
Copy link
Member

Also, @asgrim — You'll need to rebase against latest develop branch, as I pushed a bunch of QA tooling changes earlier (PHPUnit 5.7/6.0 support, in particular, as well as zend-coding-standards integration).

@asgrim asgrim self-assigned this Apr 28, 2017
@asgrim asgrim force-pushed the modify-middleware-listener-to-use-stratigility-pipe branch from b8511d3 to 7d89900 Compare April 28, 2017 16:04
{
$expectedOutput = uniqid('expectedOutput', true);

$event = $this->createMvcEvent('path', new class($expectedOutput) implements 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.

bah, doesn't work on PHP 5.6 - I thought develop branches were min PHP 7.1 now? Maybe I didn't read properly?

@asgrim
Copy link
Contributor Author

asgrim commented Apr 30, 2017

@weierophinney all updated, thanks for review!

@asgrim asgrim removed their assignment Apr 30, 2017
…g-strategy

Fixes @param & @return DefaultRenderingStrategy typo at HttpDefaultRenderingStrategyFactory
Forward port zendframework#237

Conflicts:
	src/Service/HttpDefaultRenderingStrategyFactory.php
Copy link
Member

@weierophinney weierophinney left a comment

Choose a reason for hiding this comment

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

Few last nit-picks, but looks great!

@@ -69,7 +80,13 @@ public function onDispatch(MvcEvent $event)
foreach ($routeMatch->getParams() as $key => $value) {
$psr7Request = $psr7Request->withAttribute($key, $value);
}
$return = $middleware($psr7Request, Psr7Response::fromZend($response));
$return = $pipe(
Copy link
Member

Choose a reason for hiding this comment

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

@asgrim So sorry I didn't notice this earlier, but... If we're pinning to Stratigility 2, we should likely use the http-interop process() method here instead of __invoke(), which means using a DelegateInterface instead of a callable.

May I suggest the following:

$pipe->setResponsePrototype($psr7ResponsePrototype);
$return = $pipe->process($psr7Request, new \Zend\Stratigility\Delegate\CallableDelegateDecorator(
    function (PsrServerRequestInterface $request, PsrResponseInterface $response) {
        throw ReachedFinalHandlerException::create();
    }
));

That would accomplish the same as what you have here, and be forwards compatible.

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, this seems reasonable, though I note that __invoke does the wrapping in the CallableDelegateDecorator for us, but I have no strong feelings either way. If __invoke is eventually going to go, then yes the switch makes sense!

setResponsePrototype is already called in createPipeFromSpec btw, so I don't think needs calling again (unless I missed something)

Copy link
Member

Choose a reason for hiding this comment

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

Ah, okay, nice.

As noted, using process() will be forwards compatible, meaning that when Stratigility adopts a finalized PSR-15, we likely can just add an || to the constraint and not need any actual code changes. I figure, do it once. 😄


namespace Zend\Mvc\Exception;

final class MiddlewareNotCallableException extends RuntimeException
Copy link
Member

Choose a reason for hiding this comment

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

Since we're allowing either callable or http-interop middleware, perhaps we should change this to InvalidMiddlewareException instead?

$pipe->setResponsePrototype($responsePrototype);
foreach ($middlewaresToBePiped as $middlewareToBePiped) {
if (null === $middlewareToBePiped) {
throw new \InvalidArgumentException('Middleware name cannot be null');
Copy link
Member

Choose a reason for hiding this comment

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

If we rename the exception to InvalidMiddlewareException, we could also add another named constructor: ::becauseNull() (obviously, choose something better!).

@weierophinney weierophinney dismissed Ocramius’s stale review May 1, 2017 15:58

Requested changes incorporated.

Copy link
Member

@weierophinney weierophinney left a comment

Choose a reason for hiding this comment

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

Per discussion in Slack, I'll perform the last requested changes now and merge.

Thanks, @asgrim!

…stener-to-use-stratigility-pipe

Modify middleware listener to use stratigility pipe
Also, adds a `fromNull()` method, allowing it to be used in cases where
a `null` value is provided for middleware. This means that `null`
middleware is handled exactly the same way as any other invalid
middleware type, which required changes to several test expectations.
Instead of `__invoke()`, for forwards compatibility once PSR-15 is
ratified.
@weierophinney weierophinney merged commit 6a4bf9f into zendframework:develop May 1, 2017
weierophinney added a commit that referenced this pull request May 1, 2017
weierophinney added a commit that referenced this pull request May 1, 2017
@weierophinney
Copy link
Member

Thanks, @asgrim

@asgrim asgrim deleted the modify-middleware-listener-to-use-stratigility-pipe branch May 1, 2017 16:40
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants