I need to create anonymous object and call it's method
$obj = new stdClass();
$obj->Greeting = function (string $d){return "Hello ".$d;};
$greetings = $obj->Greeting("world!");
But when I try to execute this code I get an error
Call to undefined method stdClass::Greeting()
What's wrong?
You created a stdClass object, not an anonymous one:
$obj = new class () {
public function Greeting(string $d)
{
return "Hello $d";
}
};
echo $greetings = $obj->Greeting("world!");
output:
Hello world!
What's wrong?
Nothing, let's just ask, what's behind or happening here?
The stdClass is used for "empty" objects in PHP or when casting an array to an object ($obj = (object) ['hello' => 'world']).
By default it has no properties (like in $obj = new stdClass;) and also no methods. It is empty in terms of both of these.
Properties can be added dynamically to an stdClass object - but not functions as class methods have to be declared in PHP before instantiating the object.
So the function in your case is a property (PHP has two bags here: one for properties and one for functions) and not a new method dynamically added to it (class MyClass { function method() {...} }).
Let's compare with the original example and provoke the error again:
$obj = new stdClass();
$obj->Greeting = function (string $d) {
return "Hello $d";
};
$greetings = $obj->Greeting("world!");
PHP Fatal error: Uncaught Error: Call to undefined method stdClass::Greeting()
However:
echo $greetings = ($obj->Greeting)("world!");
# #
works, the output:
Hello world!
because PHP is now guided to "call" the ($obj->Greeting) property indirectly, so not looking for the stdClass::Greeting method first.
Normally you don't want that indirection, therefore the suggestion to use the anonymous class instead.
Change
$obj->Greeting("world!");
to
($obj->Greeting)("world!");
or use call_user_func() :
call_user_func($obj->Greeting, 'world!')
Related
are there ways to pass a "method reference" to a function that accept a callback?
Eg.:
function ex($callback){
$callback();
}
$obj = (object) ['f' => function(){echo "hello";}];
ex($obj->a)
Obviously this is a very simplistic case, image having a complex object $obj with parameters inside
PHP does not work like Javascript.
This code:
$obj = (object) ['f' => function(){echo "hello";}];
Does not define "an object with method f()" it defines "an object with property f that happens to be a callable type".
Calling $obj->f(); directly results in:
Fatal error: Uncaught Error: Call to undefined method stdClass::f()
It would need to be specially handled like:
$func = $obj->f;
$func();
Or:
($obj->f)();
While not a wholly accurate statement, you could consider it that callable types need the equivalent of "being dereferenced" before they can actually be called.
You example is something of a "successful error" in that it attempts to use "first class functions" which PHP does not have, but on a malformed object that allows this to not fail.
Using a proper class:
function ex($callback){
$callback();
}
class Example {
public function f_instance() {
echo "instance call\n";
}
public static function f_static() {
echo "static call\n";
}
}
ex(['Example', 'f_static']);
$e = new Example();
ex([$e, 'f_instance']);
Output:
static call
instance call
Ref: https://www.php.net/manual/en/language.types.callable.php
When you create a method that returns a closure in PHP:
class ExampleClass {
public function test() {
$example = 10;
return function() use ($example) {
return $example;
};
}
}
The result of print_r contains this (the class whose method created the closure) and static, which appears to be the values bound within the use () statement of the closure:
$instance = new ExampleClass();
$closure = $instance->test();
print_r($closure);
Producing:
Closure Object (
[static] => Array (
[example] => 10
)
[this] => ExampleClass Object()
)
However I cannot for the life of me work out how to capture these values. It is not possible to use any form of property accessor (e.g. $closure->static or $closure->{'static'}) without receiving the following:
PHP Fatal error: Uncaught Error: Closure object cannot have properties in XYZ.
Array access notation obviously does not work either:
PHP Fatal error: Uncaught Error: Cannot use object of type Closure as array in XYZ.
JSON encoding the object, besides this making the values useless were they objects, provides an empty JSON object {} and using the ReflectionFunction class does not provide access to these items.
The closure documentation does not provide any means of accessing these values at all either.
Outside of doing something shameful like output buffering and parsing print_r or similar, I cannot actually see a way to get these values.
Am I missing something obvious?
Note: The use-case is for implementing memoization and these values would be extremely beneficial in identifying whether or not the call matched a previous cached call.
It seems you may have overlooked some of the ReflectionFunction methods.
Take a look at the ReflectionFunction::getClosureThis() method. I tracked it down by looking through the PHP 7 source code by doing a search for the zend_get_closure_this_ptr() which is defined in zend_closures.c.
The manual currently doesn't have a lot of documentation for this function. I'm using 7.0.9; try running this code based on your example:
class ExampleClass {
private $testProperty = 33;
public function test() {
$example = 10;
return function() use ($example) {
return $example;
};
}
}
$instance = new ExampleClass();
$closure = $instance->test();
print_r($closure);
$func = new ReflectionFunction($closure);
print_r($func->getClosureThis());
You should get output similar to
Closure Object
(
[static] => Array
(
[example] => 10
)
[this] => ExampleClass Object
(
[testProperty:ExampleClass:private] => 33
)
)
ExampleClass Object
(
[testProperty:ExampleClass:private] => 33
)
Regarding the closure static variables, these are returned with ReflectionFunction::getStaticVariables():
php > var_dump($func->getStaticVariables());
array(1) {
["example"]=>
int(10)
}
In order to get value of $example youn can try this
<?php
class ExampleClass {
public function test() {
$example = 10;
return function() use ($example) {
return $example;
};
}
}
$instance = new ExampleClass();
$number = call_user_func($instance->test(),null);
I found this solution into a Laravel framework and works for me.
call_user_func helps you.
Just wondering why something like this doesn't work:
public function address($name){
if(!isset($this->addresses[$name])){
$address = new stdClass();
$address->city = function($class = '', $style = ''){
return $class;
};
$this->addresses[$name] = $address;
}
return $this->addresses[$name];
}
Calling it like echo $class->address('name')->city('Class') should just echo Class, however I get Fatal error: Call to undefined method stdClass::city()
I can find a better way to do this, because this will get messy, but I'm wondering what I might be doing wrong there, or if PHP doesn't support this and why.
PHP is right when invoke fatal error Call to undefined method stdClass::city() because object $class->address('name') has no method city.
Intead, this object has property city which is instance of Closure Class (http://www.php.net/manual/en/class.closure.php)
You can verify this: var_dump($class->address('name')->city)
I found the way to call this anonymous function is:
$closure = $class->address('name')->city;
$closure('class');
Hope this helps!
Sadly it is not possible within stdClass, but there is a workaround -- PHP Anonymous Object.
// define by passing in constructor
$anonim_obj = new AnObj(array(
"foo" => function() { echo "foo"; },
"bar" => function($bar) { echo $bar; }
));
$anonim_obj->foo(); // prints "foo"
$anonim_obj->bar("hello, world"); // prints "hello, world"
AFAIK, this is not supported by PHP, and you must use the call_user_func() or call_user_func_array() functions to call closures assigned to class properties (usually you can use __call() to do this, but in your case, the class is stdClass, so this isn't possible).
I was trying to debug a PHP script when I came across a declaration like:
$cart = new form;
$$cart = $cart->function();
What is $$cart?
What PHP does when you declare $$cart, is try to get the string value of the $cart object, and use that as the name for this variable variable. This means it'd have to call the __toString() magic method of its class.
If there is no __toString() method in the class, this will cause a catchable fatal error:
Catchable fatal error: Object of class MyClass could not be converted to string...
Otherwise, the name of the $$cart variable variable is the string value of the object as returned by that magic method.
An example with the __toString() magic method implemented (different classes/names but similar to your example calling code):
class MyClass {
public function __toString() {
return 'foo';
}
public function some_method() {
return 'bar';
}
}
$obj = new MyClass();
$$obj = $obj->some_method();
echo (string) $obj, "\n"; // foo
echo $$obj; // bar
the double $ is used for a variable variable.
essentially what this entails is the second $ along with the word is a variable the value of which is used for the name of the first $
i.e.-
$first = "second";
$second = 'Goodbye';
echo $$first; // Goodbye
Take the following code as an example:
class xpto
{
public function __get($key)
{
return $key;
}
}
function xpto()
{
static $instance = null;
if (is_null($instance) === true)
{
$instance = new xpto();
}
return $instance;
}
echo xpto()->haha; // returns "haha"
Now, I'm trying to archive the same result but without have to write the xpto class. My guess is I should have to write something like this:
function xpto()
{
static $instance = null;
if (is_null($instance) === true)
{
$instance = new stdClass();
}
return $instance;
}
echo xpto()->haha; // doesn't work - obviously
Now, is it possible to add __get() magic functionality to the stdClass object? I guess not, but I'm not sure.
No, it is not possible. You cannot add anything to stdClass. Also, unlike Java, where every object is a direct or indirect subclass of Object, this is not the case in PHP.
class A {};
$a = new A();
var_dump($a instanceof stdClass); // will return false
What are you really trying to achieve? Your question sounds a bit like "I want to close the door of my car, but without having a car" :-).
The OP looks like they are trying to achieve a singleton pattern using a function in the global scope which is probably not the correct way to go, but anyway, regarding Cassy's answer, "You cannot add anything to stdClass" - this is not true.
You can add properties to the stdClass simply by assigning a value to them:
$obj = new stdClass();
$obj->myProp = 'Hello Property'; // Adds the public property 'myProp'
echo $obj->myProp;
However, I think you need PHP 5.3+ in order to add methods (anonymous functions / closures), in which case you might be able to do something like the following. However, I've not tried this. But if this does work, can you do the same with the magic __get() method?
UPDATE: As noted in the comments, you cannot dynamically add methods in this way. Assigning an anonymous function (PHP 5.3+) does just that and simply assigns a function (strictly a closure object) to a public property.
$obj = new stdClass();
$obj->myMethod = function($name) {echo 'Hello '.$name;};
// Fatal error: Call to undefined method stdClass::myMethod()
//$obj->myMethod('World');
$m = $obj->myMethod;
$m('World'); // Output: Hello World
call_user_func($obj->myMethod,'Foo'); // Output: Hello Foo