Using middleware for incremental request processing is getting increasingly popular, especially now that we have a common PSR-7 interface to support it. But it has brought out an important issue that made me rethink the very foundation of how requests are handled in what we used to call MVC.
A frequent problem that a lot of middleware users run into is passing some data along with the request to the next processor in the chain. All frameworks and even the PSR-7 use request attributes to achieve this, e.g. how routing parameters are stored in the request. This works great for simple things like strings, but it gets unweildy as soon as you try it with something more complex. For example I’ve see authentication middlewares that on successfull login put the user object into request attributes to pass along. This I feel totallly breaks the SRP, since an HTTP request should really have no idea about things like the user domain.
A quick solution for this would be to use a some sort of a Context object instead of a Request to pass along the middlewares. This Context would contain the HTTP request, authorization information, etc.
1
2
3
4
5
6
7
// Instead of this:
$id = $request->getAttribute('id');
$user = $request->getAttribute('user');
// We would have
$id = $context->httpRequest()->getAttribute('id');
$user = $context->auth()->user();
Such a context object encapsulates all the variable data that a certain middleware would require for processing. Actually this is very similar to how PHPixie ealready handles request related data, just taking it one step further.
Another important thing is that frameworks are used to passing requests to actions, or in some cases just the routing parameters, e.g.:
1
2
3
4
5
6
7
8
9
10
11
public function someAction($request)
{
}
//or
public function someAction($id)
// Where $id is a routing attribute
{
}
In those cases if a controller needs to access user authorization data, it has to get it from somewhere else, usually some authorization component:
1
2
3
4
5
public function someAction($id)
{
$user = $this->authService()->user();
//...
}
In this case the $user is actually a “hidden” parameter that is received from an outside scope. Now imagine we used a Context for it too, passing everything as a parameter:
1
2
3
4
5
public function someAction($context)
{
$id = $context->httpRequest()->getAttribute('id');
$user = $context->auth()->user();;
}
This would make unit testing so much easier, since all parameters are passed to the method, instead of relying on mocking internal services. This would also pave the way to transition PHP frameworks from being “web frameworks” to being “application frameworks” by making HTTP just an optional part of the context instead of the main core of processing logic.
The Context can be made pluggable, just look at these interfaces:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
interface AuthContext
{
public function user();
}
interface AuthContextContainer
{
/**
* @return AuthContext
*/
public function authContext();
}
interface HTTPContext
{
/**
* @return \Psr\Http\Message\RequestInterface
*/
public function request();
// maybe more methods for cookie handler etc
}
interface HTTPContextContainer
{
/**
* @return HTTPContext
*/
public function httpContext();
}
// the concrete Container
// declares which contexts it supports
// by implementing required interfaces
class Context implements AuthContextContainer, HTTPContextContainer
{
//...
}
Passing such contexts to middlewares and controllers would be a much better OOP approach to passing required parameters and would also work for non-HTTP environments.
Since I already have some experience implementing contexts in such a way for PHPixie, I was thinking perhaps there is some interest for a PSR for this. If so I would love to start working on it. Would really love to hear your thoughts on this.