Search code examples
phpmathabstract-syntax-treeinterpreter

Show processed script computation steps in PHP


Is it possible to show how php is calculating the solution to a problem.

For example:

<?php
$equation = (5+1)*3/9; // outputs 2
?>

If would like to see all the steps it processed to come to that solution: IE:

(5+1) = 6

6*3 = 18

18/9 = 2


Solution

  • The short answer is "maybe". It's not possible to hook into PHP internals during runtime like you would have expected it. But, there is an alternative which comes close to it using an external parser.

    PHP script parser & abstract syntax tree (AST)

    The reason for this is, that PHP uses an abstract syntax tree (AST) internally to define the processing steps as well as to optimize execution by creating a better binary representation as OpCache in order to avoid parsing and processing the same scripts again and again.

    Thus, in case you need to know more about the processing in PHP, you'd consider analyzing the PHP C source code:

    AST in PHP scripts - nikic/php-parser

    A re-implementation of the AST parser in PHP is available as well, see https://github.com/nikic/PHP-Parser.

    The following example puts the expression $equation = (5+1)*3/9; // outputs 2 of the original question to a dedicated math.php file. The parser is executed using the following CLI commands:

    composer require nikic/php-parser
    vendor/bin/php-parse math.php
    

    Which will output the parsed syntax tree of $equation = (5+1)*3/9; // outputs 2 (which is basically the answer to the original question):

    ====> File math.php:
    ==> Node dump:
    array(
        0: Stmt_Expression(
            expr: Expr_Assign(
                var: Expr_Variable(
                    name: equation
                )
                expr: Expr_BinaryOp_Div(
                    left: Expr_BinaryOp_Mul(
                        left: Expr_BinaryOp_Plus(
                            left: Scalar_LNumber(
                                value: 5
                            )
                            right: Scalar_LNumber(
                                value: 1
                            )
                        )
                        right: Scalar_LNumber(
                            value: 3
                        )
                    )
                    right: Scalar_LNumber(
                        value: 9
                    )
                )
            )
        )
        1: Stmt_Nop(
            comments: array(
                0: // outputs 2
            )
        )
    )
    

    OpCache analyzis

    By reading https://nikic.github.io/2017/04/14/PHP-7-Virtual-machine.html (by the author of the mentioned PHP AST parser above) one might find some more information about analyzing PHP processing. However, this section does not give additional answers to the question - it's more to outline alternatives and what they actually reveal.

    php -d opcache.enable_cli=1 -d opcache.opt_debug_level=0x10000 math.php
    

    The command above executed in a CLI context enables OpCache debugging and outputs the following:

    $_main: ; (lines=3, args=0, vars=1, tmps=1)
        ; (before optimizer)
        ; /var/www/developer/math.php:1-4
    L0:     EXT_STMT
    L1:     ASSIGN CV0($equation) int(2)
    L2:     RETURN int(1)
    

    The debug output above just contains the result int(2) but not the according steps that have been determined for the syntax tree - which is quite obvious since they are not required for an optimized version.

    This can be verified by looking into the generated OpCache binary as well:

    php -d opcache.enable_cli=1 -d opcache.opt_debug_level=0x10000 \
      -d opcache.file_cache=/var/www/developer/opcache/ \
      -d opcache.file_cache_only=1 math.php
    hexdump opcache/08202de11af2c60edca0b5438eeefab6/var/www/developer/math.php.bin -C
    

    (my working directory is /var/www/developer/ which has a sub-directory opcache/)

    00000210  2f 76 61 72 2f 77 77 77  2f 64 65 76 65 6c 6f 70  |/var/www/develop|
    00000220  65 72 2f 6d 61 74 68 2e  70 68 70 00 75 7f 00 00  |er/math.php.u...|
    00000230  28 32 60 aa 75 7f 00 00  30 32 60 aa 75 7f 00 00  |(2`.u...02`.u...|
    00000240  38 32 60 aa 75 7f 00 00  40 32 60 aa 75 7f 00 00  |82`.u...@2`.u...|
    00000250 [02]00 00 00 00 00 00 00  04 00 00 00 ff ff ff ff  |................| <--
    00000260  01 00 00 00 00 00 00 00  04 00 00 00 ff ff ff ff  |................|
    00000270  e9 0b 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    00000280  00 00 00 00 00 00 00 00  02 00 00 00 65 08 08 08  |............e...|
    00000290  e9 0b 00 00 00 00 00 00  50 00 00 00 00 00 00 00  |........P.......|
    000002a0  00 00 00 00 00 00 00 00  02 00 00 00 26 10 01 08  |............&...|
    000002b0  e9 0b 00 00 00 00 00 00  10 00 00 00 00 00 00 00  |................|
    000002c0  00 00 00 00 ff ff ff ff  04 00 00 00 3e 01 08 08  |............>...|
    000002d0  88 02 00 00 00 00 00 00  00 00 00 00 06 02 00 00  |................|
    000002e0  cb 5b 29 c4 00 e7 1a 80  08 00 00 00 00 00 00 00  |.[).............|
    000002f0  65 71 75 61 74 69 6f 6e  00 32 60 aa 75 7f 00 00  |equation.2`.u...|
    

    The above hexdump just shows the end of the OpCache file. The first byte in the marked line (at 0x00000250) contains the optimized result 02 already. For the given example, there are no further pointers to operators (e.g. ZEND_MUL) nor the integer literals 5, 1, 3 or 9. Thus, the result of the constant equation is directly stored in OpCache already.