Data, *Context*, Interaction - the DCI design pattern in PHP 5.4

16 Aug 2013

Thanks to closure object binding in PHP 5.4, we now have the ability to easily (and elegantly) implement the DCI design pattern for our classes and objects. This opens up a whole new world of code management and readability, helping us PHP developers to write more maintainable code for large projects.

So to get started, let’s first cover DCI in layman’s terms. What it is, and why would we want to use it?

DCI stands for Data, Context, Interaction and is a concept and design pattern by Trygve Reenskaug, whom is also the architect of the MVC design pattern we’ve all come to know and love. In short, this design pattern allows us to separate our data objects, from use cases and roles. If you’re interested in the theory and some of the more technical aspects, read this. In this article however, we’re going to focus on what that means for our applications in the context of an MVC framework (such as Laravel).

In larger applications DCI can help us split our models into more manageable chunks. It just so happens that I’ve implemented the DCI pattern for PHP 5.4+. Called Fatty, it’s a Laravel-esque library that you can install using composer. In your project, add the following to your composer.json file and type “composer install” (without the quotes).

1 {
2     "require": {
3         "kirkbushell/fatty", "*"
4     }
5 }

With the package installed, we now have a new class to play with: Context. To explain in detail and demonstrate just what Fatty can do, we’re going to start with a Vehicle model, and we’re going to extend this with the relevant methods for different types of vehicles at runtime. Let’s look at that now and create our model.

 1 class Vehicle extends \Eloquent
 2 {
 3   use \KirkBushell\Fatty\Context;
 4 
 5   private $direction = 'forward';
 6 
 7   private $status = 'stopped';
 8 
 9   protected function drive( $direction ) {
10     $this->direction = $direction;
11     $this->status = 'moving';
12   }
13 
14   public function reverse() {
15     $this->drive( 'backward' );
16   }
17 
18   public function state() {
19     return $this->status;
20   }
21 }

This is a simple class - but I want to demonstrate some things we can do with our model, and extending our Vehicle object at runtime. I also want to show what happens with class visibility when you’re using this new feature in PHP.

As you can see in our Vehicle model, we’ve defined a few basic functions such as the drive method. These are simple methods, and actions I think we can all agree most vehicles would undertake. This functionality is neither here nor there, but I feel it’s important to show a full working model, as trivial as it may be.

Of course, there are other kinds of vehicles that would probably have additional functionality - and this is where the pattern gets really interesting.

DCI is intended as a design pattern that can also make our model objects lighter and more modular. Another feature of this pattern is that it allows us to morph our objects in ways that match our use cases. For example, perhaps we have a variety of different vehicles in our database, such as bikes, cars, trucks, buses and the like. We can still use this model we’ve setup (why would we waste it), but instead of adding all these role-specific methods to the class, we can do something better (and smarter). We can create contexts.

In order to do this, we’ll create some new classes (I’ll leave the loading of those classes to you) that represent some of these types.

 1 class  Motorbike
 2 {
 3   public function lean() {
 4     return function() {
 5       $this->status = 'stopped';
 6     };
 7   }
 8 }
 9 
10 class Truck
11 {
12   public function load() {
13     return function( $contents ) {
14       echo $contents;
15     };
16   }
17 }
18 
19 class Sedan
20 {
21   public function packGroceries() ..
22 }

I’ve omitted some of the details, but there’s a few things that probably stand out to you immediately. One is the fact that we’re defining anonymous functions that are being returned by those methods, and you can see in the Truck class that our method’s only argument is actually on the anonymous function - what the hell?

Internally the Context trait that we’re including in our class is doing a little magic. These additional classes we’re building and their methods, when they’re called it needs to act and feel like we’re dealing with the Vehicle model directly and not some pseudo class. That’s what the Context trait does for us. And what’s more, is it works beautifully.

Let’s start working with them - I’ll show you how you can create Vehicle objects and require the necessary contexts for each individual case. Remember that in these examples I’m working with Eloquent - Laravel’s ORM, but this can be applied to any object that implements Context.

1 $vehicle = Vehicle::where_type( 'motorbike' )->first();

We should remember at this point that there is no actual motorbike-specific logic - so let’s apply it.

1 $vehicle->extend( 'Motorbike' );
2 $vehicle->lean();
3 
4 echo $vehicle->state(); // stopped

Now this is really cool. When we need to, we’ve extended the Vehicle class with the Motorbike class. Effectively, our Vehicle is now a Motorbike with all the trimmings. But what about when our methods require arguments? It’s not much more complex. Let’s instead attach our Truck class.

1 $vehicle->extend( 'Truck' );
2 $vehicle->load( 'peanuts' ); // echoes peanuts
3 $vehicle->reverse(); 

We’re at a point now where our models are truly extensible (thank you 5.4!!!) - and more modular. You don’t have to use the DCI pattern to only represent different contexts or roles for our models. You could use it just to make your models smaller - thereby making your code easier to read and maintain - or any class, for that matter.

Even though I’ve used Eloquent models as a basis for this pattern in this example, you can use Fatty in any class you feel could benefit from the implementation of the DCI pattern - and in fact, I would advise you to do so, where it makes sense.

Limitations

Despite how useful this method can be in PHP, there are some caveats.

For one, you cannot define methods on your classes that exist on the Data class (in this example, Vehicle). It won’t cause any errors, but Context only gets called if the method you’re calling on the data class doesn’t already exist. In this case, overloading is simply not possible.

Secondly, because we’re using closures inside our method calls to bind object scopes - there is no way for our contexts to have their own internal functionality unless all properties and methods are defined with public visibility. We have to pass the context object to our closure via the use() syntax, meaning we’re outside of the internal class scope when we deal with the object. This can and may be an issue for architectural fiends (like myself). Let me give you an example.

 1 class Truck
 2 {
 3   public function load() {
 4     $that = $this;
 5     $this->unload(); // fine
 6 
 7     return function( $contents ) use ( $that ) {
 8       $that->unload(); // Fatal error
 9     };
10   }
11 
12   private function unload() {
13     echo 'unloading...';
14   }
15 }

As you can see - we can call the unload method normally from within our load method, but not from inside the closure that gets returned by the load method. So there is some cool things you could do prior to the closure being called, but unfortunately the closure itself has its own scope - and because we’re binding the closure to the Vehicle object itself, it does represent some challenges in working with the Context providing the interactions.

I hope this article has demonstrated just how powerful this method can be, especially for larger applications. Feel free to install Fatty on any of your projects and let me know how it works out for you!

As per usual - comments feedback and criticism welcome!

comments powered by Disqus