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:
Related
...
public $aSettings = array(
'BindHost' => "127.0.0.1",
'Port' => 9123,
'MaxFileSize' => (5 * (1024 * 1024)), // unexpected "(" here
'UploadedURL' => "http://localhost",
'UploadPath' => dirname(__FILE__) . "/upload",
'UploadMap' => dirname(__FILE__) . "/uploads.object",
'RegisterMode' => false
);
...
This is my code, straight from a class. The problem I have is the "unexpected ( on line 22", line 22 being MaxFileSize.
I can't see a problem with it, is this a Zend Engine limitation? Or am I blind.
You cannot use non-constant values while initializing class properties in PHP versions earlier than 5.6.
These are initialized at compile time, at which PHP will do no calculations or execute any code. (5 * (1024 * 1024)) is an expression that requires evaluation, which you cannot do there. Either replace that with the constant value 5242880 or do the calculation in __construct.
PHP 5.6, introduced in 2014, allows "constant scalar expressions" wherein a scalar constant or class property can be initialized by an evaluated expression in the class definition rather than the constructor.
I suspect this is not the whole code and this is a definition of a static variable inside a class, where you're quite limited in expressions and can't calculate a lot.
If I'm right, you may want to do something like that instead:
class thingamajig {
public static $aSettings;
};
thingamajig::$aSettings = array ( ... );
P.S. Sorry, I've just read your prose where you confirm it's a part of a class static variable. So you can't just ignore out-of-place keyword.
I assume what you're showing is actually a class property (because of the public keyword). Initialization of class properties in PHP must be constant.
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.
http://www.php.net/manual/en/language.oop5.properties.php
When you define variable in class, you cannot assign expression to it. (5 * (1024 * 1024)) is an expression. 6164480 is not.
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
Public is a declaration only used in objects. This is not an object, remove public and it's fine.
while studying oop in PHP, I noticed that the property declaration accepts an array as value as mentioned here PHP documentation
class Test{
public $var7 = array(true, false);
}
and I noticed that the documentation says :
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.
and after reading this article to know how the compilation process works, I realized that within the compilation process, some expressions would be evaluated and optimized if it possible like the below snippet :
var x = strlen ("testing") => int(7);
if using the array as a value in declaring property worked because it's evaluated in the compilation process, then why did not the below initialization work if logically both of them could be evaluated at the compilation process and this is the condition to initialize a property in a class?
Class Test {
public $vars=strlen("random"); // Fails
}
The short answer to your question is that strlen() is a function while array() is a keyword.
The critical difference to understand is that keywords always reference the same thing regardless of context.
From php.net:
These words have special meaning in PHP. Some of them represent things which look like functions, some look like constants, and so on - but they're not, really: they are language constructs. You cannot use any of the following words as constants, class names, function or method names.
Functions, on the other hand, could be defined differently depending on where you are calling them.
Consider this simplistic example:
First a file we'll call "functions.php".
//functions.php
namespace My_Project_Namespace;
function strlen($string){
return 10; //In my project, all strings are length 10! 10 is a nice round number...
}
In this file, I am overriding the built-in strlen() function with another one. This is possible because my function is defined inside a namespace (in this case, My_Project_Namespace).
Now consider your file, but this time let's put it in our namespace (you should be name-spacing all your functions and classes)
//Test.php
namespace My_Project_Namespace;
Class Test {
public $vars=strlen("random"); // Fails
}
strlen() has 2 definitions depending on the namespace. Since knowing the current namespace depends on runtime information the compiler cannot know which to use for initialization in the class. Even if you didn't define a custom strlen() function you still couldn't do this because knowing that there isn't another version strlen() also depends on runtime information!
array() is a totally different beast. It is a keyword, you cannot define another meaning for array() so the compiler doesn't have to worry about one existing.
...
public $aSettings = array(
'BindHost' => "127.0.0.1",
'Port' => 9123,
'MaxFileSize' => (5 * (1024 * 1024)), // unexpected "(" here
'UploadedURL' => "http://localhost",
'UploadPath' => dirname(__FILE__) . "/upload",
'UploadMap' => dirname(__FILE__) . "/uploads.object",
'RegisterMode' => false
);
...
This is my code, straight from a class. The problem I have is the "unexpected ( on line 22", line 22 being MaxFileSize.
I can't see a problem with it, is this a Zend Engine limitation? Or am I blind.
You cannot use non-constant values while initializing class properties in PHP versions earlier than 5.6.
These are initialized at compile time, at which PHP will do no calculations or execute any code. (5 * (1024 * 1024)) is an expression that requires evaluation, which you cannot do there. Either replace that with the constant value 5242880 or do the calculation in __construct.
PHP 5.6, introduced in 2014, allows "constant scalar expressions" wherein a scalar constant or class property can be initialized by an evaluated expression in the class definition rather than the constructor.
I suspect this is not the whole code and this is a definition of a static variable inside a class, where you're quite limited in expressions and can't calculate a lot.
If I'm right, you may want to do something like that instead:
class thingamajig {
public static $aSettings;
};
thingamajig::$aSettings = array ( ... );
P.S. Sorry, I've just read your prose where you confirm it's a part of a class static variable. So you can't just ignore out-of-place keyword.
I assume what you're showing is actually a class property (because of the public keyword). Initialization of class properties in PHP must be constant.
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.
http://www.php.net/manual/en/language.oop5.properties.php
When you define variable in class, you cannot assign expression to it. (5 * (1024 * 1024)) is an expression. 6164480 is not.
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
Public is a declaration only used in objects. This is not an object, remove public and it's fine.
Class Test {
private static $one = ['a','b'];
private static $two = Test::$one; // Throws an error
// Error : syntax error, unexpected '$one', expecting 'identifier' or 'class'
}
Why does it throw an error and what is the way here to make $two = $one?
It is a limitation of the PHP compiler and it is explained in the documentation:
Like any other PHP static variable, static properties may only be initialized using a literal or constant before PHP 5.6; expressions are not allowed. In PHP 5.6 and later, the same rules apply as const expressions: some limited expressions are possible, provided they can be evaluated at compile time.
The key statement here is: "provided they can be evaluated at compile time".
From the error message you receive I can tell you are using PHP 5. On PHP 7 the error message was reworded to clearly state the problem. It says "Constant expression contains invalid operations".
The declaration of the first static variable ($one) compiles because you initialize it with a constant expression. ['a','b'] is an array of strings, it can be evaluated at the compile time, everything is fine.
The second static variable ($two) is initialized with a non-constant expression (Test::$one). Test::$one is a variable. You can tell that its value initial value is known at the compile time (see the paragraph above) and the expression can be evaluated at the compile time.
This kind of behaviour requires a deeper analysis of the code at the compile time. It is probably implemented in C++ or Java compilers but these are languages that are compiled only once and the code they generate is stored in a file and executed or interpreted later. The PHP compiler doesn't work this way for a reason. It compiles the script before each execution, that's why it aims to complete the compilation as fast as possible and doesn't put much effort in code analysis and optimizations.
Update:
As #deceze specifies in a comment, the expression Test::$one cannot be evaluated in the declaration of $two because it uses class Test that is not completely defined at this point. Even the compilers of other languages that allow this kind of reference cannot compute the value of Test::$one when they reach the declaration of $two. They need to use a second compilation pass to be able to evaluate it.
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