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
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.
function Just($x) { return $x; }
and then write all your code like
Just(new XML())->etc()->etc();
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.
Hans - nope. "new" is a keyword in PHP that can't be used for anything other than creating a new instance. Thus the issue...
Maybe you meant it nullifies your IDE's ability to inspect and do code-completion? In that case, yes, it does.
$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()
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')... The above code works just fine.
$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
Here is "crazy" method for calling class methods w/o creating variable:
unserialize(serialize(new Class1()))->dump();
Never use it :-)
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.
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.
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();
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.
