Context passing
I'm working on another "multi-tenant" PHP web application project and I noticed an interesting series of events. It felt like a natural progression and by means of a bit of dangerous induction, I'm posing the hypothesis that this is how things are just bound to happen in such projects.
In the beginning we start out with a framework that has some authentication functionality built-in. We can get the "current user" from the session, or from some other session-based object. We'll also need the "current company" (or the "current organization") of which the current user is a member.
In the web controller, we take this information out of the session (which we can possibly reach through the "request" object passed to the controller action as an argument). Then we start doing the work; making queries, making changes, sending mails, etc. All on behalf of the current user and the current organization. These valuable pieces of information are passed to basically any method, because everything has to happen in the context of the user/company.
Soon this starts to feel like a code smell known as a Data Clump: the same data hanging around together. This results in the first step in the eternal progression of events:
1. Injecting the session
Classes that need the current user, etc. will get the session injected, so they can take the relevant contextual information out of it to do their work.
It may be a bit optimistic to assume that the first solution that comes to mind is injecting the session. I'm afraid that many projects will have some global static method from which you can retrieve the context data.
Injecting the session is almost as bad as getting it from some globally available place. It couples every bit of your project to the framework, which is a bad place to be in (I'll write about it some other time).
Magic solutions
Talking about frameworks (and libraries) - they usually offer developers some magic alternative solutions. For example, Doctrine ORM has the concept of "filters", which can automatically modify queries to only return records owned by the current user, company, etc. Of course, these filter classes need to have access to the session too.
I really don't think that filters are a good solution for the problem at hand - the fact that the query gets modified behind the scenes is nowhere visible in the regular flow of your code, which is dangerous. I favor a more explicit approach here (see below: "Passing contextual values on a need-to-know basis").
Besides framework coupling, sessions will certainly be annoying to work with if at some point you'll feel the irrational urge to write tests for your code. At this point, you'll have to consider different solutions. Back to the Data Clump code smell:
If you always see the same data hanging around together, maybe it belongs together. Consider rolling the related data up into a larger class.
The suggested solution is to combine that data into a new class. This class often ends up being called...
2. The Context
class
It gets instantiated once, after the facts are known, and gets passed along from method to method so you'll always know where to look for the current user, company, etc.
As a solution, the Context
class may remind you of the concept of a value object (from Domain-Driven Design), to combine values that naturally belong together into a conceptual whole. However, there are several things about a Context
class that start to smell very quickly.
Entities in the Context
The first thing that ends up in the Context
class is the User
object, representing the authenticated user. Unfortunately, this user is often modeled as an entity. So now the Context
class will contain a complete entity. This way, we expose quite a large API of not only query methods, but also command methods, which would make it possible to modify the state of the User
entity that gets passed around. We don't want this kind of Indecent Exposure. Now every class that retrieves the Context
, also implicitly knows about the API of User
and sooner or later it will start depending on it. This kind of exposure needs to be prevented at all cost, because it basically couples everything to everything.
By
Truncated by Planet PHP, read more at the original (another 4553 bytes)