Sunday, March 11, 2012

PHP 5.4 Traits

With the recent release of PHP 5.4 there are several new features that will have PHP developers talking. The one that I immediately find most intriguing is Traits. Traits are a new feature in PHP's object-oriented toolbox that allow a form of multiple inheritance that was not possible before.

Since you cannot declare an instance of a Trait and unlike an Interface you can define base methods along with your abstract methods, it seems to me that Traits can be thought of as a special type of abstract class.

One of the more complex examples from PHP.net demonstrates how to resolve the issue of name-conflicts when inheriting from multiple Traits. <?php trait A { public function smallTalk() { echo 'a'; } public function bigTalk() { echo 'A'; } } trait B { public function smallTalk() { echo 'b'; } public function bigTalk() { echo 'B'; } } class Talker { use A, B { B::smallTalk insteadof A; A::bigTalk insteadof B; } } class Aliased_Talker { use A, B { B::smallTalk insteadof A; A::bigTalk insteadof B; B::bigTalk as talk; } } ?> Using the new "insteadof" (not to be confused with "instanceof") you can specify which implementation of a method to use, and with "as" you can essentially rename a method to use a new name in your implementation. While this does solve the issue of name-conflicts, I think that we will start to see a whole new set of problems as packages start to implement traits in their design patterns.

This is a contrived example, but consider the following code. In order to test this out for myself, I followed the instructions for installing PHP 5.4 on Ubuntu. <?php trait Revision { public function saveData() { $data = $this->getData(); // Store data, maybe add a revision timestamp and author } /** * @return array * An array of data to be stored. */ abstract public function getData(); } trait Printer { public function printData() { $data = $this->getData(); // Format data and output print_r($data); // Just for our example } /** * @return array * An array of data to be output. */ abstract public function getData(); } class MyClass { protected $data; // An array use Revision, Printer { Revision::getData insteadof Printer; Printer::getData as getPrinterData; } public function __construct($data) { $this->data = $data; } public function getData() { return $this->data; } public function getPrinterData() { // Perhaps we hide some data from being printed before returning it. $data = $this->data; unset($data['private']); return $data; } } $data = array( 'name' => 'Instance of MyClass', 'private' => 'Something that is stored somewhere but should not be output.', ); $my_class = new MyClass($data); $my_class->printData(); ?> What should happen when you call printData()? Does it call getData() or getPrinterData()? The answer is that it calls getData(), which is not our intended result as our "private" data is output as well. Array ( [name] => Instance of MyClass [private] => Something that is stored somewhere but should not be output. ) From what I can tell there isn't a way to call the renamed method as it was aliased from other methods in the Trait. While this isn't a completely realistic example, I think it demonstrates that just because you can easily alias conflicting method names with Traits, doesn't mean you should become lazy with your namespacing.