The Terrible, Horrible, No-Good, Very-Bad Things I Do to PHP

Table of Contents

The Terrible, Horrible, No-Good, Very-Bad Things I Do to PHP    titlecard slide

Iowa Code Camp, November 2014

By M.J. Hoag

http://talks.kepibu.org/2014/icc-php/

Script   notes

Hi! I'm M.J., and I happen to be quite fond of Common Lisp. So, naturally, I'm here to talk about PHP and the, uh, "interesting" things I do to it.

In This Talk   slide

  • Prerequisites
  • Underlying Motivation
  • Typehint Shenanigans
    • Non-class typehints
    • Automatic conversion via typehints
  • Composable Error Handlers
  • Method Combinations
  • Context-Oriented Programming
  • Self-Populating Meta Templates

Script   notes

We're going to cover a number of topics in this talk, broken into sections of about one per topic. You might actually prefer to think of it as a series of lightning talks all by the same guy.

It's only fair to warn you that some of this stuff has been in my head for a very long time, so if I'm not managing to get it out in a way you understand, feel free to ask questions as we go. I'll also stop for questions in between each major section of this talk, so you'll have a chance to ask about things before we jump to a completely different topic. Like I said, this is really a lot of little talks all mushed into one.

Prerequisites   slide

  • Typehints
  • Errors vs. Exceptions
  • Traits
  • Our Basic Architecture

Script   notes

How many people here have used PHP before?

Some, not all.
Okay. I do assume a passing familiarity with some of the PHP things I talk about, so let's go ahead and cover some of the basics really briefly for those who aren't as familiar with PHP.
Really, every last one of you?
Great! Then the prereqs should go really quickly.

PHP Typehints: A Quick Overview   slide

  • Typehint specified before parameter name
  • Names a class or instance
  • $arg must be an instanceof that class, or will error
function f(TypeHint $arg) {
   // ...
}

roughly equivalent to…

function f($arg) {
   if (!($arg instanceof TypeHint)) {
      trigger_error(...);
   }
   // ...
}

Script   notes

A typehint in PHP is specified in the argument list before a parameter, and it names a class that the passed-in parameter must be an instance of. Passing in a value which is not an instance of that class will result in an error.

So this first snippet is very roughly equivalent to this second, where we check if $arg is an instance of TypeHint and error if not. They're really more assertions than hints.

PHP Errors v. Exceptions: A Quick Overview   slide

  • PHP has two different ways of dealing with problems
  • Errors and Exceptions
  • Throwing an exception unwinds the stack to an appropriate catch
    • Failure to catch terminates program
    • Exception handler runs at top level (after stack unwinds)
  • Errors log message, terminate program
    • Error handler runs in place (leaves stack alone)
    • return true ignores error

Script   notes

PHP has two different mechanisms for dealing with problems. There's the original error mechanism—used for things like compiler errors, syntax errors, typehint errors, and so forth. By default, an error logs a string and terminates the program. But you can register a function to handle errors, and if you've done so, that handler will be run for any error instead of the default behavior. Which means if using your own error handler, whether to halt execution is up to you, so you have the power of "on error resume next" at your fingertips.

And there's the newer exception mechanism, which works pretty much like exceptions in any other language, where throwing an exception unwinds the stack until it hits an appropriate catch block. Failure to catch an exception ends your program.

Traits: A Quick Introduction   slide

  • "Multiple inheritance is haaaaaaard"
  • But everybody wants it, because it's useful
  • So… automated copy-and-paste
  • Share implementation across multiple classes

Script   notes

Who here has heard of traits before, either in PHP or any other language?

For those of you who haven't, don't feel bad, I'd never heard of them either until PHP introduced them and until I actually looked them up on Wikipedia I just assumed it was the PHP crowd reinventing a lumpy wheel. Turns out they're actually a thing!

Basically, a trait is a way to automatically copy-and-paste the implementation of something across multiple classes.

Traits: A Quick Example   slide

trait Singleton {
   private static $_instance = null;

   public static function getInstance() {
      if (is_null(self::$_instance)) {
         self::$_instance = new self();
      }

      return self::$_instance;
   }
}

class FrontController {
   use Singleton;
}

class WebSite extends SomeClass {
   use Singleton;
}

Script   notes

To shamelessly steal an example from Wikipedia, take the concept of a Singleton. The way you implement the singleton pattern tends to be identical from one class to the next. Traits allow you to abstract that out, so you only have to write it once, after which you can have the compiler copy-and-paste the code into your Singleton classes.

If you're familiar with mixins from a multiple-inheritance language, traits are basically just mixins bolted on to a single-inheritance language.

Basic Architecture   slide

app-1.png

WebUI
POST …/controller/method arg1=val&arg2=val2
MobileAPI
POST/PUT /endpoint.php {"arg1":"val","arg2":"val"}

Script   notes

Our basic architecture is fairly straightforward. Models sit in the middle controlling changes to the underlying data store. From there, we branch out into WebUI controllers, which send and receive data to and from the browser using URLs named for the controller class and the method in that class we'll be calling. The models also feed into MobileAPI endpoints, which sync all a user's data to and from the mobiles so it's available for offline use.

Questions?   slide

flamingo.png

Script   notes

Is that sufficiently clear for everyone?

Okay, good. Now that the prerequisites are out of the way, …

Let Us Begin!   slide

jackson-popcorn.gif

Script   notes

… let's get started!

Underlying Motivation   slide

  • Correct and secure code should be easier to write than incorrect or insecure code.
  • It is my responsibility as a library or language author to make this possible.
  • It is more important for the interface to be simple than the implementation.
  • Corollary: It is better for my replacement to hate me than for everyone who merely uses my code to hate me.

Script   notes

If you think I'm crazy by the end of this, well, I can't entirely disagree. But everything I do is guided by a few fundamental thoughts.

  • Correct and secure code should be easier to write than incorrect or insecure code.
  • It is my responsibility as a library or language author to make this possible.
  • It is more important for the interface to be simple than the implementation
  • Corollary: It is better for my replacement to hate me than for everyone who merely uses my code to hate me.

If you're familiar with Richard Gabriel's "Worse-is-Better" essay, I tend to err more on the MIT Approach side. Basically, it's my job to make everybody else's job easier.

And because I take that mindset, I sometimes do things that other people might consider insane.

Typehint Conversion Example   slide

class DateController extends Controller {
   function f(DateTime $date) {
      var_dump($date);
   }
}

$controller->f("2014-11-01");
// => class DateTime#1 (3) { ... }
POST /api/unstable/date/f
date=2014-11-01

Script   notes

Take this, for example. It takes a specially-formatted string from a form POST or JavaScript and converts it into a DateTime object, or if the string is formatted incorrectly, it'll throw an exception.

Non-class Typehint Example   slide

class IntController extends Controller {
   function f(row_id $id) {
      var_dump($id);
   }
}

$controller->f("123");
// => int(123)
POST /api/unstable/int/f
id=123

Script   notes

Or this, which takes a value from a form POST, ensures the string represents an integer greater than zero, and if it does gives me an actual int inside the function. If it doesn't, again, it'll throw an exception.

But those are the end result. Getting to this point was something of an iterative process, and like most stories, it starts out innocently enough.

In The Beginning   slide

class OldController extends Controller {
   function f() {
      if (!$this->required(['a', 'b', 'c'])) {
         return;
      }
      if (!$this->numeric(['a'])) {
         return;
      }
      // ...
   }
}
POST /router.php
controller=old&method=f&a=1&b=bar&c=baz...

Script   notes

When I started at BettrLife, this is what a controller method looked like. Kinda ugly, error-prone, a pain to write, and a pain to use. I mean, To even know what arguments you can pass to this method, you have to read through the entire method. It doesn't look terribly painful here, but that ellipsis I'm using for brevity hides a potentially large amount of complexity you have to wade through just to find out if there's an optional argument, or what the types of 'b' and 'c' are.

That's terrible.

An Improvement   slide

class BetterController extends Controller {
   function f($a, $b, $c, $o = null) {
      if (!is_numeric($a)) {
         throw new InvalidTypeException("...");
      }
      // ...
   }
}
POST /api/unstable/better/f
a=1&b=bar&c=baz...

Script   notes

So I set out to make a better way. By making use of PHP's introspective capabilities, we can define controller methods that look like normal PHP methods. Even though this defines something callable from the browser, it's just as easy to call it from other PHP code, you can tell what arguments it takes without needing to read through the entire function, and it's just generally better.

This is not a new idea. Symfony does it in the PHP world, Hunchentoot does something similar in Lisp, and I'm sure there are many other languages with a library that supports something along the same lines.

But types are still an annoyance. Checking every argument is a pain, which means people are going to skip it. And it means callers still have to read through the method to know how to format the data they're passing in. Hopefully the argument name provides a pretty good contextual clue, but probably not always. Sometimes it might even suggest the wrong thing.

Wouldn't it be nice if we could specify the types of the values as typehints?

Once More, With Type Hints   slide

class TypeHintController extends Controller {
   function f(
      row_id $a,
      string $b,
      string $c,
      int $o = null
   ) {
      // ...
   }
}
POST /api/unstable/typehint/f
a=1&b=bar&c=baz...

Script   notes

And using typehints to specify the types of arguments in our controller method might look something more like this. Now enforcing types is easy, so there's no excuse. You just type out the right word! Now knowing what each parameter expects is easy, because you don't have to read the code. And, unlike a doccomment, it can't possibly end up out of date, because it's actual code. It means something in your program.

The only problem is it doesn't work. PHP's typehints must be classes, they can't be arbitrary names. And some people would stop here.

Code, Uh, Finds a Way   slide

goldblum.png

Script   notes

But PHP happens to be conveniently flexible in this area. Failure to match a typehint in PHP triggers an E_RECOVERABLE_ERROR, which means I can write an error handler that grabs the typehint name, validates the argument using an arbitrary function defined for that typehint, and either continues on or throws an exception.

You can find some examples of this in the comments on the Type Hints page in the PHP documentation, so again, not a new idea.

Defining a Non-Class Type Hint   slide

TypeHintHandler::registerTypeHint('int',
   function ($x) {
      return is_int($x) || preg_match('/^[+-]?[0-9]+$/', $x);
   });

TypeHintHandler::registerTypeHint('positive_int : int',
   function ($x) { return 0 < (int)$x; });

TypeHintHandler::registerTypeAlias('positive_int', 'row_id');

Script   notes

As a quick example, this defines an int type which is either an actual int (important for those times you call a method from PHP), or something that looks like an int. Then it defines a positive_int type which inherits from the int type with the additional restriction that it is greater than zero. Then it defines an alias for positive_int called row_id, because it's nice to have very specific type names.

There's more code behind the scenes to tie in the error handlers and all that, of course, and we'll get to some of that shortly, but the type checking itself is fairly straightforward.

So now it's easy to define new types, it's easy to validate types, most of the periphery of defining a method is fairly straightforward. Now this code [flip back two slides quickly] can actually work.

But it's still not quite there. There are certain more structured types we'd like to be able to pass from a form POST or JavaScript into PHP. For example, it'd be really nice to be able to pass a date from the client and get a DateTime instance in the controller method. But of course, the only thing we can get from request parameters is a string.

Type Coercion Through Type Hints   slide

class DateController extends Controller {
   function f(DateTime $date) {
      var_dump($date);
   }
}

$controller->f("2014-11-01");
// => class DateTime#1 (3) { ... }
POST /api/unstable/date/f
date=2014-11-01

Script   notes

And yet we'd like this to work. It's not officially documented as such, but PHP happens to provide references to function arguments when grabbing a backtrace, which means while within our error handler, we can modify the arguments passed to the function, allowing us to make this work.

Defining a Type Converter   slide

TypeHintHandler::registerTypeHint('DateTime',

   // Checker
   function ($x) {
      return is_string($x) && preg_match('#^\\d{4}-\\d{2}-\\d{2}(?:T\\d{2}:\\d{2}:\\d{2})?(?:Z|[+-]\\d{2}:?\\d{2}| [\\w/+-]+)?$#', $x);
   },

   // Converter
   function ($x) {
      $zone = null;
      if (preg_match('#(.*?) ([\\w/+-]+)$#', $x, $m)) {
         list($x, $zone) = [ $m[1], $m[2] ];
      }
      return new DateTime($x, $zone);
   });

Script   notes

So we get a specially-formatted string from the client, parse it out into a date, and return a new instance of DateTime. The machinery does its thing, and our function gets a shiny new object instead of a string. Sweet!

Teetering On the Brink   slide

humpty-dumpty.jpg

Script   notes

Unfortunately, due to "quirks" in PHP's implementation, it only works for required arguments. I'd call them bugs, but my use-case is not supported. What's the point of even triggering a recoverable error if recovering from that error is not supported?—but whatever. And here, we finally hit the boundary of what is possible under this particular implementation strategy.

So that's how we ended up with controller methods performing type conversions based on typehints. It's a somewhat perilous situation—it may stop working in PHP7, probably doesn't work in HHVM at all, and is nonetheless incredibly useful. Barring the optional argument caveat, it makes correct code both easy to write, and very succinct.

Questions?   slide

caterpillar.png

Script   notes

Are there any questions on the typehint shenanigans?

PHP Error Handlers   slide

  • There can be only one
  • Hard to compose
  • Want to do things based on the error code, string
  • Monolithic error handlers suck
  • So … built a dispatcher

Script   notes

As I mentioned earlier, all those typehint shenanigans are implemented through an error handler. The trouble with implementing typehint shenanigans via the error handler mechanism in PHP is there can only be one error handler. This makes it hard to compose assorted bits of code that want to handle different error conditions. This bit of code wants to log something, that bit over there wants to convert errors into exceptions, some other bit wants the typehint shenanigans, and so on.

I like things to be nice and composable—a large monolithic function that handles everything isn't really my thing—so I built a way to dispatch to different error handlers based on error codes, and error strings, and handler priorities. It's not a lot of code, and it sure beats maintaining a handler stack yourself.

Multiple Error Handlers   slide

$eh = new ErrorHandler();
$eh->addErrorHandler(
  E_RECOVERABLE_ERROR,
  '/^Argument (\\d)+ passed to (?:([\\w\\\\]+)::)?(\\w+)\\(\\) must be an instance of ([\\w\\\\]+), ([\\w\\\\]+) given/',
  'TypeHintHandler::_type_error_handler');

$eh->addErrorHandler(
  E_ERROR | E_WARNING | ...,
  "/^(.*)$/",
  ['Exceptionizer', 'handleError'],
  ErrorHandler::Late);

Script   notes

That dispatcher enables us to do things like this, where we add two different error handlers—one for the typehint shenanigans, and another which converts errors to exceptions. The exception converter is set to run after any other error handlers, so if those error handlers handle the condition, it won't run, and so only unhandled errors get converted.

This is handy. Now we can split things up into small pieces, and enable each piece as necessary.

Now, some of you might be wondering why bother? There seems to be a contingent of people who think everything should be an exception, that errors should be abolished entirely from PHP. Those people are wrong.

In Defense of Errors   slide

  • Exceptions are good for some things
  • Errors are good for some other things
  • They're good for different things!

Script   notes

Exceptions are great for what they do, but they're very limited. In Common Lisp, exceptions are generalized into conditions, and throwing to signaling. But more important than the name change is the behavior difference. Signaling a condition (throwing an exception), handling that condition (try-catch), and unwinding the stack are separated out. We can do some of that without having to do all it. In other words, we can handle a condition without unwinding the stack all the way up to the handler code.

To make that clear by way of analogy, let's say we're at a checkout line at the grocery store. So we've got ourself as the customer, the clerk, and the bagger. We give our items to the clerk, the clerk scans them, and gives them to the bagger who bags them.

Grocery Checkout: Parameter Passing   slide

checkout.png

Script   notes

By parameter passing, that might go something like this:

Customer
I'd like my milk in a sack, please.
Clerk
He'd like his milk in a sack.
Bagger
Okay.

Annoyingly verbose, perhaps, but not unreasonable. But let's say I forget to pass that parameter. Most of the time I'm not buying milk, so there's no issue, but this time I happen to have milk in my cart.

Grocery Checkout: Exceptions   slide

bagger-confused.png

Script   notes

With exceptions, the bagger gets to the milk, and:

Bagger
I got milk. You didn't say what to do with milk! Unknown Milk Bagging Preference Exception! [sword!]

Grocery Checkout: Exceptions   slide

clerk-confused.png

Script   notes

The clerk sees this, and doesn't know what to do.

Clerk
Unknown Milk Bagging Preference Exception! [gun!]

Grocery Checkout: Exceptions   slide

customer-disapproves.png

Script   notes

Customer
… Sonofa…

Now your customer is left with groceries scattered about that they have to collect to go check out in another lane. Or, depending on how you handle exceptions at the top level, maybe the entire store explodes.

Grocery Checkout: Conditions   slide

bagger-confused.png

Script   notes

Conditions are a little more reasonable. The bagger gets to the milk and:

Bagger
You want your milk in a sack?
Me
Yes please.

Grocery Checkout: Conditions   slide

bagger-success.png

Script   notes

The clerk doesn't need to play middleman, the customer can make the decision as late in the process as possible, no state is thrown away, the stack remains in tact. It's fantastic!

This isn't really a Lisp talk, but I bring up the condition system to explain why I have a certain fondness for PHP errors. Yes, they're horribly limited and a poor implementation of the general concept, but they have one singularly important distinguishing factor from exceptions: you can deal with them without unwinding the stack.

And that's huge, because it enables things that aren't otherwise possible. If you're unwinding the stack, then by the time you get to the high-level code that has enough application context it can decide what to do, you've already lost. You consumed bytes from a stream, they got thrown away while unwinding the stack, and now you're screwed. Exceptions are great for keeping the error-handling code out of the main line of execution, but they absolutely suck at recovery.

Links   slide

Elephander: Type Hint and Error Handling Shenanigans
https://github.com/bettrlife/elephander
Practical Common Lisp, by Peter Seibel
http://www.gigamonkeys.com/book/

Script   notes

But I've digressed somewhat. If you're interested in the typehint or error handling shenanigans, those are available via the Elephander library, which you can find on GitHub or composer install. And if you're interested in learning more about Common Lisp conditions, I recommend Peter Seibel's book Practical Common Lisp.

Questions?   slide

cat2.png

Script   notes

So are there any questions on error handler dispatch or conditions or what-have-you?

DataObjects   slide

  • Other devs old C++ guys
  • So no ORM
  • Everything a resultset array
  • Problem: different model functions return/accept different keys for the same underlying table
    • Big pain

Script   notes

When I got hired at BettrLife, I was the odd man out. All the devs had worked together at a previous company, so they knew each other really well, their strengths and weaknesses, what each person likes to do and not do. So I got to come into a team with a really well-established dynamic, and that dynamic is pretty great.

But that previous company built a monolithic C++ app. I was really the first guy on the team with any significant web experience. (I probably could have given an entire talk just on the security holes we've closed since I started because the web is such a labyrinth of security pitfalls.) So, to avoid beating my head against the wall unnecessarily, whenever I'm evaluating an old architectural decision, I tend to have to frame it in the mindset of "Would this have made sense if this were a monolithic C++ app?". And usually the answer is yes.

So there's no ORM. Makes some development harder, but okay. They're not a panacea, I can kind of see the logic there. But in the end, you have to return some kind of structure from the models, so they return arrays. They originally returned resultsets, but let's not get in to that.

Arrays are great. They're very flexible. You want some extra stuff attached to a row? Just slap it in there!

But arrays come with problems. The big one being there's nothing enforcing any kind of structure. So two different people writing two different model functions pulling data from the same tables can and do end up returning arrays with different key names for the same values. Heck, even the same programmer on different days or in different moods might do that.

This is a pain. Not just because it means lots of refactoring to make it all align, but because that refactoring won't entirely help. If you recall the slide about our basic architecture, all that data syncs down to the mobiles. Since we can't control if or when a user updates their app, all those crazy key names need to keep working even after you've excised them from the rest of the codebase.

DataObject Example 1   slide

class Nutrition extends DataObject {
   public $id;
   use NutrientsMixin;

   protected static function addIterableKeyExclusions() {
      return ['id'];
   }
}

Script   notes

To solve this problem, we came up with what we call a DataObject, which when you get down to it is basically just an array with a well-defined structure.

So here, for instance, we're defining a Nutrition type, which has an id, some nutrients which we pull in from a trait shared by numerous other dataobjects, and this fancy bit here which is used to exclude the 'id' during a foreach loop. So it'll iterate over the nutrients—calories, sodium, vitamin A, etc.—but not the id.

DataObject Example 2   slide

class Ingredient extends DataObject {
   public $id;
   // ...

   protected static function addKeyAliases() {
      return [ 'ingredient_id' => 'id' ];
   }
}

Ingredient::create(['ingredient_id' => 1])->id;   // === 1
Ingredient::create(['id' => 2])['ingredient_id']; // === 2

Script   notes

Or in this example, we've got a recipe ingredient, and it specifies that the key ingredient_id is equivalent to the key id, so setting or fetching the ingredient_id key will set or fetch the underlying id property.

DataObjects gives us quite a lot of control. We can separately control which properties are json-encoded, which are iterable, what keys are aliases for other properties, and maybe one or two other things I'm forgetting. Doccomment annotations might have worked here too, now that I think about it, but nobody thought of them at the time.

But all that flexibility can be hard to manage.

Scary DataObject Example   waytoomuchcode slide

trait HasImageMixin {
   protected $imageURL;
   protected $imageID;

   protected static function getJSONKeyExclusions() {
      return [ 'image_url' ];
   }
}

class Food extends DataObject {
   use HasImageMixin {
      getJSONKeyExclusions as HasImageMixin_getJSONKeyExclusions;
   };
   public $id;
   public $excludeMe;
   // ...
   protected static function getJSONKeyExclusions() {
      return array_merge(
         [ 'exclude_me' ],
         self::HasImageMixin_getJSONKeyExclusions(),
         parent::getJSONKeyExclusions());
   }
}

class Product extends Food {
   public $usdaNo;
   // ...
   protected static function getJSONKeyExclusions() {
      return array_merge(
         [ 'usda_no' ],
         parent::getJSONKeyExclusions());
   }
}

Script   notes

As soon as inheritance and traits get thrown into the mix, things go to hell. Because now to use a trait, you have to know whether it defines any aliases, or excludes any of its keys. Same for your parent. And you have to know how to combine all of that stuff.

This is tedious and error-prone. People are going to screw it up. Fortunately, Common Lisp provides us with an answer to this problem: method combinations!

Method What-Nows?   slide

  • Normally, subclass method author must:
    • Decide if and when to call parent method
    • Combine result of parent class's method with own result
    • Return combined value
  • Method combinations:
    • Take all the functions from class, parent classes
    • Call all of them
    • Combines results automatically

Script   notes

When you call a method on a class, you call that class's method, which potentially calls the parent class's method, and so on up the inheritance chain. But whether it does, and when it does, is programmer driven. Method combination generalizes and automates that concept.

To steal Peter Seibel's explanation from his most excellent book "Practical Common Lisp":

"method combinations produce an effective method that contains the code of all the primary methods, one after another, all wrapped in a call to the function … that gives the method combination its name. … For example, a generic function that uses the + method combination will return the sum of all the results returned by its primary methods."

Method Combination: + (Code)   toomuchcode slide

class A {
  protected function f() { return 1; }

  /** Calls f() across hierarchy and combines results. */
  public function m() {
    $mc = new MethodCombinator([], '+', ::IncludeTraits);
    return $mc->invoke('f', $this, []);
  }
}

class B extends A { }

trait T {
  protected function T_f() { return 2; }
}

class C extends B {
  use T;
  protected function f() { return 3; }
}

(new A())->m(); // => 1
(new B())->m(); // => 1
(new C())->m(); // => 6

Script   notes

For example, here each class defines a function f() which returns a value. We've also got a function m() which acts as the public interface, and creates a method combinator. That combinator walks the class hierarchy calling each function f(), and then sums the values returned.

So we create an A instance, call m, and it calls f() in A and returns 1. We create an instance of B, call m, and looks—oh, no f in B, so nothing to do there—but there's an f in A, so call that, and return 1. Then we create an instance of C, call f in C get three. C uses T, T has an f, call f in T, get 2. So now I've got three and I've got two. C's parent B doesn't have an f, nothing to do. B's parent A has an f, call f in A, get 1. Sum them all together and we get 6.

If you're wondering what's up with the T_f(), that's the abstraction leaking. We namespace the trait functions so trait users don't have to resolve name conflicts. If that was just function f() in the trait, then PHP would get bitchy when using it in class C.

In pictorial form, that might look more like this.

Method Combination: + (Picture)   slide

mc-1.png

Script   notes

So, like in the code from the previous slide or our diagram here, each class function returns a value, and our method combinator runs the method in each class and trait, and sums the results together.

Now, obviously method combinations aren't applicable in every scenario, but when they are they're a really handy tool to have available. It's really a little sad they don't exist in more languages, because being able to abstract away the cruft of "How do I combine this with all the other pieces of this same sort of information?" from "What part do I care about right here?" is just fantastic.

If you recall our DataObject example from earlier [flip back to Scary DataObject Example], is really ugly and painful. By using method combinations [next slide] it becomes much more palatable.

With Method Combinations   toomuchcode slide

trait HasImageMixin {
   protected $imageURL;
   protected $imageID;

   protected static function HasImageMixin_addJSONKeyExclusions() {
      return [ 'image_url' ];
   }
}

class Food extends DataObject {
   use HasImageMixin;
   public $id;
   public $excludeMe;
   // ...
   protected static function addJSONKeyExclusions() {
      return [ 'exclude_me' ];
   }
}

class Product extends Food {
   public $usdaNo;
   // ...
   protected static function addJSONKeyExclusions() {
      return [ 'usda_no' ];
   }
}

Script   notes

Each function is responsible for just the part applicable to the class or trait it's in, and no more. Combining the results from all those functions is somebody else's problem, the author of a particular class or trait doesn't have to worry about it.

This is much more manageable! Easier to read, easier to write, harder to screw up. Parts are vastly more composable, I'm decoupled from the implementation of any given trait I might want to use, and maintenance is easier because I don't have to refactor any class which uses my trait, or any subclasses, just to add in a method.

Links!   slide

SchmancyOO: Method Combinations
https://github.com/bettrlife/schmancy-oo
Practical Common Lisp, by Peter Seibel
http://www.gigamonkeys.com/book/

Script   notes

If I've convinced you method combinations are pretty great, you can use them in your own projects by making use of the SchmancyOO library, available on GitHub and composer installable. If you'd like to learn more about method combinations in Common Lisp, check out the book Practical Common Lisp.

Questions?   slide

deedum.png

Script   notes

Any questions on method combination?

Context-Oriented Programming   slide

  • Inspired by Pascal Costanza's ContextL
  • A Common Lisp library
  • Add or remove a property / method depending on the context

Script   notes

But that's not the end of our DataObjects, oh no. Because what we found was that fairly often we want to pass different subsets of data to the mobile phones and the browser.

While we code do that via the mobile-API-specific code, or the web controllers, that ends up sucking, because you end up having to do it in multiple places, and now you no longer have a single place you can look to see what format a given object will be in. We'd quickly end up in the same situation we were in before, which this API gives you one thing, and that API gives you another.

We certainly don't want that.

So again I turned to Common Lisp for inspiration. There's a library ContextL which provides something called Context-Oriented Programming. You can kind of think of it as Aspect-Oriented Programming done better. The author Pascal Costanza actually wrote an aspect-oriented programming library for Common Lisp, and when he was done looked at it and realized it wasn't really necessary in Lisp. And that inspired him to invent Context-Oriented Programming.

Which, as it's name kind of implies, is a way of saying "within this context, apply this specific behavior". Add this piece, take away that, whatever.

A DataObject Example   toomuchcode slide

class ThreadMessage extends DataObject {
   // Message fields
   public $id;
   public $body;
   public $userID;

   protected $user; // has getter

   protected function getUser() {
      return $this->_usermodel->getUser($this->userID);
   }

   // ...




}

Script   notes

For instance, here we define the structure for a message in a conversation thread. That user property is an automatically-populated nested object. If we send six messages from the same user, we'd send that user down the pipe six times. Redundant data might compress well, but it's still a waste of memory and bandwidth and battery life. That's okay for the web UI since it simplifies populating a template, but we don't really want to do that via the mobile API where data is precious. We don't want to be the app that drains the battery.

Components!   toomuchcode slide

class ThreadMessage extends DataObject {
   // Message fields
   public $id;
   public $body;
   public $userID;

   protected $user; // has getter

   protected function getUser() {
      return $this->_usermodel->getUser($this->userID);
   }

   // ...

   protected static function addDefaultComponents() {
      return [ ThreadMessageMobileAPI::create([]) ];
   }
}

class ThreadMessageMobileAPI extends Component {
   public static function getApplicableLayerName() {
      return "MobileAPI";
   }

   protected static function addJSONKeyExclusions() {
      return [ 'user' ];
   }
}

Script   notes

So we created what we call a Component, which is a little adjunct class that gets attached to our DataObject, and defines structural changes to that DataObject that apply only within a particular context.

This component just says that within the context of the mobile API, we exclude the 'user' key from JSON serialization. But components can do pretty much anything our DataObjects support: we could have just as easily added an alias that only exists within the context of the mobile API, or added a property that only exists within the context of the web, or what-have-you. There are lots of possibilities.

ThreadMessage in Pictures   slide

doc-1.png

Script   notes

Or, if we put that in a diagram rather than code, we can specify that our object has certain base properties, and add or remove those properties depending on context. So we have our base ThreadMessage object, and inside the WebUI no changes are made so it comes out the same, but when we filter it through the mobile API that user key gets removed and it ends up looking a little different.

Activating Contextual Layers   slide

// No, you wouldn't actually do this with these particular layers
Context::withActiveLayer('WebUI', function () use ($msg) {
   echo "WebUI: ", json_encode($msg), "\n";
   Context::withActiveLayer('MobileAPI', function () use ($msg) {
      echo "MoAPI: ", json_encode($msg), "\n";
   });
   echo "WebUI: ", json_encode($msg);, "\n"
});
WebUI: {id:1,body:"text",user_id:127,user:{name:"Bob"}}
MoAPI: {id:1,body:"text",user_id:127}
WebUI: {id:1,body:"text",user_id:127,user:{name:"Bob"}}

Script   notes

Activating a layer isn't particularly hard. Here I'm using a pattern from Lisp, with-foo, which takes a thunk and sets the context prior to calling the thunk and resets it back to whatever it was before returning.

Obviously this particular example is not realistic, but it demonstrates things in action. We start in the WebUI, json_encode our object, and get one thing, enter the MobileAPI, json_encode that same object, get something else, then back out into the WebUI and get what we got before. Our object itself never really changes, but the way it serializes does.

So far in our codebase, nobody really has to deal with this. It's already taken care of prior to entering the Mobile API or Web Controllers. But if we wanted to have more specific layers, say a layer for each specific version of the mobile API, or a layer for some narrow section of code, we could totally do that, it's not super hard.

Defining Contextual Layers   slide

// WebUI, MobileAPI, and GearmanWorker are disjoint layers
class WebUI extends Layer {
   public function adjoin(&$context) {
      foreach ($context as $l) {
         if ($l instanceof MobileAPI) $l->remove($context);
         if ($l instanceof GearmanWorker) $l->remove($context);
      }
      parent::adjoin($context);
   }
}

Script   notes

Defining a layer can be a little trickier. Here, for example, we define the WebUI layer. It's disjoint with some of our other layers, so whenever it's activated it disables those layers. When we leave the scope that enabled the WebUI layer, anything it deactivated will be automagically restored.

Again, not something that tends to happen much in our codebase—the basic layers of MobileAPI and WebUI are already defined and we haven't had much call for additional layers—but still something we might want to do at some point.

There's probably some room to make defining a new layer easier, but for now this works well enough.

Links   slide

Script   notes

Our Context-Oriented stuff is not available as open source, but if you're interested in the general idea, check out ContextL. There's not a lot of documentation, but there is an overview paper which should give you a decent idea of what Context-Oriented Programming is all about.

Questions?   slide

tea.png

Script   notes

Any questions on context-oriented programming?

Self-Populating Meta Templates   slide

nutrients.png

Script   notes

NOTE: Check time! Skip if near end of time.

BettrLife is a "Health and Wellness" app. So we do a lot of things with nutrition. Which means we have a lot of templates that involve nutrients. They're everywhere!

Nutrition Structure, A Simple Template   slide

{
   "calories": 100,
   "total_fat": 50,
   "sodium": 1200,
   "vitamin_a": 15
}
Calories: {{ calories|round(0) }}
Total Fat: {{ total_fat|round(1) }}g
Sodium: {{ sodium|round(0) }}mg
Vitamin A: {{ vitamin_a|round(1) }}%

Script   notes

Our basic nutrition structure is dead simple. This makes lots of things, like math operations, really easy. But the templates are incredibly repetitive.

And that just won't do. Any time you have to repeat yourself is a recipe for bugs and inconsistencies. Nevermind all the changes you'd have to make to add or remove a nutrient. It'd be crazy for every template involving nutrients to repeat all this data.

ViewModels and A Less Repetitive Template   slide

{% foreach nutrient in nutrition %}
{{ nutrient.name }}: {{ nutrient.value|round(nutrient.precision) }}{{ nutrient.unit }}
{% endfor %}
{ "nutrition": [
    { "name": "Calories",  "value": 100,  "precision": 0, "unit": ""  },
    { "name": "Total Fat", "value": 50,   "precision": 1, "unit": "g" },
    { "name": "Sodium",    "value": 1200, "precision": 0, "unit": "mg" },
    { "name": "Vitamin A", "value": 15,   "precision": 1, "unit": "%" }
  ]
}

Script   notes

So maybe you reach for a ViewModel. You trade a much more complicated data structure for a much less repetitive template. And that certainly works.

But it sucks. Now every user of the template has to know to transform the data, even if they're merely incidental users. Passed data to a template that happens to import this template? You'd better have transformed the data! The necessity of the data transformation leaks out. The fact I have to remember to do a transformation at all makes it error prone. I'm a simple guy, I want to take data out of the model, shove it at the template, and call it a day.

It also complicates client-side templates. Your JavaScript already has that nice simple structure where each nutrient has a key, and it does something with that, and now you have to complicate it by making it transform that data before it can be put into a template.

So now my transformation's duplicated, my structure's complicated, template use got harder. This solution has some benefits I desperately want, but overall, it blows.

Self-Populating Meta-Templates to the Rescue   slide

<% foreach key, nutrient in _context() %>
<< nutrient.name >>: {{ <<key>>|round(<<nutrient.precision>>) }}<< nutrient.unit >>
<% endfor %>
<?php return getNutrientDefinitions();
function getNutrientDefinitions() {
   return [
      'calories'  => ['name' => 'Calories',  'precision' => 0, 'unit' => ''],
      'total_fat' => ['name' => 'Total Fat', 'precision' => 1, 'unit' => 'g'],
      'sodium'    => ['name' => 'Sodium',    'precision' => 0, 'unit' => 'mg'],
      'vitamin_a' => ['name' => 'Vitamin A', 'precision' => 1, 'unit' => '%'],
   ];
}

compiles into…

Calories: {{ calories|round(0) }}
Total Fat: {{ total_fat|round(1) }}g
Sodium: {{ sodium|round(0) }}mg
Vitamin A: {{ vitamin_a|round(1) }}%

Script   notes

So instead, we go meta. We take the definitions, which are basically template data, and use that to populate a template. That template outputs a template which takes a nice simple data structure.

The term meta-template might be a little scary, but it's not really all that different from our ViewModel template. [flip back] They're very similar in appearance. So for this teensy bit of extra mental effort to go meta, we get the benefit of the brevity and ease of maintenance of our ViewModel template, with the simplicity of use of our original repetitive template.

And that, to me, is a pretty clear win.

Questions?   slide

lobster.png

Script   notes

Any questions on meta-templates?

Thanks for Listening!    slide

This Talk
http://talks.kepibu.org/2014/icc-php/
Elephander: Type Hint and Error Handling Shenanigans
https://github.com/bettrlife/elephander
SchmancyOO: Method Combinations
https://github.com/bettrlife/schmancy-oo
Practical Common Lisp, by Peter Seibel
http://www.gigamonkeys.com/book/ (It's a good book!)
ContextL
http://common-lisp.net/project/closer/contextl.html

Script   notes

Awesome. Well, thanks for listening!

This talk is available online, so if you'd like to go over it again or want links to more details, there's the URL. That's also got my e-mail address, so if you want to pester me about something, you can totally do that.

If I've managed to pique your interest in Common Lisp, check out the book "Practical Common Lisp" by Peter Seibel. It's available for free online, and you can also buy a dead tree version. Peter Seibel is a very lucid author and does a pretty good job relating the strange and wonderful things in Common Lisp to programming idioms with which you're probably already familiar, so it's totally worth your money.

Thanks for having me!