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 php-fig+unsubscr...@googlegroups.com.
To post to this group, send email to php-fig@googlegroups.com.
To view this discussion on the web visit 
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.

Reply via email to