slimphp / Slim

Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs.
http://slimframework.com
MIT License
11.94k stars 1.95k forks source link

[v3] Middleware: Before and After #1281

Closed designermonkey closed 9 years ago

designermonkey commented 9 years ago

I don't know if anyone has brought this up before, or if I'm missing something fundamental about how middleware operates, has anyone considered before and after scenarios?

What I mean is middleware that runs before the router, and middleware that runs after, so in essence, two separate stacks. This would make the app have in total three middleware stacks (including the route).

How do people deal with this normally?

lalop commented 9 years ago

I think this point has been discussed here https://github.com/slimphp/Slim/pull/1264

designermonkey commented 9 years ago

Thanks for the pointer @lalop, it's kind of what I mean...

The middleware I would want implemented would be a standard type to avoid the attachment to one specific framework, but what I mean is to have more than one queue to make it so that I can ensure a specific outcome after a route has dispatched.

geggleto commented 9 years ago

Before and after... @designermonkey https://gist.github.com/geggleto/7249ed53920c8b1618d3

Or am I not understanding what you mean.

designermonkey commented 9 years ago

No, I mean two stacks that Slim runs. So I add middleware to a stack that I know runs before the router, and I add middleware to a different stack that I know runs after the router. The middleware is all the same format, I am talking about having two distinct stacks that I can add to.

// From the App::run method

        // Traverse middleware stack
        try {
            $response = $this->callMiddlewareStack($request, $response);
        } catch (SlimException $e) {
            $response = $e->getResponse();
        } catch (Exception $e) {
            $errorHandler = $this->container->get('errorHandler');
            $response = $errorHandler($request, $response, $e);
        }

So the app tries to run the middleware stack, and the router (via App::__invoke middleware) is the last item to run. I am talking about another stack of middleware to run after that first stack:

        // Traverse middleware stacks
        try {
            $response = $this->callBeforeMiddlewareStack($request, $response);
            $response = $this->callAfterMiddlewareStack($request, $response);
        } catch (SlimException $e) {
            $response = $e->getResponse();
        } catch (Exception $e) {
            $errorHandler = $this->container->get('errorHandler');
            $response = $errorHandler($request, $response, $e);
        }
geggleto commented 9 years ago

What do you mean by after the router exactly.

Are you talking about middleware that runs after the route callable has been called ?

JoeBengalen commented 9 years ago

How do you want to use the second stack? Can you not just do it like this?

$middleware = function ($request, $response, $next) {
  // before
  $newResponse = $next($request, $response);
  // after
  return $newResponse;
};

Or do you want the second stack so you can first call the first global stack, then the router stack and finally the second global stack?

geggleto commented 9 years ago

I think this sort of thing is already possible and I don't think you need another stack. Granted the details are in how you write the middleware so it is not very readable. Ordering I think goes the same way, Last in first executed. So even if you intermixed before and after middlewares it executes as intended.

//Before
$middleware = function ($request, $response, $next) {
  //Do Stuff here
  $newResponse = $next($request, $response);
  return $newResponse;
};

//after
$middleware = function ($request, $response, $next) {
  $newResponse = $next($request, $response);
  // after
  // do stuff here
  return $newResponse;
};
codeguy commented 9 years ago

-1. You already have the ability to run code before and after the application.

designermonkey commented 9 years ago

@codeguy can you explain how then? How do I modify the response after the router has run?

codeguy commented 9 years ago

Each middleware, when invoked, is provided a Request object, a Response object, and a reference to the next middleware. The middleware may manipulate the Request and Response objects before and after it invokes the next middleware.

    function (Request $req, Response $res, callable $next) {
        // Add attribute BEFORE next middleware
        $req = $req->withAttribute('foo', 'bar');

        // Invoke next middleware and fetch response
        $res = $next($req, $res);

        // Update response AFTER middleware
        $res = $res->withStatus(400);

        // Return a response to upstream middleware
        return $res;
    }
designermonkey commented 9 years ago

Yes, I get that, but as the Slim router is the last middleware to be queued up in the stack, I cannot manipulate the response object after the router has called a route and run it's callable.

Ahhhhh, lightbulb moment. I see. Idiot is leaving the discussion now ;P

codeguy commented 9 years ago

:+1: