Take a look at this code, please:
$array = array(
'action' => function () { echo "this works"; }
);
class Test {
public $array = array(
"action" => function () { echo "this doesn't"; }
);
}
The first function literal parses fine, but the second - the one inside the class - triggers a syntax error:
Parse error: syntax error, unexpected 'function' (T_FUNCTION)...
Can somebody explain this to me? Is this a bug?
EDIT: This is the latest PHP: 5.6.6
From the class it's a property !
Rule from properties :
Declaration may include an initialization, but this
initialization must be a constant value--that is, it must be able to
be evaluated at compile time and must not depend on run-time
information in order to be evaluated.
http://php.net/manual/en/language.oop5.properties.php
I dont have chance to test Your code on PHP 5.6.6, but I think this code resolve Your problem.
class Test{
public $array;
function __construct(){
$this -> array = array(
'action' => function (){
echo 'It works too';
}
);
}
}
$test = new Test();
$test -> array['action']();
Try it like this, let me know if this works for you
<?php
$array = array('action' => function () { echo "this works"; });
class Test {
public $arr;
function __construct() {
$this->arr = array("action" => function () { echo "this works too"; });
}
function getArr(){
var_dump($this->arr);
}
}
var_dump($array);
$obj = new Test();
$obj->getArr();
Related
I wonder whether this is a bug or normal. Let’s say I have a class with some magical functions:
class Foo {
public function __toString() {
return '`__toString` called.';
}
public function __get($key) {
return '`__get(' . $key . ')` called.';
}
public function __invoke($x = "") {
return '`__invoke(' . $x . ')` called.';
}
}
And then create an instance in an object property like this:
$object = (object) [
'foo' => 'bar',
'baz' => new Foo
];
Then test it:
echo $object->baz;
echo $object->baz->qux;
echo $object->baz('%'); // :(
It is broken in the last echo: Call to undefined method stdClass::baz()
Currently, the only solution I can do is to store the __invoke part in a temporary variable and then call that variable as a function like this:
$x = $object->baz;
echo $x('%'); // :)
It works fine when I instantiate the class in an array property:
$array = [
'baz' => new Foo
];
echo $array['baz'];
echo $array['baz']->qux;
echo $array['baz']('%'); // :)
By the way, I need this ability on my object for something related to API:
$foo = (object) ['bar' => new MyClass];
echo $foo->bar; → should trigger __toString
echo $foo->bar->baz; → should trigger __get
echo $foo->bar(); → should trigger __invoke
echo $foo->bar->baz(); → should trigger __call
All of them should return a string.
Can this be done in PHP completely? Thanks.
No can do.
The line in question is simply ambigous, and the error message shows you how ... It is more logical to try to access the baz() method of your $object object.
That's just the context given by the parser when it sees $object->baz()
As already mentioned in the comments, you can remove that ambiguity, help the parser by telling it that $object->baz is itself an expression that needs to be executed first:
($object->baz)('arg');
PHP is also itself a program, and has to know how to execute something before executing it. If it could blindly try every possible "magic" method on every object in a $foo->bar->baz->qux chain, then it wouldn't be able to tell you what the error is when it is encountered - it would just silently crash.
I have solved my problem by detecting the existence of an __invoke method inside the __call method of a class.
class MyStdClass extends stdClass {
protected $data = [];
public function __construct(array $array) {
$this->data = $array;
}
public function __get($key) {
return isset($this->data[$key]) ? $this->data[$key] : null;
}
public function __call($key, $args = []) {
if (isset($this->data[$key])) {
$test = $this->data[$key];
// not an object = not an instance, skip!
if (!is_object($test)) {
return $this->__get($key);
}
if (!empty($args) && get_class($test) && method_exists($test, '__invoke')) {
// or `return $test(...$args)`
return call_user_func([$test, '__invoke'], ...$args);
}
}
return $this->__get($key);
}
public function __set($key, $value = null) {
$this->data[$key] = $value;
}
public function __toString() {
return json_encode($this->data);
}
public function __isset($key) {}
public function __unset($key) {}
}
So, instead of converting the array into object with (object), here I use:
$object = new MyStdClass([
'foo' => 'bar',
'baz' => new Foo
]);
Is it possible to add a method/function in this way, like
$arr = array(
"nid"=> 20,
"title" => "Something",
"value" => "Something else",
"my_method" => function($arg){....}
);
or maybe like this
$node = (object) $arr;
$node->my_method=function($arg){...};
and if it's possible then how can I use that function/method?
This is now possible to achieve in PHP 7.1 with anonymous classes
$node = new class {
public $property;
public function myMethod($arg) {
...
}
};
// and access them,
$node->property;
$node->myMethod('arg');
You cannot dynamically add a method to the stdClass and execute it in the normal fashion. However, there are a few things you can do.
In your first example, you're creating a closure. You can execute that closure by issuing the command:
$arr['my_method']('Argument')
You can create a stdClass object and assign a closure to one of its properties, but due to a syntax conflict, you cannot directly execute it. Instead, you would have to do something like:
$node = new stdClass();
$node->method = function($arg) { ... }
$func = $node->method;
$func('Argument');
Attempting
$node->method('Argument')
would generate an error, because no method "method" exists on a stdClass.
See this SO answer for some slick hackery using the magic method __call.
Since PHP 7 it is also possible to directly invoke an anonymous function property:
$obj = new stdClass;
$obj->printMessage = function($message) { echo $message . "\n"; };
echo ($obj->printMessage)('Hello World'); // Hello World
Here the expression $obj->printMessage results in the anonymous function which is then directly executed with the argument 'Hello World'. It is however necessary to put the function expression in paranetheses before invoking it so the following will still fail:
echo $obj->printMessage('Hello World');
// Fatal error: Uncaught Error: Call to undefined method stdClass::printMessage()
Another solution would be to create an anonymous class and proxy the call via the magic function __call, with arrow functions you can even keep reference to context variables:
new Class ((new ReflectionClass("MyClass"))->getProperty("myProperty")) {
public function __construct(ReflectionProperty $ref)
{
$this->setAccessible = fn($o) => $ref->setAccessible($o);
$this->isInitialized = fn($o) => $ref->isInitialized($o);
$this->getValue = fn($o) => $ref->getValue($o);
}
public function __call($name, $arguments)
{
$fn = $this->$name;
return $fn(...$arguments);
}
}
class myclass {
function __call($method, $args) {
if (isset($this->$method)) {
$func = $this->$method;
return call_user_func_array($func, $args);
}
}
}
$obj = new myclass();
$obj->method = function($var) { echo $var; };
$obj->method('a');
Or you can create defult class and use...
Hi I've been playing around with phps filter class getting and have come across a snag with the filter_callback filter.
the following rough bit of code works but shows an error every time
Warning: filter_var() [function.filter-var]: First argument is expected to be a valid callback in /Users/Rob/sites/test_val.php on line 12
class test
{
public function callback($string)
{
$var = filter_var($string, FILTER_CALLBACK, array('options' => $this->foo($string)));
}
public function foo($string){
echo $string;
}
}
$test = new test();
$string = 'test';
$tested = $test->callback($string);
am i calling the function correctly or is there a different way?
$this->foo($string)
...should be...
array($this, 'foo')
When using a method as a callback, you need to provide the reference in this manner.
Documentation.
This code works for me :)
<?php
class myClass {
public function myFunc($var){
return filter_var($var, FILTER_CALLBACK, array('options'=> 'self::myCallback'));
}
public function myCallback(){
return true;
}
}
$obj = new myClass();
var_dump($obj->myFunc("myname#gmail.com"));
//output:- bool(true)
?>
echo filter_var('wo9w9w9', FILTER_CALLBACK, array('options' => array(new MyFilter(), 'filter1'))) . PHP_EOL;
options You can directly transfer object instance, like $this or new self ,
For example, I have such a code:
<?php
$A = array(
'echoSmth' => function(){
echo 'Smth';
}
);
$A['echoSmth'](); // 'Smth'
?>
It works fine!
But If $A is not just a variable, but a Class method - than this doesn't work:
<?php
class AA {
public $A = array(
'echoSmth' => function(){ // Parse Error here!
echo 'Smth';
}
);
}
// Fixed call:
$t = new AA();
$t->A['echoSmth']();
// no matter what call, because error occurs early - in describing of class
?>
Why doesn't it work?
It displays:
Parse error: syntax error, unexpected T_FUNCTION
P.S. Sorry, I've made some mistakes in the way I call the method, I was in hurry. But there's no matter how I call. Error ocurrs even if I just declare class, without calling
As far as I'm aware, you can't have anything dynamic when defining a class member, you can however set it dynamically as below. So basically, you can't do it for the same reason that you can't do this: public $A = functionname();
Also, your call signature was incorrect, I've fixed it in my example.
<?php
class AA {
public $A = array();
public function __construct() {
$a = function() {
echo 'Smth';
};
$this->A['echoSmth'] = $a;
}
}
$t = new AA();
$t->A['echoSmth']();
Alternatively, you could define the whole array within __construct(), containing the method (so basically shift your code).
I got this to work. Not sure why direct declaration is not permitted though.
class AA {
var $A;
public function __construct() {
$this->A = array(
'echoSmth' => function(){
echo 'Smth';
}
);
}
}
$t = new AA();
$t->A['echoSmth']();
EDIT: I also saw and corrected $t->$a first, but I needed to move the declaration as well to make it work.
well , it kinda works ...
<?php
class Bleh
{
public $f = array();
public function register( $name , $func )
{
$this->f[ $name ] = $func;
}
}
$foo = new Bleh;
$foo->register( 'bar' , function(){ echo 'foobar'; } );
$foo->f['bar']();
?>
In PHP, I am able to use a normal function as a variable without problem, but I haven't figured out how to use a static method. Am I just missing the right syntax, or is this not possible?
(EDIT: the first suggested answer does not seem to work. I've extended my example to show the errors returned.)
function foo1($a,$b) { return $a/$b; }
class Bar
{
static function foo2($a,$b) { return $a/$b; }
public function UseReferences()
{
// WORKS FINE:
$fn = foo1;
print $fn(1,1);
// WORKS FINE:
print self::foo2(2,1);
print Bar::foo2(3,1);
// DOES NOT WORK ... error: Undefined class constant 'foo2'
//$fn = self::foo2;
//print $fn(4,1);
// DOES NOT WORK ... error: Call to undefined function self::foo2()
//$fn = 'self::foo2';
//print $fn(5,1);
// DOES NOT WORK ... error: Call to undefined function Bar::foo2()
//$fn = 'Bar::foo2';
//print $fn(5,1);
}
}
$x = new Bar();
$x->UseReferences();
(I am using PHP v5.2.6 -- does the answer change depending on version too?)
PHP handles callbacks as strings, not function pointers. The reason your first test works is because the PHP interpreter assumes foo1 as a string. If you have E_NOTICE level error enabled, you should see proof of that.
"Use of undefined constant foo1 - assumed 'foo1'"
You can't call static methods this way, unfortunately. The scope (class) is relevant so you need to use call_user_func instead.
<?php
function foo1($a,$b) { return $a/$b; }
class Bar
{
public static function foo2($a,$b) { return $a/$b; }
public function UseReferences()
{
$fn = 'foo1';
echo $fn(6,3);
$fn = array( 'self', 'foo2' );
print call_user_func( $fn, 6, 2 );
}
}
$b = new Bar;
$b->UseReferences();
In php 5.2, you can use a variable as the method name in a static call, but to use a variable as the class name, you'll have to use callbacks as described by BaileyP.
However, from php 5.3, you can use a variable as the class name in a static call. So:
class Bar
{
public static function foo2($a,$b) { return $a/$b; }
public function UseReferences()
{
$method = 'foo2';
print Bar::$method(6,2); // works in php 5.2.6
$class = 'Bar';
print $class::$method(6,2); // works in php 5.3
}
}
$b = new Bar;
$b->UseReferences();
?>
You could use the full name of static method, including the namespace.
<?php
function foo($method)
{
return $method('argument');
}
foo('YourClass::staticMethod');
foo('Namespace\YourClass::staticMethod');
The name array array('YourClass', 'staticMethod') is equal to it. But I think the string may be more clear for reading.
In PHP 5.3.0, you could also do the following:
<?php
class Foo {
static function Bar($a, $b) {
if ($a == $b)
return 0;
return ($a < $b) ? -1 : 1;
}
function RBar($a, $b) {
if ($a == $b)
return 0;
return ($a < $b) ? 1 : -1;
}
}
$vals = array(3,2,6,4,1);
$cmpFunc = array('Foo', 'Bar');
usort($vals, $cmpFunc);
// This would also work:
$fooInstance = new Foo();
$cmpFunc = array('fooInstance', 'RBar');
// Or
// $cmpFunc = array('fooInstance', 'Bar');
usort($vals, $cmpFunc);
?>
Coming from a javascript background and being spoiled by it, I just coded this:
function staticFunctionReference($name)
{
return function() use ($name)
{
$className = strstr($name, '::', true);
if (class_exists(__NAMESPACE__."\\$className")) $name = __NAMESPACE__."\\$name";
return call_user_func_array($name, func_get_args());
};
}
To use it:
$foo = staticFunctionReference('Foo::bar');
$foo('some', 'parameters');
It's a function that returns a function that calls the function you wanted to call. Sounds fancy but as you can see in practice it's piece of cake.
Works with namespaces and the returned function should work just like the static method - parameters work the same.
This seems to work for me:
<?php
class Foo{
static function Calc($x,$y){
return $x + $y;
}
public function Test(){
$z = self::Calc(3,4);
echo("z = ".$z);
}
}
$foo = new Foo();
$foo->Test();
?>
In addition to what was said you can also use PHP's reflection capabilities:
class Bar {
public static function foo($foo, $bar) {
return $foo . ' ' . $bar;
}
public function useReferences () {
$method = new ReflectionMethod($this, 'foo');
// Note NULL as the first argument for a static call
$result = $method->invoke(NULL, '123', 'xyz');
}
}
"A member or method declared with static can not be accessed with a variable that is an instance of the object and cannot be re-defined in an extending class"
(http://theserverpages.com/php/manual/en/language.oop5.static.php)