What's the shortest way to bind variable $this to a callable object field?

244 views Asked by At

I bound the $this variable with Closure::bind() method (lines 12-13) to my getName() and getAge() methods (lines 4 and 7) so that they can refer to their own member fields (lines 2-3) in an instance of stdClass. It works, but I find this a bit tedious.

Is there any special variable or built-in function that I can refer to the current instance of stdClass inside my getName() and getAge() methods (lines 4 and 7) without calling Closure::bind() method? Or, is there any workaround to this, if any?

PHP Code

$human = (object) [
    'name' => 'John Doe',
    'age'  => 25,
    'getName' => function() {
        var_dump($this->name);
    },
    'getAge' => function() {
        var_dump($this->age);
    },
];

$human->getName = Closure::bind($human->getName, $human);
$human->getAge = Closure::bind($human->getAge, $human);

print_r($human);

($human->getName)();
($human->getAge)();

Sample Output:

stdClass Object
(
    [name] => John Doe
    [age] => 25
    [getName] => Closure Object
        (
            [this] => stdClass Object
 *RECURSION*
        )

    [getAge] => Closure Object
        (
             [this] => stdClass Object
 *RECURSION*
        )

)
string(8) "John Doe"
int(25)
2

There are 2 answers

3
KIKO Software On

I think you should make a better case for why you would want to do this. That said, the Closure::bind() is indeed a bit tedious. There is something you can do, but I doubt you will like it. It goes like this:

$human = (object) [
    'name' => 'John Doe',
    'age'  => 25,
    'getName' => function($object) {
        var_dump($object->name);
    },
    'getAge' => function($object) {
        var_dump($object->age);
    },
];

($human->getName)($human);
($human->getAge)($human);

See: PHP fiddle

Here you admit that, at the time of creation, the functions aren't aware that they are part of an object, when $this would make sense. Therefore you supply the needed object later as an argument, and everything is fine.

Again, creating a normal class would be preferable, if only because it is easier to understand. Remember, you write code to do something, but also because it is a way to communicate your intent to yourself and others.

I thought of another way to do this. Also not what you want, but it kind of works:

$human = (object) [
    'name' => 'John Doe',
    'age'  => 25,
    'getName' => function() {
        global $human;
        var_dump($human->name);
    },
    'getAge' => function() {
        global $human;
        var_dump($human->age);
    },
];

($human->getName)();
($human->getAge)();

See: PHP fiddle

2
user3840170 On

First of all: despite the question body calling them such, getName and getAge are not methods. In PHP, methods and fields live in separate namespaces: methods are always looked up on the class, whereas fields are looked up on the instance. If the call expressions looked like $human->getName() and $human->getAge() instead of ($human->getName)() and ($human->getAge)(), they would be looking up the corresponding stdClass methods, which of course don’t exist, so they would fail.

Furthermore, once a field value is read, the object where it was read from is discarded and not used any further; it makes no difference that the value obtained happens to be callable. Because the object is discarded, there is nothing to bind the $this variable to inside the closure. This is unlike JavaScript, where methods are exactly equivalent to fields, they share a single namespace, and invoking a function just looked up on an object binds that object as this within the invoked function’s body.

I think what you actually want to accomplish is best achieved with an anonymous class:

$human = new class {
    public $name = 'John Doe';
    public $age = 25;

    public function getName() {
        var_dump($this->name);
    }

    public function getAge() {
        var_dump($this->age);
    }
};

$human->getName();
$human->getAge();

This makes getName and getAge actually methods, which receive the appropriate $this binding.

You can even make your anonymous class a subclass of stdClass if you insist, though I see no particular reason why.