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?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/da0de433-f1e4-d534-530c-9d4ae103e42f%40garfieldtech.com.
For more options, visit https://groups.google.com/d/optout.