Thursday, September 24, 2009

A Different Way: Traits in PHP

For a while now I've had some friends who are Perl fanatics. Perl's OOP support is minimal, which makes it extremely flexible and powerful. One module that takes advantage of that is Moose, which provides an easy-to-use framework for OO design. Basically, Moose gives Perl the OO capabilities that most languages take for granted, if that's what you're looking for. In addtion to such capabilities are a few that most languages don't have, such as Roles.

Moose Roles are:

  • like multiple-inheritance, except that a Role handles method and attribute collisions

  • like interfaces, except that a Role is more than a contract--it's fully-working code

  • like mixins, except that Roles can have requirements for being applied to an object

  • like duck typing, except that a Role is a named collection of methods and attributes, so the state of being something is more concrete


These ideas really inspired me, but I'm stuck with PHP. Fortunately, PHP 5.3 comes with a crippled implementation of lambdas and closures, which gives just enough functionality to implement something similar to Moose::Role. I also read these two papers, which gave me some solid ideas, although I didn't read them as gospel.

You can get the code from GitHub.

How It Works

First, you have to define some traits:

TObj::Trait('Sortable',
'sort',function($obj) {
...
},
'sortAttribute','',
'setSortAttribute',function($obj,$attrName) {
$obj->sortAttribute = $attrName;
}
);

TObj::Trait('Categorizable',
'category','',
'getCategory',function($obj) {
return $obj->category;
}
);

Now we create some other class:

class Something extends TObj {
private $foo;

public function __construct($baz) {
$this->foo = $baz;
}
}

And now we apply our traits to Something object:

$something = new Something('lolwut');
$something->apply(Sortable);
$something->apply(Categorizable);

You can now call those traits' methods as if they belonged to the Something class! Note that an object's methods and attributes will override any methods or attributes in the traits, even within the context of the calling trait (see part about aliasing below). This makes traits as flexible as extended super classes.

It's a fairly simple concept, though there are some hang-ups when you get down to the nitty-gritty. For instance, what if two traits have methods or attributes with the same name? Well, you can simply exclude it from one of the traits:

$something->apply(Categorizable,array(
except=>array('getCategory')
)
);

or alias it:

$something->apply(Categorizable,array(
alias=>array('getCategory'=>'getCategoryName')
)
);

But what if getCategory() is used all over in other Categorizable methods? Now they'll actually be calling the getCategory() from the other trait, right?

Wrong! Since TObj has to handle all trait method dispatch, it does a little magic. To resolve method and attribute names, it checks for the original name ("getCategory") within the trait whose method made the call, then moves outside and checks aliased and unaliased names. It works the same way for trait attributes, too. This prevents aliases from breaking trait code.

To know if some random object you've received has a certain trait:

if ($something->applied(Categorizable)) {
echo 'It is categorizable!';
} else {
echo 'This object marches to the beat of a different drummer.';
}

To require that the object have a certain trait method:

TObj::Trait('TraitThatRequires',
required,array('aRequiredMethod'),
...
);

$something->apply(TraitThatRequires);

The last line would throw an exception because aRequiredMethod() is not yet implemented by a trait applied to $something.

TObj traits do other things, and you're welcome to check out its unit tests to see its full functionality.

This project is very new, and it has little or no documentation and I'm sure it has bugs (though all tests pass, of course). If you're interested in the project, feel free to pull it and contact me if you have any interesting ideas.

Wednesday, September 2, 2009

Vim Tip: Debug Path

I work for a company whose code is in thousands of files in hundreds of directories. We have multiple VCS repositories. Function libraries, class files, HTML, XSL--a convoluted maze to navigate, especially when trying to debug something.

Whenever I'm debugging, I may follow the rabbit hole from function to function, through various libraries and classes, only to find that I'd taken a wrong turn somewhere and come to a dead end. I then either have to sift through the multiple tabs I've opened to find where I should pick up the search again, or, if I've been closing files along the way, I have to start the search from the beginning. This is annoying and time-consuming.

I decided to write a simple vim script to save my cursor's current line, along with the file name and line number, to a file in my home directory. I can then continually save lines as I follow a path through the code. If, at any time, I hit a dead end, I simply go through this "trace" file and find where to pick up the search again. Combined with vim's "gf" command, this file is very useful!

Here's the code you can add to your own .vimrc file:

nmap <F8> <ESC>:call WriteTrace()<CR>

function WriteTrace()
let lineNum = line('.')
let lineFile = bufname('%')
let lineVal = getline(lineNum)

let allLines = readfile("$HOME/trace.txt")
let allLines = add(allLines,lineFile.":".lineNum)
let allLines = add(allLines,lineVal)
let allLines = add(allLines,"")

call writefile(allLines,"$HOME/trace.txt")
endfunction

In the code above, F8 will save the line under the cursor, but the nmap can be changed to map to any key, of course.