Const behavior in PHP 7

fluid.320
const behavior may be surprising

I always thought const behavior to be like class. Well, of course, beside the obvious differences, const and class were compile-time structures. They are complete at compilation, and could be op-coded and cached. This is unlike the slower define() function (for const), which requires PHP execution to be creating constants.

This means that the following code is valid :

<?php

namespace BAR {

echo A;

new foo();

}

A may be defined somewhere else in the code, and foo() may even be loaded by autoload() when needed, as long as it is a inside BAR namespace. Practice tends to differ :

<?php

namespace BAR {

echo A;

new foo();

}

namespace BAR {

const A = 'a';

class foo {}

}

This produces (see live https://3v4l.org/aAmI3)

Notice: Use of undefined constant A – assumed ‘A’ on line 4 A

No mention of foo() class is reported, as all is running smoothly. foo() is declared later in the script, and is available as soon as the class is instantiated. No autoload is defined, so this is probably because PHP has already the definition available.

On the other hand, A doesn’t exist at echo time, and is converted to a string. The script may be updated with an extra echo A, after the definition, and this will work as expected. (see live https://3v4l.org/jia3D)

<?php

namespace BAR {

echo A;

new foo();

}

namespace BAR {

const A = 'a';

class foo {}

echo A;

}

Notice: Use of undefined constant A – assumed ‘A’ on line 4 Aa

The const is present twice, once defined, and once undefined. Const definition’s position in the script is important, while class’s are not. A legend is dead.

More const behavior

Let’s be a bit more crazy : in the following script, the const is defined inside the class. We are using T_STRING, which is a PHP native constante, that is always defined in the global scope. This way, it is always defined in the BAR namespace, as PHP falls back to global space first, if it can’t find a definition for the constant in the current namespace.

<?php

namespace BAR {

echo foo::T_STRING;

}

namespace BAR {

const T_STRING = 'a';

echo T_STRING;

class foo {

const T_STRING = T_STRING * 2;

}

}

const T_STRING = T_STRING is a funny expression, indeed, though it makes sense : the first T_STRING is BAR\foo::T_STRING, while the second is \BAR\T_STRING, or \T_STRING. T_STRING has been redefined in the local namespace just above, and it is checked with an echo. The result is : (see live : https://3v4l.org/GeN6c)

319a

319 is \T_STRING and is obtained with echo foo::T_STRING. At line 2, const \BAR\T_STRING is not defined yet, but the class is. Since the class was defined at compile time, it has no idea of the value of \BAR\T_STRING, so it fell back on \T_STRING. Later, the const is defined, but it is too late : PHP has already its value for the class const.

Forcing const T_STRING = \BAR\T_STRING; leads to a fatal error. Using non-PHP native constant leads to error message and a string.

Finally, this strange behavior may be reproduced, by tweaking the examples from the documentation :

<?php

echo foo::TWO;

const ONE = 1;

class foo {

const TWO = ONE * 2;

}

?>

If the class constant is requested before definition, its default value is used, leading to 0. While the same echo, placed after the definition, leads to the expected result.

Don’t push const too far, they’ll bite

In the end, this all is probably an edge case. May be I pushed the enveloppe too far.

Creating constants and mixing them with class constant is probably some bad practices. I ended there by trying a trick I learn with the famous Unit Testing Framework Atoum : within a namespace, one may use PHP native constants (or function in its case), or be replaced by namespace-defined constant easily. That was a convenient idea to handle various PHP versions without changing much of the code, up to a certain point.