Yoan Blanc’s weblog

Another lost swiss guy

December 2010

The Lithium way

Yoan BlancTue 28 December 2010, ,

I’m currently working with Lithium (aka li3 or Cake 3) mostly for more than a month and would like to share some structure they’ve been putting it and that I like.

I like them because they made that framework so much flexible in a way you can plug anything at almost any places or add — what they call — filters to certain key methods or functions to tweak their behaviour at your needs.

Let’s start with the mentioned filters. A filter will wrap the body of a (static) function or method and let you act before / after the initial work, very much like monkeypatching.

<?php
namespace monkey;

class Math extends \lithium\core\StaticObject {
 static function add($a, $b) {
  $params = compact('a', 'b');
  return static::_filter(__FUNCTION__, $params, function($self, $params) {
   extract($params);
   return $a + $b;
  });
 }
}

Math::add(2, 2); // 4

As you can see we are using PHP 5.3 features like namespace, closures and late static binding.

  • The namespace enable you to avoid Zend Framework alike naming, here we are in the monkey namespace.
  • A closure is used to describe the body of the function, what it’s gonna do. Before that PHP had only eval-like function (via create_function) that were very ugly to create and use.
  • And last but not least, the late static binding lives in static:: (which is close to self::) but will always be the class where it’s written and not any sub classes (that’s what self:: is for)

Just in case you didn’t know, PHP 5.2 is not supported anymore. At least before its next release. Jamais deux sans trois. (never two times without a third time).

Now, let’s hack this.

<?php
use lithium\util\collection\Filters;

Filters::apply('\monkey\Math', 'add', function ($self, $params, $chain) {
 extract($params);
 $ret = $chain->next($self, $params, $chain);
 fwrite(STDERR, "$a + $b = $ret");
 return $ret;
});

use monkey\Math;
Math::add(1, 2); // 3
// stderr: 1 + 2 = 3

For those who don’t know, stderr is the error output on UNIX. You can pipe / redirect it to another location using 2>.

Inside lithium this kind of filter are mostly used in the Dispatcher. The Dispatcher is the class that handles all the requests, matching a route with an existing controller. So you can do exactly what you would do with WSGI/Rack/… middlewares.

Methods can aslo be written in a way you can filter them afterwards.

<?php
namespace monkey;

class Tweeter extends \lithium\core\Object {
 function tweet($message) {
  $params = compact('message');
  return $this->_filter(__METHOD__, $params, function ($self, $params) {
   echo 'tweet: ', $params['message'], "\n";
  });
 }
}

$t = new Tweeter();
$t->tweet(’Hello’); // Hello
$t->applyFilter('tweet', function ($self, $params, $chain) {
 $params['message'] .= ' ^YB';
 return $chain->next($self, $params, $chain);
});

$t->tweet('Hello') // Hello ^YB

Pardon my examples to be simplistic. A great usage of all this are tests. By performing surgerical modification inside your objects, it becomes very easy to tweak everything without bloating the initial object much. I agree that filterable methods or functions aren’t as readable as normal ones.

And because on major thing you do in Object Oriented Programming is to link objects, instances together here comes the last pattern, kind of, you may find into Lithium. I dunno exactly if it’s Inversion of Control or Dependencies Injection. I’ve stayed away from Java a too long time to know.

<?php
namespace monkey;

class MyObject extends \lithium\core\Object {
  protected $_classes = array(
   'math' => '\monkey\Math'
  );

  protected $_autoConfig = array('classes');

  public function plus1($n) {
   $math = $this->_classes['math'];
   return $math::add($n, 1);
  }
}

$obj = new MyOject(array('classes' => array(
 'math' => '\ape\Math'
)));

$obj->plus1(2); // 3

I always feel silly having such useless example but I hope that you get the point. MyObject isn’t linked to a particular Math class but can use a different one if needed with no code modifications.

By the way, Symfony2 uses something similar with a _class suffix in the options.

From a JavaScript, Ruby or even Python point of view, this is the poor’s man monkeypatching and it involves a really heavy syntax for few gains. As I’m still complaining for every array I have to type, this is no big deal.

Don’t forget to join #li3 on freenode.

Depuis mon retour à la civilisation ultra-moderne, j’ai (re)plongé dans PHP et un framework très intéressant : Lithium (ou li3 pour les intimes, voire Cake3 pour qui aurait raté une étape) et aimerait pointer deux, trois éléments qu’il offre.

Étant un touche-à-tout, PHP s’avère rapidement frustrant quand on passe un peu trop de temps à faire mumuse avec Python, Ruby ou JavaScript. J’exècre tout particulièrement le mot-clé array. Heureusement que PHP 5.3 offre tout un tas de nouveautés qui satisfont ma curiosité et pèse dans la balance de la frustration.

Il n’est tout simplement pas possible d’altérer un fonction ou méthode en PHP. Ce qu’on appelle monkeypatching.

Lithium met en place un système se basant sur les closures (fermetures, clôtures en français) permettant de filtrer l’exécution d’une fonction (statique) ou méthode.

Un filtre, à la manière un middleware WSGI/Rack/JSGI/…, va permettre d’agir avant et après l’exécution initiale. Ajouter des filters, signifie ajouter des couches concentriques. Le code initial doit être adapté pour offrir cette fonctionnalité, hélas, mais l’aspect positif est qu’il ne devrait pas être nécessaire de modifier certains composants centraux du framework.

Autre point intéressant est qu’une relation logique entre deux classes n’est pas figée. Une table de correspondance permet de, à la volée, modifié quel classe sera utilisée. Point très, très utile lors des tests pour remplacer un système envoyant des e-mails par exemple par un autre ne faisant rien (autrement dit mocking).

Un simple respet d’architecture, structure pour une flexibilité finale.

Symfony2 utilise, par endroit, la même idée pour l’interchangeabilité des composants mais je n’ai rien vu de similaire aux filtres. J’imagine que le système évènementiel permet d’obtenir des résultats similaires.

Amusez-vous bien, réveillonnez à souhait et n’oubliez pas que PHP 5.2 est tellement 2006.

About

meYoan Blanc is a web developer that lives in Switzerland (rue des Fahys 15, 2000 Neuchâtel) and works as a freelance.

Get my vCard or contact me by phone (skype:yoan.blanc) or email ().

Misc

RSS, list.blogug.ch

This site reflects only my opinion and is not affiliated with anyone else.

copyright 2006-2010 — doSimple.ch