“Inline” Class Instantiation in PHP? (For Ease of

2020-03-16 12:46发布

问题:

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.

回答1:

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.


回答2:

<?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);
?>


回答3:

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('somebody@example.com', 'Some Sender')
  -> addTo('somebody_else@example.com', 'Some Recipient')
  -> setSubject('TestSubject');


回答4:

A simple shortcut to

$Obj = new ClassName();
$result = $Obj->memberFunction();

is

$result = (new ClassName())->memberFunction();


回答5:

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();


回答6:

<?php 
//PHP 5.4+ class member access on instantiation support.
$s = (new User())->login()->get_db_data()->get_session_data();