I need to use variable function names for a project I'm working on but have run into a bit of strange issue. The function name ends up as a string element in an array.
This works:
$func = $request[2];
$pages->$func();
This doesn't:
$pages->$request[2]();
And I can't figure out why. It throws an array to string conversion error, as if it's ignoring that I have supplied a key to a specific element. Is this just how it works or am I doing something wrong?
As for php 5, you can use curly-braced syntax:
$pages->{$request[2]}();
Simple enough example to reproduce:
<?php
$request = [
2 => 'test'
];
class Pages
{
function test()
{
return 1;
}
}
$pages = new Pages();
echo $pages->{$request[2]}();
Alternatively (as you noted in the question):
$methodName = $request[2];
$pages->$methodName();
Quote from php.net for php 7 case:
Indirect access to variables, properties, and methods will now be
evaluated strictly in left-to-right order, as opposed to the previous
mix of special cases.
Also there is a table for php 5 and php 7 differences for this matter just below quote in the docs I've supplied here.
Which things you should consider:
Check value of the $request[2] (is it really a string?).
Check your version of php (is it php 5+ or php 7+?).
Check manual on variable functions for your release.
Related
Hello I'm writing an article allowing to bypass a filter that allows only base_convert except that there is something I discovered recently is that when we write this syntax in php:
"system"("id")
the function is interpreted, do you have a technical explanation for this?
Thanks to you !
Functions in PHP aren't first-class objects. What does that mean? When you want to pass around a function, for example as callback to array_map, you cannot just pass the function itself like so:
function my_callback() { ... }
array_map(my_callback, $some_array)
my_callback here is interpreted as a constant, and barring its existence, a bare string. (This works in other languages where functions are first class objects.) You can only pass the function by name, which means you pass a string that contains the name of the function:
array_map('my_callback', $some_array)
PHP will then look up the globally registered function with the name "my_callback" and use it.
This means inside array_map it must look something like this:
function array_map($callback, $array) {
$callback($array); // let's ignore the "mapping" part…
}
So, a variable can hold the name of a function, and "calling" that variable which holds the name of a function actually calls that function.
Now, we know that a variable can just as well be replaced with a literal of the same value:
$a = 1;
$b = 2;
$c = $a + $b;
is the same as:
$c = 1 + 2;
The same happens to hold for calling-strings-with-names-of-functions:
'my_callback'($array)
Note that this only works since PHP 7, where the PHP parser got a huge revamping. Before, $f() was sort of a special case hack, but the PHP 7+ parser properly follows the variable-is-substitutable-by-literal logic.
I have object properties in my code that look like this:
$obj ->field_name_cars[0];
$obj ->field_name_clothes[0];
The problem is I have 100s of field names and need to write the property name dynamically. Otherwise, the object name and the keys for the property will always be the same. So I tried:
$obj -> $field[0];
Hoping that the name of the property would dynamically be changed and access the correct values. But, I keep getting 'undefined property $field in stdClass::$field;
More or less I am trying dynamically write the php before it executes so that it can output the proper values. Thoughts on how to approach this?
Update for PHP 7.0
PHP 7 introduced changes to how indirect variables and properties are handled at the parser level (see the corresponding RFC for more details). This brings actual behavior closer to expected, and means that in this case $obj->$field[0] will produce the expected result.
In cases where the (now improved) default behavior is undesired, curly braces can still be used to override it as shown below.
Original answer
Write the access like this:
$obj->{$field}[0]
This "enclose with braces" trick is useful in PHP whenever there is ambiguity due to variable variables.
Consider the initial code $obj->$field[0] -- does this mean "access the property whose name is given in $field[0]", or "access the element with key 0 of the property whose name is given in $field"? The braces allow you to be explicit.
I think you are looking for variable-variable type notation which, when accessing values from other arrays/objects, is best achieved using curly bracket syntax like this:
$obj->{field[0]}
The magic method __get is you friend:
class MyClass
{
private $field = array();
public function __get($name)
{
if(isset($this->field[$name]))
return $this->field[$name];
else
throw new Exception("$name dow not exists");
}
}
Usage:
$myobj = new MyClass();
echo $myobj->myprop;
Explanation: All your field data is stored in a array. As you access $myobj->myprop that property obviously does not exists in the class. That is where __get is called. __get looks up the name in the field array and returns the correct value.
today i face that challenge. I ended up with that style of development
$countTickets = new \stdClass;
foreach ($tickets as $key => $value) {
if(!isset($countTickets->total)){
$countTickets->total = 0;
}
if(!isset($countTickets->{$value['categoryname']})){
$countTickets->{$value['categoryname']} = 0;
}
$countTickets->total += $value['number'];
$countTickets->{$value['categoryname']} += $value['number'];
}
I worked on some code that used dynamically created object properties. I thought that using dynamically created object properties was pretty cool (true, in my opinion). However, my program took 7 seconds to run. I removed the dynamic object properties and replaced them object properties declared as part of each class (public in this case). CPU time went from over 7 seconds to 0.177 seconds. That's pretty substantial.
It is possible that I was doing something wrong in the way I was using dynamic object properties. It is also possible that my configuration is broken in some way. Of course, I should say that I have a very plain vanilla PHP configuration on my machine.
It was noted in another question that wrapping the result of a PHP function call in parentheses can somehow convert the result into a fully-fledged expression, such that the following works:
<?php
error_reporting(E_ALL | E_STRICT);
function get_array() {
return array();
}
function foo() {
// return reset(get_array());
// ^ error: "Only variables should be passed by reference"
return reset((get_array()));
// ^ OK
}
foo();
I'm trying to find anything in the documentation to explicitly and unambiguously explain what is happening here. Unlike in C++, I don't know enough about the PHP grammar and its treatment of statements/expressions to derive it myself.
Is there anything hidden in the documentation regarding this behaviour? If not, can somebody else explain it without resorting to supposition?
Update
I first found this EBNF purporting to represent the PHP grammar, and tried to decode my scripts myself, but eventually gave up.
Then, using phc to generate a .dot file of the two foo() variants, I produced AST images for both scripts using the following commands:
$ yum install phc graphviz
$ phc --dump-ast-dot test1.php > test1.dot
$ dot -Tpng test1.dot > test1.png
$ phc --dump-ast-dot test2.php > test2.dot
$ dot -Tpng test2.dot > test2.png
In both cases the result was exactly the same:
This behavior could be classified as bug, so you should definitely not rely on it.
The (simplified) conditions for the message not to be thrown on a function call are as follows (see the definition of the opcode ZEND_SEND_VAR_NO_REF):
the argument is not a function call (or if it is, it returns by reference), and
the argument is either a reference or it has reference count 1 (if it has reference count 1, it's turned into a reference).
Let's analyze these in more detail.
First point is true (not a function call)
Due to the additional parentheses, PHP no longer detects that the argument is a function call.
When parsing a non empty function argument list there are three possibilities for PHP:
An expr_without_variable
A variable
(A & followed by a variable, for the removed call-time pass by reference feature)
When writing just get_array() PHP sees this as a variable.
(get_array()) on the other hand does not qualify as a variable. It is an expr_without_variable.
This ultimately affects the way the code compiles, namely the extended value of the opcode SEND_VAR_NO_REF will no longer include the flag ZEND_ARG_SEND_FUNCTION, which is the way the function call is detected in the opcode implementation.
Second point is true (the reference count is 1)
At several points, the Zend Engine allows non-references with reference count 1 where references are expected. These details should not be exposed to the user, but unfortunately they are here.
In your example you're returning an array that's not referenced from anywhere else. If it were, you would still get the message, i.e. this second point would not be true.
So the following very similar example does not work:
<?php
$a = array();
function get_array() {
return $GLOBALS['a'];
}
return reset((get_array()));
A) To understand what's happening here, one needs to understand PHP's handling of values/variables and references (PDF, 1.2MB). As stated throughout the documentation: "references are not pointers"; and you can only return variables by reference from a function - nothing else.
In my opinion, that means, any function in PHP will return a reference. But some functions (built in PHP) require values/variables as arguments. Now, if you are nesting function-calls, the inner one returns a reference, while the outer one expects a value. This leads to the 'famous' E_STRICT-error "Only variables should be passed by reference".
$fileName = 'example.txt';
$fileExtension = array_pop(explode('.', $fileName));
// will result in Error 2048: Only variables should be passed by reference in…
B) I found a line in the PHP-syntax description linked in the question.
expr_without_variable = "(" expr ")"
In combination with this sentence from the documentation: "In PHP, almost anything you write is an expression. The simplest yet most accurate way to define an expression is 'anything that has a value'.", this leads me to the conclusion that even (5) is an expression in PHP, which evaluates to an integer with the value 5.
(As $a = 5 is not only an assignment but also an expression, which evalutes to 5.)
Conclusion
If you pass a reference to the expression (...), this expression will return a value, which then may be passed as argument to the outer function. If that (my line of thought) is true, the following two lines should work equivalently:
// what I've used over years: (spaces only added for readability)
$fileExtension = array_pop( ( explode('.', $fileName) ) );
// vs
$fileExtension = array_pop( $tmp = explode('.', $fileName) );
See also PHP 5.0.5: Fatal error: Only variables can be passed by reference; 13.09.2005
This is a very edge case in PHP 5.4 regarding passing objects by reference where get this error:
PHP Warning: Parameter 1 to A::foo() expected to be a reference, value given
But only as a compound effect of:
Using reflection to set an inherited method as 'accessible',
AND that method taking an explicitly referential argument (&argument sig)
AND then invoking it with func_get_args() as opposed to constructing the array of args manually.
No idea why these things all cause this behaviour or if they should.
Important to note that this effect isn't present in PHP 5.5.
This is the code that will cause the above error, but if you comment the line with COMMENT THIS LINE the code runs fine (e.g. the object gets passed correctly to the 'foo' function):
class A {
private function foo(&$arg1) {
var_dump('arg1: ', $arg1);
}
}
class B extends A {
public function bar() {
$x = new stdClass();
$x->baz = 'just a value';
$this->callPrivate($x);
}
private function callPrivate($x)
{
$method = new \ReflectionMethod(
'A',
'foo'
);
//* for some reason, the private function needs to have been changed to be 'accessible' for this to work in 5.4
$method->setAccessible(true);
//working 5.4 (* see above) but not in 5.5
$arguments = func_get_args();
//not working in either
$arguments = array($x); // <---- COMMENT THIS LINE TO SEE IT WORK IN PHP 5.4
return $method->invokeArgs($this, $arguments);
}
}
$y = new B();
$y->bar();
I can't see why there would be any difference between the two $arguments arrays since var_dumping them shows the same output. Therefore, I assume it is to do with something lower level like object 'pointers' being different (out of my depth here)?
The other question is if this is a bug in PHP 5.4, 5.5 or both?
Prior to PHP 5.5.6 func_get_args() took arguments from the VM stack, copied them and returned them in an array. In PHP 5.5.6 an optimization was introduced which avoids these expensive copies in the common case. Instead of copying the zvals only the refcount is increased (by-ref args notwithstanding.)
Normally such a change would have zero effect on user code. But there are a few places in the engine where observable behavior differs based on the refcount of the zval. One such place is by-reference passing:
For the case of a dynamic function call, a zval can be passed by reference either if it is a reference or if it has refcount==1.
Before PHP 5.5.6 the zvals in the array returned by func_get_args() always had refcount==1, so they went through based on that second case. As of PHP 5.5.6 this is no longer true as by-value zvals will always have refcount>1 and cause an error if you try to pass them by-reference.
Note: The code didn't actually work before PHP 5.5.6 (the by-ref was ignored). It was just an unfortunate coincidence that you didn't get an error telling you so ;)
Update: We decided to revert the change on the 5.5 branch due to the BC break. You will get the old behavior back in PHP 5.5.8 and the new behavior will only be in PHP 5.6.
According to the PHP docs, one can initialize properties in classes with the following restriction:
"This 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."
I'm trying to initialize an array and having some issues. While this works fine:
public $var = array(
1 => 4,
2 => 5,
);
This creates a syntax error:
public $var = array(
1 => 4,
2 => (4+1),
);
Even this isn't accepted:
public $var = 4+1;
which suggests it's not a limitation of the array() language construct.
Now, the last time I checked, "4+1" equated to a constant value that not only should be accepted, but should in fact be optimized away. In any case, it's certainly able to be evaluated at compile-time.
So what's going on here? Is the limitation really along the lines of "cannot be any calculated expression at all", versus any expression "able to be evaluated at compile time"? The use of "evaluated" in the doc's language suggests that simple calculations are permitted, but alas....
If this is a bug in PHP, does anyone have a bug ID? I tried to find one but didn't have any luck.
PHP doesn't do such operations at compile-time; you cannot assign calculated values to constants, even if all operators are constants themselves. Default values of class members are treated the exact same way. I encountered this behaviour as I tried to assign powers of two to constants:
class User {
const IS_ADMIN = 1;
const IS_MODERATOR1 = 1 << 1; // Won't work
const IS_MODERATOR2 = 0x02; // works
}
This limitation no longer exists as of PHP 5.6
The new feature that enables the previously-disallowed syntax is called constant scalar expressions:
It is now possible to provide a scalar expression involving numeric
and string literals and/or constants in contexts where PHP previously
expected a static value, such as constant and property declarations
and default function arguments.
class C {
const THREE = TWO + 1;
const ONE_THIRD = ONE / self::THREE;
const SENTENCE = 'The value of THREE is '.self::THREE;
public function f($a = ONE + self::THREE) {
return $a;
}
}
echo (new C)->f()."\n"; echo C::SENTENCE; ?>
The above example will output:
4 The value of THREE is 3
Before you throw your arms up at php for this, think about the execution model. In the environment that php is typically used for(and, in fact, designed for), everything is built up, executed, and then thrown away...until the next http request comes in. It doesn't make a lot of sense to waste time doing computations during the parsing/compilation phase. The engine needs to be very swift here in the general case.
But, you're right, that quote from the manual does say "evaluate". Maybe you should open a documentation ticket.
Edit march 2014
it looks like php will now support Constant Scalar Expressions in php 5.6: