Asked  1 Year ago    Answers:  5   Viewed   15 times

I would like to be able to call a closure that I assign to an object's property directly without reassigning the closure to a variable and then calling it. Is this possible?

The code below doesn't work and causes Fatal error: Call to undefined method stdClass::callback().

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback();

 Answers

3

As of PHP7, you can do

$obj = new StdClass;
$obj->fn = function($arg) { return "Hello $arg"; };
echo ($obj->fn)('World');

or use Closure::call(), though that doesn't work on a StdClass.


Before PHP7, you'd have to implement the magic __call method to intercept the call and invoke the callback (which is not possible for StdClass of course, because you cannot add the __call method)

class Foo
{
    public function __call($method, $args)
    {
        if(is_callable(array($this, $method))) {
            return call_user_func_array($this->$method, $args);
        }
        // else throw exception
    }
}

$foo = new Foo;
$foo->cb = function($who) { return "Hello $who"; };
echo $foo->cb('World');

Note that you cannot do

return call_user_func_array(array($this, $method), $args);

in the __call body, because this would trigger __call in an infinite loop.

Thursday, April 1, 2021
 
3

You can implement your own way to iterate over object just by implementing Iterator interface. By implementing methods next and current you define how to get current element and how to get the next one (but you will have to implement all methods).

For iteration use

foreach ($o as $k => $v) {
  print $k . '->' . $v . PHP_EOL;
}

Care to see some example? Did you understand it from link above?

On the other hand if you want to use your object as array, check ArrayObject interface or for simplier use ArrayAccess interface

Thursday, April 1, 2021
 
Corne
 
2

The usual way to check if the value of a property is the special value undefined, is:

if(o.myProperty === undefined) {
  alert("myProperty value is the special value `undefined`");
}

To check if an object does not actually have such a property, and will therefore return undefined by default when you try and access it:

if(!o.hasOwnProperty('myProperty')) {
  alert("myProperty does not exist");
}

To check if the value associated with an identifier is the special value undefined, or if that identifier has not been declared. Note: this method is the only way of referring to an undeclared (note: different from having a value of undefined) identifier without an early error:

if(typeof myVariable === 'undefined') {
  alert('myVariable is either the special value `undefined`, or it has not been declared');
}

In versions of JavaScript prior to ECMAScript 5, the property named "undefined" on the global object was writeable, and therefore a simple check foo === undefined might behave unexpectedly if it had accidentally been redefined. In modern JavaScript, the property is read-only.

However, in modern JavaScript, "undefined" is not a keyword, and so variables inside functions can be named "undefined" and shadow the global property.

If you are worried about this (unlikely) edge case, you can use the void operator to get at the special undefined value itself:

if(myVariable === void 0) {
  alert("myVariable is the special value `undefined`");
}
Tuesday, June 1, 2021
 
lechup
 
4

Updated for PHP 7.2

PHP 7.2 introduced a behavioral change to converting numeric keys in object and array casts, which fixes this particular inconsistency and makes all the following examples behave as expected.

One less thing to be confused about!


Original answer (applies to versions earlier than 7.2.0)

PHP has its share of dark alleys that you really don't want to find yourself inside. Object properties with names that are numbers is one of them...

What they never told you

Fact #1: You cannot access properties with names that are not legal variable names easily

$a = array('123' => '123', '123foo' => '123foo');
$o = (object)$a;
echo $o->123foo; // error

Fact #2: You can access such properties with curly brace syntax

$a = array('123' => '123', '123foo' => '123foo');
$o = (object)$a;
echo $o->{'123foo'}; // OK!

Fact #3: But not if the property name is all digits!

$a = array('123' => '123', '123foo' => '123foo');
$o = (object)$a;
echo $o->{'123foo'}; // OK!
echo $o->{'123'}; // error!

Live example.

Fact #4: Well, unless the object didn't come from an array in the first place.

$a = array('123' => '123');
$o1 = (object)$a;
$o2 = new stdClass;
$o2->{'123'} = '123'; // setting property is OK

echo $o1->{'123'}; // error!
echo $o2->{'123'}; // works... WTF?

Live example.

Pretty intuitive, don't you agree?

What you can do

Option #1: do it manually

The most practical approach is simply to cast the object you are interested in back into an array, which will allow you to access the properties:

$a = array('123' => '123', '123foo' => '123foo');
$o = (object)$a;
$a = (array)$o;
echo $o->{'123'}; // error!
echo $a['123']; // OK!

Unfortunately, this does not work recursively. So in your case you 'd need to do something like:

$highlighting = (array)$myVar->highlighting;
$data = (array)$highlighting['448364']->Data;
$value = $data['0']; // at last!

Option #2: the nuclear option

An alternative approach would be to write a function that converts objects to arrays recursively:

function recursive_cast_to_array($o) {
    $a = (array)$o;
    foreach ($a as &$value) {
        if (is_object($value)) {
            $value = recursive_cast_to_array($value);
        }
    }

    return $a;
}

$arr = recursive_cast_to_array($myVar);
$value = $arr['highlighting']['448364']['Data']['0'];

However, I 'm not convinced that this is a better option across the board because it will needlessly cast to arrays all of the properties that you are not interested in as well as those you are.

Option #3: playing it clever

An alternative of the previous option is to use the built-in JSON functions:

$arr = json_decode(json_encode($myVar), true);
$value = $arr['highlighting']['448364']['Data']['0'];

The JSON functions helpfully perform a recursive conversion to array without the need to define any external functions. However desirable this looks, it has the "nuke" disadvantage of option #2 and additionally the disadvantage that if there is any strings inside your object, those strings must be encoded in UTF-8 (this is a requirement of json_encode).

Tuesday, June 1, 2021
 
cbcp
 
2

There were two methods added to Groovy to aid serialization of Closures, dehydrate and rehydrate. Basically, they strip (and reconstruct) a Closure's owner, thisObject and delegate. In this example, you could do:

myMethodClosure.rehydrate( myClass2, myClass2, myClass2 )()

To get the output 2, however I'd be wary about doing this as it is not what the method was intended for and there could be serious unforeseen consequences.

A better solution would probably be to write a factory method that gets a method reference for the given instance of MyClass. There may be other -- better -- solutions, but it depends on the situation you are in (that I suspect is not shown by the example in the question)

Thursday, August 19, 2021
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :