The implication that the controller itself is responsible for request and response is a particularly architectural choice. It's not a bad choice, but there are plenty of others.

For instance, in Drupal 8, the thing we call controllers (referenced by "_controller" in the request attributes) usually takes name-mapped parameters from the other request attributes (currently the same logic as Symfony, although we had a slight variant on the same idea before Symfony as well) and returns a render array, NOT a response. The render array is essentially a ViewModel, and there's other code elsewhere that turns it into a Response. (That lives in the Symfony View event in Drupal 8; it was hard-coded in Drupal 7.) It's very ADR-ish.

A controller can ask for the request if it wants, but most don't. A controller could return a Response object if it wants, but most don't. A controller that both takes the request object directly and returns a Response directly is in practice an extreme edge case. There's extra translation layers both incoming and outgoing that are a key part of the developer experience.

Now, you could argue that what you're describing here isn't a controller as Drupal defines it, but rather the "bottom middleware", which you're then calling a controller. The term "controller" has at least 14 definitions, even just within the world of routing, IME. :-) That makes it a poor name choice, at the very least.

OK, so next step would be to define it as a "bottom middleware" explicitly. And a given implementation could very easily then contain the pre- and post-processing I describe above, and internally call the for-reals controller (or action, or whatever you want to call it). Certainly true. Drupal would then have only a single "controller" (bottom middleware). However, I then don't see what the advantage is over having a middleware implementation that ignores the $next parameter entirely and just does... the exact same thing. There's little additional value to a separate "bottom middleware" interface that I can see.

--Larry Garfield

On 01/24/2017 01:14 PM, Rasmus Schultz wrote:
> While I can certainly see the value in the architecture this would imply, it's by no means the only architecture

I disagree that this implies architecture.

"takes a request and returns a response" is in my opinion the least opinionated, most open-ended definition you can have of a controller - in my opinion, it does not imply architecture, at all. You're completely free to use traits, composition, base-classes, external services, registries, dependency-injection, middleware - anything you can dream up, the only requirement is that you ultimately take a PSR-7 request and produce a response, which, ultimately, if you're using PSR-7, you're going to, one way or the other.

Are you commenting on the controller-interface itself or are you more opposed to the idea of going as far as defining filters?

I agree that maybe the filter concept is a bit more opinionated, but this would of course be optional - that's why it's a separate interface.

> Many controller systems I've seen (including Drupal's, even before Symfony) used one or another means of automatically extracting information from the request to pass into the controller; loaded domain objects based on the URL, for instance

Interestingly, that was our first approach - it's perfectly possible with this interface signature, and moving from our initial controller base-class to a new one was painless. We can port one controller at a time from one strategy to another, because no matter the internal architecture of each controller, or how we wire them up in our stack, they only ultimately need to have one thing in common: they take a request and return a response.

>it's just one viable option among many that differ in more than just incidentals

Can you name an example?

If you're building a PSR-7 application, no matter the architecture, taking a PSR-7 request and returning a response is ultimately what any PSR-7 application does, regardless of how it does that.

If you're building an HTTP application, no matter the architecture, taking a request and returning a response is ultimately what any application does, regardless of how it does it.

Besides mandating the use of PSR-7 models, how do you feel that this interface implies more architecture than the HTTP message exchange itself?

In my opinion, "takes a request and returns a response" is the broadest definition you can give and still imply that it's even an HTTP application.

Am I missing something?


On Tue, Jan 24, 2017 at 7:47 PM, Larry Garfield <[email protected] <mailto:[email protected]>> wrote:

    This strikes me as drifting too far into the "defining a
    framework" territory.  While I can certainly see the value in the
    architecture this would imply, it's by no means the only
    architecture.  Many controller systems I've seen (including
    Drupal's, even before Symfony) used one or another means of
    automatically extracting information from the request to pass into
    the controller; loaded domain objects based on the URL, for
    instance.  That creates a shallower and IMO better signature for
    individual controllers, but would be incompatible with the design
    below.

    It's not that the model described here is bad; it's quite
    powerful.  But it's just one viable option among many that differ
    in more than just incidentals, and so I don't think a PSR (which
    would have the effect of pushing everyone toward this one model,
    which is the point of a PSR) is appropriate in this case.

    --Larry Garfield


    On 01/24/2017 10:45 AM, Rasmus Schultz wrote:
    Any interest in a simple PSR for Controllers?

    What I'm suggesting here is something that is extremely abstract
    and generic - something that builds upon PSR-7.

    The interface itself could be as simple as this:

    interface ControllerInterface
    {
        public function dispatch(ServerRequestInterface $request):
    ResponseInterface;
    }

    To contrast this with the middleware-interface of PSR-15,
    "middleware" is a component that shares control with other
    middleware-components, e.g. *may* do something to a request, or
    might just pass - as opposed to a "controller", which is a
    component that *must* process the request and create a response,
    so this would be something to which routing (of any kind) has
    determined that, for the given request, this *is* the component
    responsible for processing.

    This would work for front-controllers (such as a middleware-stack
    based on PSR-15) as well as for any other types
    front-controllers, e.g. anything you might use in a catch-all
    "index.php" as the top layer of request routing.

    It would work for any kind of action-controller as well, e.g.
    using abstract base-classes to implement specific
    dispatch-strategies, and assuming any other dependencies would be
    provided via dependency-injection - which would be outside the
    scope of this PSR, but you can imagine abstract base-classes
    implementing different dispatch-strategies, such as dynamically
    mapping GET or POST params against argument-names of a run()
    method, providing integration with a DI container, etc.

    We currently use such a strategy and an identical
    ControllerInterface at work, and it's been a real success. Our
    default base-class, for example, detects a JSON object body being
    posted, decodes it and maps object-properties against arguments -
    and for form-posts, it checks for scalar type-hints of the
    run-method and performs int, float and bool conversions, array
    and string type-checks, etc.

    We enjoy the security of being able to replace our
    controller-pattern completely without breaking compatibility with
    existing controllers, and the freedom of being able to implement
    highly specialized controllers (such as a controller that resizes
    images) without using a base-class at all.

    This pattern and interface makes any controller-implementation
    compatible with any router capable of resolving a request to a
    ControllerInterface instance, or perhaps a class-name for
    integration with a DI container. (In our stack, that means the
    router is responsible solely for determining a class-name - we
    use a class-per-action pattern, but a router could of course also
    resolve to an action-method name and provide that to a
    controller-implementation via constructor-injection - as with
    most patterns this simple, your imagination seems to be the limit.)

    Basically any router that can resolve a path to a string and
    HTTP-method can interop with this - micro-frameworks that go
    beyond just resolving the request to a value (e.g. creates or
    runs controllers) would likely be able to interop with this in
    other ways.

    This is one part of the story.

    The other part is about filters - often there is a need to secure
    a controller, accept or reject certain content-types, apply
    caching, or do some other form of filtering before/after actually
    running the controller.

    That sounds a lot like middleware - in fact, a lot of middleware
    components would be immediately useful if a controller could
    simply apply them as filters. So there is no need to invent a new
    concept here, PSR-15 would do the job, we only need to define how
    filters get created.

    The following simple interface would do that:

    interface FilterInterface
    {
        /**
         * @return ServerMiddlewareInterface[]
         */
        public function getFilters(): array;
    }

    This could be part of the same PSR or separate.

    Now a controller can (optionally) implement this and use it to
    declare controller-specific middleware as filters - e.g. return
    [new CacheMiddleware(), new PostFilter()] might apply some
    caching-headers and a POST-method restriction.

    Whatever is returned by this method gets run before the
    controller itself is dispatched - in other words, to the last
    middleware-component (PostFilter in this example) the delegate
    that gets passed does not delegate to a middleware-component but
    to the controller itself.

    How precisely the filter-middleware gets dispatched is outside
    the scope of this PSR - it's up to the controller base-class or
    framework, wherever you choose to place this responsibility. An
    abstract controller base-class could support FilterInterface
    internally, or it could be done in a router, middleware or
    micro-framework.

    We haven't attempted the filter pattern in our own stack yet, so
    I can't say for sure if this would work out as dreamy as I think
    it would - but I think we eventually will try it, as there's a
    very real need and many practical use-cases.

    As for the controller pattern, it's sort of a no-brainer - it's
    totally trivial, and it just works.

    Well, I figured I'd put the idea out there, as it has already had
    great value for us, in terms of decoupling and separating
    concerns like routing and middleware from controllers and
    dispatch-strategies.

    I figure it's worth sharing the idea :-)

    Thoughts?

    - Rasmus

-- You received this message because you are subscribed to the
    Google Groups "PHP Framework Interoperability Group" group.
    To unsubscribe from this group and stop receiving emails from it,
    send an email to [email protected]
    <mailto:[email protected]>.
    To post to this group, send email to [email protected]
    <mailto:[email protected]>.
    To view this discussion on the web visit
    
https://groups.google.com/d/msgid/php-fig/b6c0ab24-9030-4f0f-aaca-facdfecc9f47%40googlegroups.com
    
<https://groups.google.com/d/msgid/php-fig/b6c0ab24-9030-4f0f-aaca-facdfecc9f47%40googlegroups.com>.
    For more options, visit https://groups.google.com/d/optout
    <https://groups.google.com/d/optout>.

-- You received this message because you are subscribed to a topic in
    the Google Groups "PHP Framework Interoperability Group" group.
    To unsubscribe from this topic, visit
    https://groups.google.com/d/topic/php-fig/HD5meon7TX0/unsubscribe
    <https://groups.google.com/d/topic/php-fig/HD5meon7TX0/unsubscribe>.
    To unsubscribe from this group and all its topics, send an email
    to [email protected]
    <mailto:[email protected]>.
    To post to this group, send email to [email protected]
    <mailto:[email protected]>.
    To view this discussion on the web visit
    
https://groups.google.com/d/msgid/php-fig/da0de433-f1e4-d534-530c-9d4ae103e42f%40garfieldtech.com
    
<https://groups.google.com/d/msgid/php-fig/da0de433-f1e4-d534-530c-9d4ae103e42f%40garfieldtech.com?utm_medium=email&utm_source=footer>.


    For more options, visit https://groups.google.com/d/optout
    <https://groups.google.com/d/optout>.


--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected] <mailto:[email protected]>. To post to this group, send email to [email protected] <mailto:[email protected]>. To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/CADqTB_jFW99UzAEbsh6SZ3NySJ35uj5tQ%2B%3DzeYSkvH%2Bu3asAbg%40mail.gmail.com <https://groups.google.com/d/msgid/php-fig/CADqTB_jFW99UzAEbsh6SZ3NySJ35uj5tQ%2B%3DzeYSkvH%2Bu3asAbg%40mail.gmail.com?utm_medium=email&utm_source=footer>.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "PHP 
Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/php-fig/da2f210d-5c2f-6226-14c4-919dfd24b662%40garfieldtech.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to