views:

1966

answers:

4

An idiom commonly used in OO languages like Python and Ruby is instantiating an object and chaining methods that return a reference to the object itself, such as:

s = User.new.login.get_db_data.get_session_data

In PHP, it is possible to replicate this behavior like so:

$u = new User();
$s = $u->login()->get_db_data()->get_session_data();

Attempting the following results in syntax error, unexpected T_OBJECT_OPERATOR:

$s = new User()->login()->get_db_data()->get_session_data();

It seems like this could be accomplished using static methods, which is probably what I'll end up doing, but I wanted to check the lazyweb: Is there actually a clean, simple way to instantiate PHP classes "inline" (as shown in the above snippet) for this purpose?

If I do decide to use static methods, is it too sorcerous to have a class's static method return an instantiation of the class itself? (Effectively writing my own constructor-that-isn't-a-constructor?) It feels kind of dirty, but if there aren't too many scary side effects, I might just do it.

I guess I could also pre-instantiate a UserFactory with a get_user() method, but I'm curious about solutions to what I asked above.

+3  A: 
<?php

    class User
    {
     function __construct()
     {
     }

     function Login()
     {
      return $this;
     }

     function GetDbData()
     {
      return $this;
     }

     function GetSession()
     {
      return array("hello" => "world");
     }
    }

    function Create($name)
    {
     return new $name();
    }

    $s = Create("User")->Login()->GetDbData()->GetSession();

    var_dump($s);
?>

This is a possible solution :) Of course, you should choose a better name for the function...

Or if you don't mind a little overhead:

<?php

    class User
    {
     function __construct($test)
     {
      echo $test;
     }
...
    }

    function CreateArgs($name)
    {
     $ref = new ReflectionClass($name);
     return $ref->newInstanceArgs(array_slice(func_get_args(), 1));
    }

    $s = CreateArgs("User", "hi")->Login()->GetDbData()->GetSession();

    var_dump($s);
?>
nlaq
Thank you! I accepted the other fellow's answer, but if I could accept two, I would accept yours as well. tobyhede's point about bending PHP to do something it wasn't designed to do just felt particularly salient to me.
Max
+2  A: 

The only way you can get something similar is with a factory or singleton static method. For example:

class User
{
    //...
    /**
     *
     * @return User
     */
    public static function instance()
    {
     $args = func_get_args();
     $class = new ReflectionClass(__CLASS__);
     return $class->newInstanceArgs($args);
    }
    //...
}

That uses the PHP5 reflection API to create a new instance (using any args sent to ::instance()) and returns it allowing you to do the chaining:

$s = User::instance()->login()->get_db_data()->get_session_data();

By the way that code is flexible enough that the only thing you'll have to change when copying that static method is the PHPDoc comment's @return.


If you want to prematurely optimize your code like our friend Nelson you can replace the contents of User::instance() with:

return new self();
dcousineau
Using the reflection is not worth the overhead when doing the static-method approach. You should not add overhead where overhead is not required ;)
nlaq
Or we can follow standards to send in arguments so you don't have to constantly update the instance() method when you alter the __construct()
dcousineau
It is simply idiotic to do something knowing that the same thing can be done faster. "prematurely optimizing" is different from "using common sense." And my solution does not require copy+pasting code either. However - I bet we can both agree that a factory pattern should of been used anyway :)
nlaq
+5  A: 

All of these proposed solutions complicate your code in order to bend PHP to accomplish some syntactic nicety. Wanting PHP to be something it's not (like good) is the path to madness.

I would just use:

$u = new User();
$s = $u->login()->get_db_data()->get_session_data();

It is clear, relatively concise and involves no black magic that can introduce errors.

And of course, you could always move to Ruby or Python. It will change your life.

  • And yeah, I am harsh on PHP. I use it every day. Been using it for years. The reality is that it has accreted, rather than been designed and it shows.
Toby Hede
I prefer Ruby, then Python, then PHP, but I regularly program in all three.
Max
Yeah ... the BIGGEST lesson I have learned with PHP is that you have to cut with the grain. Work with PHP and it is awesome, but start wanting it to be different and you are going to dig your own hole.
Toby Hede
Well at least I now have confirmation that you can't do this. Can you point me to where in the PHP language reference that says why you can't do this? Argh.
Joseph Kingry
A: 

There's no reason to stick together a hack (to work around the syntax issue) and the object creation code itself.

Since a new-expression cannot be used as the left member of your object operator, but a function call expression can, you only need to wrap your object in a call to a function that returns its own argument:

function hack($obj) { return $obj; }

$mail = hack(new Zend_Mail())
  -> setBodyText('This is the text of the mail.')
  -> setFrom('[email protected]', 'Some Sender')
  -> addTo('[email protected]', 'Some Recipient')
  -> setSubject('TestSubject');