PHP 7 Execute object method as array element - php

I am a bit stuck with question why in PHP 7 dropped functionality of accesing object methods using array elements as method name.
E.g.:
$carObj = new Car();
$array = ['method'=>'getMilage', 'object'=>$carObj];
// FATAL HERE:
$mileage = $array['object']->$array['method']();
// WORKS OK:
$objName = $array['object'];
$metName = $array['method'];
$mileage = $objName->$metName();
This code works on PHP 5.6 for sure, however when switched to PHP 7.1 it throws now fatal. Failed to find anything re this in release notes and SO topics.
P.S. Originally found this in Magento 1.14.2.0 version upon PHP upgrade, as Varien library uses this code:
File: Varien/File/Uploader.php
//run validate callbacks
foreach ($this->_validateCallbacks as $params) {
if (is_object($params['object']) && method_exists($params['object'], $params['method'])) {
$params['object']->$params['method']($this->_file['tmp_name']);
}
}
Gives this:
Fatal error: Uncaught Error: Function name must be a string in
/var/www/html/lib/Varien/File/Uploader.php on line 274
--
EDIT #1:
You can TEST it here:
http://sandbox.onlinephpfunctions.com/code/d1d2d36f96a1b66ed7d740db328cd1f14cc2d7d8

(Note: I'm assuming the 'object'=>'carObj' declaration is supposed to be 'object'=>$carObj here - there's no way this code works in any version of PHP otherwise.)
The clue is in the Notice: Array to string conversion in... notice raised before the fatal error.
In PHP 5, the following statement:
$array['object']->$array['method']();
is evaluated like this:
$array['object']->{$array['method']}();
where $array['method'] is evaluated before calling it on the object.
In PHP 7, it's evaluated like this
($array['object']->$array)['method']();
where the $array property is looked up on the object first. Since it doesn't exist (obviously, since it's an array), a notice is thrown, and then a subsequent fatal error when the method itself can't be called.
If you want to preserve the PHP 5 behaviour, wrap some {} around the method name lookup:
$carObj = new Car();
$array = ['method'=>'getMilage', 'object'=>$carObj];
$mileage = $array['object']->{$array['method']}();
See https://3v4l.org/Is5lX
This is explained in a bit more detail here: http://php.net/manual/en/migration70.incompatible.php

Your code works for sure not in PHP5.6
The content of $array['object'] is an string and using the -> member operator on an string always throws an error
Call to a member function getMilage() on string in [...][...] on line [...]
The only way to get this to work is resolve the var with $ or - even better store the object inside the array and not simply the var.
$mileage = ${$array['object']}->$array['method']();
better solution
$array = ['method' => 'getMilage', 'object' => $carObj];
$mileage = $array['object']->{$array['method']}();
Sidenote: in php7 they changed the evaluation order in case of ambiguities - so you have to add explicitly {} around $array['method']. To prevent this, one would normaly extract first the object and method and then simply call it without the array dereferencing.
Btw. the Magento Varien code you posted also expects $params['object'] to be an array. There is even a is_object test to ensure, you couldn't pass just var names.
//run validate callbacks - even in php7
foreach ($this->_validateCallbacks as $params) {
if (is_object($params['object']) && method_exists($params['object'], $params['method'])) {
$object = $params['object'];
$method = $params['method'];
$object->$method($this->_file['tmp_name']);
}
}

You must add $ before the object variable
$carObj = new Car();
$array = ['method'=>'getMilage', 'object'=>'carObj'];
$object = $array['object'];
$method = $array['method'];
// FATAL HERE:
$mileage = $$object->$method();
// WORKS OK:
$objName = $array['object'];
$metName = $array['method'];
$mileage = $$objName->$metName();

Related

I get this error "Function name must be a string"

I have a PHP class where one of the private member is a callback to my log function (i.e. in PHP land, a function pointer is simply a string containing the name of the function to call).
self::$logferr = "msgfunc";
self::$logferr($res);
I get this error:
Fatal error: Function name must be a string
self::$logferr is equal to "msgfunc" which is my log function.
If I rewrite the code like this (on the same very class method):
$tmp = "msgfunc";
$tmp($res);
It works, the log function get called
Just wrap your variable in parenthesis, let PHP resolve the value first:
(self::$logferr)($res);
Proof of concept
You can use call_user_func. ref: this
call_user_func(self::$logferr, $res);
You should call it by using
self::{self::$logferr}($req)
Working example : https://3v4l.org/CYURS
Let's build a reproducible example:
class Foo {
private static $_loggerCallback;
static function setLogCallback(callable $loggerCallback) {
self::$_loggerCallback = $loggerCallback;
}
static function log(...$arguments) {
if (NULL !== self::$_loggerCallback) {
return self::$_loggerCallback(...$arguments);
}
return NULL;
}
}
Foo::setLogCallback(function() { echo 'success'; } );
Foo::log();
Output:
Notice: Undefined variable: _loggerCallback in /in/f3stL on line 13
Fatal error: Uncaught Error: Function name must be a string in /in/f3stL:13
The notice reports the actual mistake in this case. If you do not get something like it, you should check your error reporting configuration.
The notice shows that PHP looks for a local variable $_loggerCallback. It tries to execute $_loggerCallback(...$arguments). Here are different possibilities to make the call explicit.
Use parenthesis (PHP >= 7.0):
return (self::$_loggerCallback)(...$arguments);
Use a local variable (as you did):
$callback = self::$_loggerCallback;
return $callback(...$arguments);
A small advise. PHP support anonymous functions. You do not need a (global) function for a callback. This avoids calling to the function by name as well.

error when using variable class name and static method

Running PHP 5.4, so I wasn't expecting this, but I'm encountering the following error:
Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM)
Assume you have a variable of stdClass setup as follows:
$this->variable = new stdClass();
$this->variable->other = array('class' => 'helloworld');
Now, assume you want to access a static method of class helloworld:
// Standard call
$x = helloworld::my_static_method();
// Call with variable class name
$x = $this->variable->other['class']::my_static_method();
When calling the above using the variable class name, I receive the parsing error. What's odd, is that if I do the following, no error is presented:
$class = $this->variable->other['class'];
$x = $class::my_static_method();
To me this seems very odd, can anyone think of a reason why the class name isn't resolving correctly when using the first example versus the second?
can anyone think of a reason why the class name isn't resolving correctly when using the first example versus the second?
The PHP parser does not support such a syntax, and that's merely all. This is because the parser has grown historically. I can't give more reason than that.
It will be that with PHP 7 you can see some changes on these syntax details working more into your expected direction Uniform Variable Syntax:
($variable->other['class'])::my_static_method();
But until then, you can go around that with the help of call_user_func:
call_user_func([$variable->other['class'], 'my_static_method']);
call_user_func($variable->other['class'] . '::my_static_method');
Or as you wrote your own, by creating a variable:
$class = $variable->other['class'];
$class::my_static_method();
Or even a variable that looks like something different:
${(int)!${0}=$variable->other['class']}::my_static_method();
Related Material:
Interpolation (double quoted string) of Associative Arrays in PHP
This doesn't work ($this->variable->other['class']::my_static_method()) as it's essentially using a string as the class name directly. It works when you assign it to a variable first, as it's then being evaluated out as the class name instead.
You can also look into using ReflectionMethod invocation in order to call the method, in which case you wouldn't have to store the class name in a variable before using it. Here's the docs on that: http://php.net/manual/en/class.reflectionmethod.php and on the invoke method (you pass in NULL to indicate a static method) http://php.net/manual/en/reflectionmethod.invoke.php
Here are a couple examples of ways to invoke your function:
class helloworld{
public static function my_static_method($i = 0){
echo "Here: ".$i;
}
}
class Foo{
private $variable;
public function __construct(){
//Create a new class
$this->variable = new stdClass();
//Create a new property of the class, storing an array
$this->variable->other = array('class' => 'helloworld');
//Call function statically
$x = helloworld::my_static_method(1); //Outputs: "Here: 1"
//Store class name in a variable before use
$class = $this->variable->other['class'];
$y = $class::my_static_method(2); //Outputs: "Here: 2"
//Using a ReflectionMethod, you can call the function this way, too
$z = new ReflectionMethod($this->variable->other['class'], 'my_static_method');
$z->invoke(null, 3); //Outputs: "Here: 3"
}
}
//Instantiate new Foo class
new Foo();

PHP 5 code not working in PHP 4

I have this function in PHP 5:
function myPHPFunction($shows) {
foreach($shows as &$show) {
$showData = $this->Api->getID(null, $show->showId, false);
$show->Id = $showData->Id;
}
return $shows;
}
the $shows array has empty id, I am able to get the id with an Api call and when I return $shows at the end, the id field is populated. If I move this code to PHP 4:
function myPHPFunction($shows) {
foreach($shows as $show) {
$showData = $this->Api->getID(null, $show->showId, false);
$show->Id = $showData->Id;
}
return $shows;
}
The $shows array still has an empty id field when I return is. Does it have something to with &$ because the & does not work in PHP 4
Yes pass by reference was available only after PHP 4.0.4
There is no reference sign on a function call - only on function
definitions. Function definitions alone are enough to correctly pass
the argument by reference. As of PHP 5.3.0, you will get a warning
saying that "call-time pass-by-reference" is deprecated when you use &
in foo(&$a);. And as of PHP 5.4.0, call-time pass-by-reference was
removed, so using it will raise a fatal error.
Source

php version upgrade cause an error for old program

I just installed xampp, to run some old program (created 2 or more years ago) and I'm getting 3 errors I can't figure out.
Strict Standards: Only variables should be passed by reference in C:\xampp\htdocs\2010\web\core\route\route.php on line 117
public function loadClass($address,$ext='') {
$this->extname = preg_replace('/_/','/',$address,3);
line:117> $this->classname = end(explode('_',$address)).($e= $ext!='' ? '('.$ext.')' : '');
include_once(ROOT_ROUTE.'/'.$this->extname.'.php');
$this->newclass = new $this->classname;
return $this->newclass;
}
the line 117 i can't understand, it is not using passed by reference, why there is a error?
Because end() expects an argument passed by reference, you can't use it with a non-variable such as the direct result of another function call or construct.
Quoting from the argument definition in the manual:
This means you must pass it a real variable and not a function returning an array because only actual variables may be passed by reference.
Change
$this->classname = end(explode('_',$address)).($e= $ext!='' ? '('.$ext.')' : '');
to
$addressTemp = explode('_',$address);
$this->classname = end($addressTemp) . ($e= $ext!='' ? '('.$ext.')' : '');

Creating default object from empty value in PHP?

I see this error only after upgrading my PHP environment to PHP 5.4 and beyond. The error points to this line of code:
Error:
Creating default object from empty value
Code:
$res->success = false;
Do I first need to declare my $res object?
Your new environment may have E_STRICT warnings enabled in error_reporting for PHP versions <= 5.3.x, or simply have error_reporting set to at least E_WARNING with PHP versions >= 5.4. That error is triggered when $res is NULL or not yet initialized:
$res = NULL;
$res->success = false; // Warning: Creating default object from empty value
PHP will report a different error message if $res is already initialized to some value but is not an object:
$res = 33;
$res->success = false; // Warning: Attempt to assign property of non-object
In order to comply with E_STRICT standards prior to PHP 5.4, or the normal E_WARNING error level in PHP >= 5.4, assuming you are trying to create a generic object and assign the property success, you need to declare $res as an object of stdClass in the global namespace:
$res = new \stdClass();
$res->success = false;
This message has been E_STRICT for PHP <= 5.3. Since PHP 5.4, it was unluckilly changed to E_WARNING. Since E_WARNING messages are useful, you don't want to disable them completely.
To get rid of this warning, you must use this code:
if (!isset($res))
$res = new stdClass();
$res->success = false;
This is fully equivalent replacement. It assures exactly the same thing which PHP is silently doing - unfortunatelly with warning now - implicit object creation. You should always check if the object already exists, unless you are absolutely sure that it doesn't. The code provided by Michael is no good in general, because in some contexts the object might sometimes be already defined at the same place in code, depending on circumstances.
Simply,
$res = (object)array("success"=>false); // $res->success = bool(false);
Or you could instantiate classes with:
$res = (object)array(); // object(stdClass) -> recommended
$res = (object)[]; // object(stdClass) -> works too
$res = new \stdClass(); // object(stdClass) -> old method
and fill values with:
$res->success = !!0; // bool(false)
$res->success = false; // bool(false)
$res->success = (bool)0; // bool(false)
More infos:
https://www.php.net/manual/en/language.types.object.php#language.types.object.casting
If you put "#" character begin of the line then PHP doesn't show any warning/notice for this line. For example:
$unknownVar[$someStringVariable]->totalcall = 10; // shows a warning message that contains: Creating default object from empty value
For preventing this warning for this line you must put "#" character begin of the line like this:
#$unknownVar[$someStringVariable]->totalcall += 10; // no problem. created a stdClass object that name is $unknownVar[$someStringVariable] and created a properti that name is totalcall, and it's default value is 0.
$unknownVar[$someStringVariable]->totalcall += 10; // you don't need to # character anymore.
echo $unknownVar[$someStringVariable]->totalcall; // 20
I'm using this trick when developing. I don't like disable all warning messages becouse if you don't handle warnings correctly then they will become a big error in future.
Try this if you have array and add objects to it.
$product_details = array();
foreach ($products_in_store as $key => $objects) {
$product_details[$key] = new stdClass(); //the magic
$product_details[$key]->product_id = $objects->id;
//see new object member created on the fly without warning.
}
This sends ARRAY of Objects for later use~!
In PHP 7 anonymous objects can be created this way:
$res = new class {
public $success = false;
};
https://www.php.net/manual/en/language.oop5.anonymous.php
http://sandbox.onlinephpfunctions.com/code/ab774707a8219c0f35bdba49cc84228b580b52ee
First think you should create object
$res = new \stdClass();
then assign object with key and value thay
$res->success = false;
Try this:
ini_set('error_reporting', E_STRICT);
I had similar problem and this seem to solve the problem. You just need to initialize the $res object to a class . Suppose here the class name is test.
class test
{
//You can keep the class empty or declare your success variable here
}
$res = new test();
$res->success = false;
Starting from PHP 7 you can use a null coalescing operator to create a object when the variable is null.
$res = $res ?? new \stdClass();
$res->success = false;
A simple way to get this error is to type (a) below, meaning to type (b)
(a) $this->my->variable
(b) $this->my_variable
Trivial, but very easily overlooked and hard to spot if you are not looking for it.
You may need to check if variable declared and has correct type.
if (!isset($res) || !is_object($res)) {
$res = new \stdClass();
// With php7 you also can create an object in several ways.
// Object that implements some interface.
$res = new class implements MyInterface {};
// Object that extends some object.
$res = new class extends MyClass {};
}
$res->success = true;
See PHP anonymous classes.
Try using:
$user = (object) null;
I had a similar problem while trying to add a variable to an object returned from an API. I was iterating through the data with a foreach loop.
foreach ( $results as $data ) {
$data->direction = 0;
}
This threw the "Creating default object from empty value" Exception in Laravel.
I fixed it with a very small change.
foreach ( $results as &$data ) {
$data->direction = 0;
}
By simply making $data a reference.
I hope that helps somebody a it was annoying the hell out of me!
no you do not .. it will create it when you add the success value to the object.the default class is inherited if you do not specify one.
This problem is caused because your are assigning to an instance of object which is not initiated. For eg:
Your case:
$user->email = 'exy#gmail.com';
Solution:
$user = new User;
$user->email = 'exy#gmail.com';
This is a warning which I faced in PHP 7, the easy fix to this is by initializing the variable before using it
$myObj=new \stdClass();
Once you have intialized it then you can use it for objects
$myObj->mesg ="Welcome back - ".$c_user;
I put the following at the top of the faulting PHP file and the error was no longer display:
error_reporting(E_ERROR | E_PARSE);

Categories