Using the Fluent Interface Design Pattern to Make Code more Concise and Readable

Published:
Updated:
The Fluent Interface Design Pattern

You can use the Fluent Interface design pattern to make your PHP code easier to read and maintain.  "Fluent Interface" is an object-oriented design pattern that resembles message handling, where a message is passed from program to program, with each program adding a bit of value to the message.  It was first given its name about a decade ago by Martin Fowler, and is now in wide use in the PHP world, considered a "best practice" by industry leaders.  Fluent design allows us to do rapid application development with Laravel, and facilitates formalized automated application testing with PHPUnit.  One of the defining principles of the design pattern is code that is "primarily designed to be readable and to flow."  One of the most delightful effects of using a Fluent Interface design is the elegant simplicity of method chaining.

Before we see a Fluent Interface in practice, let's look at some programming activities we have all encountered before, and the steps (and missteps) we have used to solve our problems.  A good example is the Address Book or Contact Directory.  When we start writing this application, we think, "Well, I need names and phone numbers."  And that is fine for starters, but right after you get it all set up, the boss comes in.  "Don't forget that you also need email addresses."  

OK, no problem, we can add that field to our data model.

Here's what an original data model might look like (in prototype):
<?php
                      $name = 'Ray';
                      $phone = '703.555.1212';
                      $email = 'ray@gmail.com';

Open in new window


Of course, we will need more than one name in our directory.
<?php
                      $name1 = 'Ray';
                      $phone1 = '703.555.1212';
                      $email1 = 'ray@gmail.com';
                      
                      $name2 = 'Toby';
                      $phone2 = '703.555.1212';
                      $email2 = 'toby@gmail.com';
                      
                      $name3 = 'Lucky';
                      // NOTICE THAT THERE IS NO PHONE #3
                      $email3 = 'lucky@gmail.com';

Open in new window


Wow, that is going to get unwieldy fast!  Any time you have a proliferation of variables in your code, you know something is wrong.  Usually the first bit of reorganization brings us to an array-related solution.

<?php
                      $names = [ 'Ray', 'Toby', 'Lucky' ];
                      $phones = [ '703.555.1212', '703.555.1212' ];
                      $emails = [ 'ray@gmail.com', 'toby@gmail.com', 'lucky@gmail.com' ];

Open in new window


Now if we want to look up the information, we can just assign a number to our search criterion and take that numeric position in each of the arrays.  This works, sort-of.  Where it breaks down is in the phones - we have a missing phone number.  And that means we cannot readily add data to the end of these arrays, because we risk putting the data into the wrong "columns" for want of a better term.  We need to reorganize, and to that end we start thinking about the "objects" of information in our Contact Directory.

Each object represents a person we might want to contact.  We might represent them with a PHP class like this.
<?php
                      Class Person
                      {
                          public $name, $phone, $email;
                      }

Open in new window


Using that data model for one person, we can easily see that our contact directory can be organized into an array or a database table that will hold an unlimited number of independent data objects.  And since each person is represented by a uniquely isolated data structure, we don't have to worry about partial or missing data - errors and omissions in one object do not affect the validity of other objects.

This is a very simple example, but if you started adding more data elements to the data model you would find that your collection of properties could grow quite large.  And every time more data elements were added, you would have to consider ways to keep the existing data models up to date with the newer, more fully completed models.

Let's examine how an ordinary program might look.  If we wanted to create a new Person object we might expect to do it like this:
<?php
                      $person = new Person;
                      $name   = 'Ray';
                      $phone  = '703.555.1212';
                      $email  = 'ray@gmail.com';
                      $person->setName($name);
                      $person->setPhone($phone);
                      $person->setEmail($email);
                      $per_id = $person->save();

Open in new window


We can do that, but it's several lines of code when many fewer lines could do the same thing.  And with fewer lines we would have a more readable way of expressing the same idea.  In a fluent interface we might write something like this.  Notice how easily it reads from left to right:

<?php
                      $person = new Person;
                      $person->name('Ray')->phone('703.555.1212')->email('ray@gmail.com')->save();

Open in new window


There are two important concepts (and one simple concept) in play when we try to write fluent code. 

The first concept is method chaining.  While there are more complex ways to do this, our example takes advantage of the simplest case.  When our method is complete, instead of returning some isolated data element, we simply return the entire object.  This means that the object is implicitly available to each new method call.   So instead of having to write many lines of code, we can put all of our method calls into a single statement.   They can be chained together because each method receives the object that was returned from the previous method.  Almost magic!

The second concept is the magic method.   Whenever a PHP class method is called and the method does not exist on the class, PHP will raise a fatal error.  But before that error occurs, PHP will first substitute the name __call() for the missing method. We can write our code to take advantage of this automatic method substitution.  Again, almost magic!

There is a third concept in play here, too.  We want to know what properties exist (since these are almost certainly column names in a database table).  Our class constructor creates these properties and our magic method __call() checks that the properties exist and throws an error or exception if they do not exist.  This prevents the accidental injection of unwanted properties into our object, and in turn prevents a database error.

Here is a line-by-line annotation of the code sample below:

Line 11: The class constructor furnishes the names of the properties we can inject; this is a whitelist (line 14)
Line 23: A stub function (placeholder) for what will eventually become our data base update
Line 37: Our magic method that updates the property of the same name, subject to whitelist filtering
Line 43: Our return statement simply returns the entire object
<?php // demo/magic_call.php
                      error_reporting(E_ALL);
                      
                      Class Person
                      {
                          /**
                           * The constructor for the class - assigns the properties from the data model
                           * @param  none
                           * @return none
                           */
                          public function __Construct()
                          {
                              // STUB FUNCTION: SIMULATE GETTING THE STRUCTURE OF THE DATA MODEL
                              $this->name = $this->phone = $this->email = NULL;
                          }
                      
                          /**
                           * Save the data model by inserting or replacing its row in the database
                           *
                           * @param  none
                           * @return id  The id of the row in the database
                           */
                          public function save()
                          {
                              // STUB FUNCTION: SIMULATE SAVING THE MODEL
                              return 1;
                          }
                      
                          /**
                           * Magic Setter for any Object Property
                           *
                           * @see    http://php.net/manual/en/language.oop5.overloading.php
                           * @param  $argname  The property to be set (must exist)
                           * @return $this  The existing object returns itself
                           * @error  E_USER_ERROR if the named property does not exist
                           */
                          public function __call($argname, array $arglist)
                          {
                              if ( ! property_exists($this, $argname) )
                                  trigger_error("Property <b><i>$argname</i></b> is undefined on the " . __CLASS__ . " data model", E_USER_ERROR);
                      
                              $this->$argname = $arglist[0];
                              return $this;
                          }
                      }
                      
                      
                      // DEMONSTRATE
                      $p  = new Person;
                      $id = $p->name('Ray')->phone('703')->email('EE')->save();
                      
                      // SHOW THE DATA STRUCTURES
                      var_dump($id, $p);

Open in new window


And that's it -- a simple design pattern that makes for compact, readable code. 

There are arguments both for and against this kind of code.  One of the "against" arguments suggests that you have to know the structure of the class to understand what methods can be called on the objects and in what order these methods can be called (see Demeter Chains).  I respect the argument, and in my experience as a professional programmer, I find that you cannot simultaneously understand a system and avoid the knowledge that the Demeter Law says you should avoid.  So I willingly choose compact readable code.

Here are some references and further reading to help complete the picture:
http://martinfowler.com/bliki/FluentInterface.html
http://stackoverflow.com/a/17940086/1907917
http://go.aopphp.com/blog/2013/03/19/implementing-fluent-interface-pattern-in-php/
http://c2.com/cgi/wiki?LawOfDemeter
http://docs.mockery.io/en/latest/reference/demeter_chains.html

Just for fun, here's a contrasting point of view from a fellow that says the Fluent Interface rustles his jimmies.

Please give us your feedback!
If you found this article helpful, please click the "thumb's up" button below. Doing so lets the E-E community know what is valuable for E-E members and helps provide direction for future articles.  If you have questions or comments, please add them.  Thanks!
 
4
3,537 Views

Comments (1)

CERTIFIED EXPERT

Commented:
Personally I'm not bothered about the syntactic sugar a fluent interface brings (as per the SO question you linked to - this example is really method chaining rather than fluency as other than save() it has no semantic value). While I agree that this makes some code very readable (it's a great fit for SQL query building), it has big implications for error handling. Because you never get an opportunity to return an error (you always have to return $this), the only way to handle errors is to throw exceptions (trigger_error amounts to the same thing), so your function call chain needs a potentially large number of exception handlers, this sort of thing:

try {
$person->name('Ray')->phone('703.555.1212')->email('ray@gmail.com')->save();
} catch (invalidNameException $e) {
} catch (invalidPhoneException $e) {
} catch (invalidEmailException $e) {
} catch (saveFailedException $e) {
} catch (Exception $e) {
}

Open in new window


While this retains the interface, the error-handling path gets disconnected from the flow. Also note that once you've thrown an error, there's no way of getting back into the call chain to fix the problem and continue - you have to start again.

There is a ton of debate on exceptions vs return values, but broad exceptions are great way of handling large-scale problems, for example your database connection breaking when you're busy thinking about something else.

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.