TravisSwicegood.com

26 October

Fluent-API, here I come!

One of the things that bugs me about PHP is that fluent APIs that were made possible by method chaining in PHP 5 are hindered by the new keyword. In Python or Javascript it's possible to instantiate the object and continue chaining it. An example in PHP syntax would be:

$result = new SQL()->select('*')
                   ->from('users')
                   ->where('username')
                   ->like('travis%')
                   ->execute();

In PHP, that "new" kills it. However, this code would work:

$result = SQL()->select('*')
               ->from('users')
               ->where('username')
               ->like('travis%')
               ->execute();

All you have to do is define a function that has the same name as your class that wraps the new and off you go. This falls into that "I guess I should have known" category. Functions and classes aren't the same in PHP and since it will never going down the everything is an object road (unfortunately - if you've ever programmed in a language that has it you know what I mean) it would make sense that there wouldn't be any name clashing between a function and class of the same name.

This means I can start removing all of the factory methods I have that do nothing but instantiate an object and return it. Now I'll just slap a function in and away we go.

As an extra benefit, interfaces fall into the same category as classes. You can't have an interface and an object both named SQL, but either one with a function is fine. This gives all sorts of new possibilities to a Dependency Injection Manager:

interface SQL {
    public function query($query);
}
$sql = SQL();
$sql instanceof SQL_Driver_MySQL;

Now, if I could just find a use for a DI manager that couldn't be solved by other means. :-)

18 comments

If you're going to wrap the initial object creation for chaining purposes, IMHO one could create a generic type that can provide this clean syntax along with some nice features. Most of all, when dealing with chained method calls on an API, the freedom to not care whether exceptions or unexpected return values (like null) will ever arise.

What you'd need is a class that wraps any object, reflecting the object's members and methods and using overloading to provide a transparent pass-thru to the reflected methods. Luckily PHP5 has an easy and robust reflection API, plus its standard __call/__get/__set overloading.

Anyway I implemented this a while back in a class called "Maybe" since it represents a wrapper that contains maybe an object, or maybe nothing. Like your suggestion here, I included a function Maybe() that returns a new Maybe. Then you can write code like:

Maybe(new SQL())->select('*')
->from('users')
->where('username')
->like('travis%')
->execute();

And not worry about whether where() or like() will return correctly or throw exceptions -- just make the overloaded method calls return a new Maybe containing the result of the actual reflected method call, and the chaining is pretty much guaranteed to never fail on you.

So, if you're interested I can post the code on my website.
p.s. You could also keep it insanely simple and just write this function:

function Just($x) { return $x; }

and then write all your code like

Just(new XML())->etc()->etc();
I typically prefer to just use a method on the objects.

XML::new()->etc()-etc()

With late static binding (5.3, right?) one should be able to define that new() method in a parent class and have it work on subclasses.
Err ... I meant a [static] method on the *classes*, of course :)
Nick - that's an interesting idea for sure. I got all excited about it, but then remembered that it would nullify all type hinting. The Just() code is essentially what I'm suggesting, except there would be no "new" in your code at all.

Hans - nope. "new" is a keyword in PHP that can't be used for anything other than creating a new instance. Thus the issue...
I'm not sure what you mean by nullifying all type hinting. If you have, say, a domain class Person with method setMother(Person $mother){}, then wrapping a Person in Maybe() and calling setMother('foo') will still result in E_RECOVERABLE_ERROR.

Maybe you meant it nullifies your IDE's ability to inspect and do code-completion? In that case, yes, it does.
How about something like;

$result = SQL::new()
->from('users')
->where('username')
->like('travis%')
->execute();

Or since it looks like you are only going to be doing that one thing, you could just do the good ol' singleton function getInstance()
Oops, should have read the comments. Hans beat me too it.
Nick - Exactly my point. If I wrap everything and use the overloading and magic methods I end up with a "MaybeWrapper" object instead of SQL. If function SQL() returns a fully instantiated object implementing SQL, then type-hinting continues to work, I just removed the standard "new SQL()" from the equation and didn't have to assign a real variable.

Dougal - as mentioned above, new() won't work due to a keyword violation. getInstance() also implies Singleton which is the last thing I want in most cases as there should only ever be one instance of that object around. What I am wanting is bona-fide replacement to "new SQL()". Something that will allow me to go straight into a fluent API without the new keyword messing it up.

The problem is that you get feature bloat by having to wrap instantiation everywhere. A perfect example is $this->getMock() in PHPUnit. Using this method of wrapping it in a function, you could still keep the fluent API without having to assign a variable and keep the code in the base TestCase much more compact. With the new package, err - alias, err - namespace code in 5.3 it will be even that much nicer:

from PHPUnit import Mock;
... rest of the code ...
$mock = Mock('SomeObject')->method('log')...
There is a patch I proposed for the Zend Engine on php-internals designed for PHP 5.3 and 6.0 which would allow any keyword to be used as a method name. Note that we can already do this for variables:



The above code works just fine.
Actually a simple pattern is to use the instance (factory/singleton/getInstance) way.

$sql = SQL::getInstance('mysql')->doStuff()..

getInstance of course calls factory which uses the singleton or not if you don't want it to :)

$0.02
Thank you for interesting discussion.

Here is "crazy" method for calling class methods w/o creating variable:

unserialize(serialize(new Class1()))->dump();

Never use it :-)
new() is a keyword and though not allowed as a method name. But anyway, think an MyClass::create(). With late static binding this might be implemented in the parent class, something like this works fine:

class Parent
{
public static function create()
{
$arguments = func_get_args();
$class = new ReflectionClass(get_called_class());
return $class->newInstanceArgs($arguments);
}
}

class Child extends Parent
{}


This technique allows you to stick various parameters to the constructor.
Why not just simply create an identity function:

function identity ($x)
{
return $x;
}

Now you can just write

$result = identity (new SQL)
->select('*')
->from('users')
->where('username')
->like('travis%')
->execute();

... and you can still use type hints.

Nick: Trying to do Haskell in PHP is kind of ugly.
Mark - that wrapper does give you the flexibility, but adds more to the chain than just doing a straight up SQL function whereas the function makes it appear like Python'ish code where you can instantiate and chain immediately.

Lars, David - the static method looks clunky to me. SomeClass::getInstance()->continue->on() does not read as fluently to me as SomeClass()->continue->on(). There's also the issue that getInstance() implies an instance is available, thus its use on the Singletons. It could be getNewInstance(), but there again, more clutter to the fluent API.

Greg - your code got eaten by the HTML guard dog :-) I'm assuming it looked something like this:

$new = 'foobar';

Having the ability to do:

public function new() { }

Does make since as the parser only needs be smart enough to realize that new came after function, but it is just one extra syntax rule making the parser slower. Marginally slow, definitely; but still slower. Personally, I'd be for removing the new keyword all together and classes to be instantiated the way they are in Python:

$obj = SomeClass();

Of course, I bet its a bigger performance hit to make the parser analyze everything to see if it should be a new instance, but a boy can still dream, right?

It flows but isn't terribly fluent:


$result = call_user_func_array(
array(new Sql, 'select'),
array('*'))
->from('users')
->where('username')
->like('travis%')
->execute();


Encapsulating object creation often has benefits. I think I'd usually be doing something like this - no "new Foo" to trip over:

$result = $this->injector
->instantiate('Sql')
->select('*')
->from('users')
->where('username')
->like('travis%')
->execute();

$result = $this->assembler
->getSql()
->select('*')
->from('users')
->where('username')
->like('travis%')
->execute();
@Travis,
You're right, and this is precisely the purpose of what I'm getting at: encapsulating the chained objects inside a wrapper. This obscures code-introspection because all the calls are made using reflection. But the trade-off is that you can make chained API calls and not worry about unexpected return values in the middle of the chain, which might cause your program to terminate in a user-unfriendly manner.

@Mark Twain,
:) I'm only stealing names from Haskell, and parts of ideas.
It is to complicated in my opinion.

Leave a comment


Your email address will not be revealed on this site.

Your URL will be displayed.
(Line breaks become <br />)
(Name, email & website)
(Allow users to contact you through a message form (your email will not be revealed.)