php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Request #73608 inconstistent const behavior
Submitted: 2016-11-25 23:51 UTC Modified: 2016-12-14 00:27 UTC
From: spam2 at rhsoft dot net Assigned:
Status: Suspended Package: Scripting Engine problem
PHP Version: 7.0.13 OS:
Private report: No CVE-ID: None
Have you experienced this issue?
Rate the importance of this bug to you:

 [2016-11-25 23:51 UTC] spam2 at rhsoft dot net
Description:
------------
why can't you still not use 'const' inside a if-statement?

"because it's compile time" is a lie which is easily to realize because otherwise CONST_B could never contain the value of CONST_A which was defined in the if-statement before

[harry@srv-rhsoft:/downloads]$ cat test.php
<?php
 $x = 1;
 if($x == 1)
 {
  define('CONST_A', 'A');
 }
 const CONST_B = 'TEST ' . CONST_A;
 echo CONST_B . "\n";
?>

[harry@srv-rhsoft:/downloads]$ php test.php
TEST A




Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-11-25 23:59 UTC] nikic@php.net
-Type: Bug +Type: Feature/Change Request -Package: PHP Language Specification +Package: Scripting Engine problem
 [2016-12-13 15:10 UTC] dave at mudsite dot com
"because it's compile time" does not appear to exactly be a lie.  The const keyword wants to be used (as is declared in the grammar) as a top-level statement.  Why?  Well a constant is compiled as a left, or right, node to op-codes at compile time, and therefore needs to be a literal constant.  Now, imagine this code:

<?php
class Foo {
    function __toString() {
        return "I'm a FOO";
    }
}


$f = new Foo();
const CONST_A =  $f;
const CONST_B = 'TEST ' . CONST_A;
echo CONST_B . "\n";
?>

You would get a compilation fatal here as well, since a constant can not be a variable.  define() however, is a runtime function that can accept variables and update the constant table with a reference to that compiled-variable(here that's an object).  So looking again at your example, you're using the const-keyword within a statement dependent on a variable, therefore the value of a constant expression is variable, and a constant can not be variable.  So if you want to define a constant that is variable, you have use of define() to do it at runtime.

That said, just moving the grammar for T_CONST from top_statement into statement does get your code to run; however, I imagine the desire to keep const's out of places dependent on variables is the languages desire.  Any internals people could feel free to correct my thoughts here.
 [2016-12-13 16:17 UTC] spam2 at rhsoft dot net
did you look at my example?

const CONST_B = 'TEST ' . CONST_A; *works* here and since one part (CONST_A) of it is inside a if-statement how can that be when "a constant can not be variable"
 [2016-12-13 23:19 UTC] dave at mudsite dot com
Yes I did see your example, and tried to expand upon it highlighting the use of runtime.


To specifically address your (desired) example:

<?php
$x = 1;
if($x == 1)
{
  const CONST_A = 'A';
}

We must read this to say that we wish to define a constant based on a variable expression.  During compilation the compiler does not know what the value of any compiled-variable is, rather it knows how to look for the value during execution.  So understanding that we can look at the if statement and see that we compare the equality of compiled-variable($x) and a constant(1).  Only during execution will the result of that equality be known.

Taking your example to the next logical example to showcase why it's not possible we have:
<?php
$x = 2;
if ($x == 1) {
    const CONST_A = 'A';
} else {
    const CONST_A = 'AAA';
}

Knowing that const is a compile-time portion of the grammar, how can we properly compile this example?  We can't very well set a constant to two values, nor can we know which constant to compile.  This is why "because it's compile time" is not a lie.


Enter define().  As you show in your example it's the proper way to update the constant-table during runtime when variable-expressions can be evaluated to know which branch of code will be executed.
<?php
$x = 2;
if ($x == 1) {
    define('CONST_A', 'A');
} else {
    define('CONST_A', 'AAA');
}
 [2016-12-13 23:47 UTC] spam2 at rhsoft dot net
how does that change the fact that in case of "if($x == 1)" you have no idea at compile time if $x will be 1 - either my sample would be invalid code and needs to be a fatal/compile/whatever error or you can use const with variables

the point is the damned function call for define() which means 15 define() calls are 0.2% runtime of our complete core-application while you can assign thounsands of variables in the same runtime but well, they are variables which can be changed and are not visible in every scope without additional opcodes $GLOBALS or global $foo;
 [2016-12-13 23:50 UTC] spam2 at rhsoft dot net
or to make it very clear, the following code wrks just fine with PHP 7.0 and respects if it is running via CLI or not - in real life - rh_serverurl . MY_PHP_SELF - you have no idea at compile time which valkue both will have BUT it works

 if(PHP_SAPI != 'cli')
 {
  define('MY_PHP_SELF', $_SERVER['SCRIPT_NAME']);
  define('rh_serverurl', PROTOCOL_PREFIX . MY_SERVER_NAME . $rh_port);
 }
 else
 {
  define('MY_PHP_SELF', '/' . basename($_SERVER['SCRIPT_NAME']));
  define('rh_serverurl', 'http://localhost');
 }
 const rh_phpself = rh_serverurl . MY_PHP_SELF;
 [2016-12-14 00:09 UTC] dave at mudsite dot com
"how does that change the fact that in case of "if($x == 1)" you have no idea at compile time if $x will be 1"

Well, you have to take a look to the grammar.  It, as well as the compiler, are written pretty abstractly.  The actual grammar of an if() statement is:

T_IF '(' expr ')' statement

You'd then look to expr, and see that it encompasses MANY types of expressions.  For example, function calls are an expression.  So we can take your example and rewrite it as such:

$fp = fopen('/dev/urandom');
if (fread($fp, 2) == "no") [
    const FOO = 'bar';
}

In your basic example it's simple to mentally think $x is 1, 1 is 1, 1==1 why is this hard?  Well again, it's abstract, so what is the first 2 characters of the filehandle here?  At compile time, we would have no idea, so we would have no clue if that path should be taken or not.



"either my sample would be invalid code and needs to be a fatal/compile/whatever error or you can use const with variables"

It's not invalid code and needs no error.  There's a disjoint between compiling syntax, and executing operations.  The compiler takes your code and compiles it into opcodes that a virtual machine can execute over.  To show you what the VM syntax looks like here's a very simple example.

<?php
$x = 1;
var_dump($x);

compiled vars:  !0 = $x
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   3     0  E >   ASSIGN                                                   !0, 1
   4     1        INIT_FCALL                                               'var_dump'
         2        SEND_VAR                                                 !0
         3        DO_ICALL                                                 
   6     4      > RETURN                                                   1

You can see that the compiler has converted the code into a set of 5 operations, using 1 compiled variable.  The first op sets the value of the CV to 1, begin function call, send a variable denoted by the memory of the compiled-variable(!!), run the call, and return default(1).  You can see that the compiler doesn't know the value of the compiled variable when it creates the opcode sending the value to the function, rather, it sends the location that stores the value.  To get a better understanding of -why- this is important.

<?php
$x = rand(0, 1);
var_dump($x);

compiled vars:  !0 = $x
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   3     0  E >   INIT_FCALL                                               'rand'
         1        SEND_VAL                                                 0
         2        SEND_VAL                                                 1
         3        DO_ICALL                                         $1      
         4        ASSIGN                                                   !0, $1
   4     5        INIT_FCALL                                               'var_dump'
         6        SEND_VAR                                                 !0
         7        DO_ICALL                                                 
   6     8      > RETURN                                                   1

Here we end up doing a function call to rand, and take the return value of that setting it to the memory location of !0($x).  So we don't know at compile time if a compiled variable contains a known value or a variable value, since we need to do things quite abstractly.
 [2016-12-14 00:13 UTC] spam2 at rhsoft dot net
that's nitpicking

if i can use const in context of a constant filled with define() which used a variable i can also use a variable for const - deny that ha sno technical reason - period
 [2016-12-14 00:27 UTC] nikic@php.net
-Status: Open +Status: Suspended
 [2016-12-14 00:27 UTC] nikic@php.net
I don't think we have any technical problems with allowing control-flow dependent const declarations. In fact, the following is legal:

    <?php
    if (!$cond) return;
    const A = 1;

Or, as a more extreme case:

    <?php
    if ($cond) goto defA1;
    else goto defA2;
    defA1: const A = 1; goto cont;
    defA2: const A = 2;
    cont:

This is effectively equivalent to a const declaration inside an if/else block.

In any case, this change (allowing constant declarations outside of top statements) will need an RFC. As such, I am suspending this issue.
 [2016-12-14 00:55 UTC] dave at mudsite dot com
Upon looking deeper it seems the problem exists with registered symbols within a file-context.  I don't know why that'd be top-statement.  I imagine this may actually be a relic from php4, or 5.4 or some old version that never changed since we have a runtime function to handle the issue.
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Sun Nov 19 01:31:42 2017 UTC