欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

php页面不渲染显示源代码_PHP如何执行-从源代码到渲染

程序员文章站 2022-05-19 17:08:08
...

php页面不渲染显示源代码

This article was peer reviewed by Younes Rafie. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

该文章由Younes Rafie进行了同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!



Inspired by a recent article on how Ruby code executes, this article covers the execution process for PHP code.

受最近有关Ruby代码如何执行的文章的启发,本文介绍了PHP代码的执行过程。

php页面不渲染显示源代码_PHP如何执行-从源代码到渲染

介绍 (Introduction)

There’s a lot going on under the hood when we execute a piece of PHP code. Broadly speaking, the PHP interpreter goes through four stages when executing code:

当我们执行一段PHP代码时,有很多事情要做。 概括地说,PHP解释器在执行代码时经历四个阶段:

  1. Lexing

    乐兴
  2. Parsing

    解析中
  3. Compilation

    汇编
  4. Interpretation

    解释

This article will skim through these stages and show how we can view the output from each stage to really see what is going on. Note that while some of the extensions used should already be a part of your PHP installation (such as tokenizer and OPcache), others will need to be manually installed and enabled (such as php-ast and VLD).

本文将简要介绍这些阶段,并展示我们如何查看每个阶段的输出以真正了解发生了什么。 请注意,尽管使用的某些扩展名应该已经是PHP安装的一部分(例如tokenizer和OPcache),但其他扩展名则需要手动安装和启用(例如php-ast和VLD)。

第一阶段–乐兴 (Stage 1 – Lexing)

Lexing (or tokenizing) is the process of turning a string (PHP source code, in this case) into a sequence of tokens. A token is simply a named identifier for the value it has matched. PHP uses re2c to generate its lexer from the zend_language_scanner.l definition file.

Lexing(或标记化)是将字符串(在这种情况下为PHP源代码)转换为标记序列的过程。 令牌只是与其匹配的值的命名标识符。 PHP使用re2czend_language_scanner.l定义文件生成词法分析器。

We can see the output of the lexing stage via the tokenizer extension:

我们可以通过tokenizer扩展看到词法分析阶段的输出:

$code = <<<'code'
<?php
$a = 1;
code;

$tokens = token_get_all($code);

foreach ($tokens as $token) {
    if (is_array($token)) {
        echo "Line {$token[2]}: ", token_name($token[0]), " ('{$token[1]}')", PHP_EOL;
    } else {
        var_dump($token);
    }
}

Outputs:

输出:

Line 1: T_OPEN_TAG ('<?php
')
Line 2: T_VARIABLE ('$a')
Line 2: T_WHITESPACE (' ')
string(1) "="
Line 2: T_WHITESPACE (' ')
Line 2: T_LNUMBER ('1')
string(1) ";"

There’s a couple of noteworthy points from the above output. The first point is that not all pieces of the source code are named tokens. Instead, some symbols are considered tokens in and of themselves (such as =, ;, :, ?, etc). The second point is that the lexer actually does a little more than simply output a stream of tokens. It also, in most cases, stores the lexeme (the value matched by the token) and the line number of the matched token (which is used for things like stack traces).

以上输出中有几点值得注意的要点。 第一点是,并非所有源代码都被命名为令牌。 取而代之的是, 某些符号本身被视为标记 (例如=;:?等)。 第二点是,词法分析器实际上比简单地输出令牌流做得更多。 在大多数情况下,它还存储词素(与令牌匹配的值)和匹配的令牌的行号(用于堆栈跟踪之类的东西)。

阶段2 –解析 (Stage 2 – Parsing)

The parser is also generated, this time with Bison via a BNF grammar file. PHP uses a LALR(1) (look ahead, left-to-right) context-free grammar. The look ahead part simply means that the parser is able to look n tokens ahead (1, in this case) to resolve ambiguities it may encounter whilst parsing. The left-to-right part means that it parses the token stream from left-to-right.

解析器也会生成,这次是Bison通过BNF语法文件生成的 。 PHP使用LALR(1)(从左至右向前看)上下文无关的语法。 前瞻部分只是意味着解析器能够向前解析n令牌(在本例中为1),以解决解析过程中可能遇到的歧义。 从左到右的部分意味着它从左到右解析令牌流。

The generated parser stage takes the token stream from the lexer as input and has two jobs. It firstly verifies the validity of the token order by attempting to match them against any one of the grammar rules defined in its BNF grammar file. This ensures that valid language constructs are being formed by the tokens in the token stream. The second job of the parser is to generate the abstract syntax tree (AST) – a tree view of the source code that will be used during the next stage (compilation).

生成的解析器阶段将来自词法分析器的令牌流作为输入,并具有两个作业。 它首先通过尝试将令牌顺序与其BNF语法文件中定义的任何一种语法规则进行匹配来验证令牌顺序的有效性。 这样可以确保由令牌流中的令牌构成有效的语言构造。 解析器的第二项工作是生成抽象语法树 (AST)–将在下一阶段(编译)中使用的源代码树视图。

We can view a form of the AST produced by the parser using the php-ast extension. The internal AST is not directly exposed because it is not particularly “clean” to work with (in terms of consistency and general usability), and so the php-ast extension performs a few transformations upon it to make it nicer to work with.

我们可以使用php-ast扩展查看解析器生成的AST的一种形式 。 内部AST不会直接暴露出来,因为使用它并不是特别“干净”(就一致性和通用性而言),因此php-ast扩展对其进行了一些转换,以使其使用起来更加美观。

Let’s have a look at the AST for a rudimentary piece of code:

让我们看一下AST的基本代码:

$code = <<<'code'
<?php
$a = 1;
code;

print_r(ast\parse_code($code, 30));

Output:

输出:

ast\Node Object (
    [kind] => 132
    [flags] => 0
    [lineno] => 1
    [children] => Array (
        [0] => ast\Node Object (
            [kind] => 517
            [flags] => 0
            [lineno] => 2
            [children] => Array (
                [var] => ast\Node Object (
                    [kind] => 256
                    [flags] => 0
                    [lineno] => 2
                    [children] => Array (
                        [name] => a
                    )
                )
                [expr] => 1
            )
        )
    )
)

The tree nodes (which are typically of type ast\Node) have several properties:

树节点(通常是ast\Node类型)具有几个属性:

  • kind – An integer value to depict the node type; each has a corresponding constant (e.g. AST_STMT_LIST => 132, AST_ASSIGN => 517, AST_VAR => 256)

    kind –描述节点类型的整数值; 每个都有对应的常量(例如AST_STMT_LIST => 132, AST_ASSIGN => 517, AST_VAR => 256)

  • flags – An integer that specifies overloaded behaviour (e.g. an ast\AST_BINARY_OP node will have flags to differentiate which binary operation is occurring)

    flags –一个指定重载行为的整数(例如, ast\AST_BINARY_OP节点将具有用于区分正在发生哪个二进制操作的标志)

  • lineno – The line number, as seen from the token information earlier

    lineno –行号,如之前的令牌信息所示

  • children – sub nodes, typically parts of the node broken down further (e.g. a function node will have the children: parameters, return type, body, etc)

    children -子节点,典型地进一步细分的节点的部件(例如,功能节点将具有小孩:参数,返回类型,身体等)

The AST output of this stage is handy to work off of for tools such as static code analysers (e.g. Phan).

对于诸如静态代码分析器(例如Phan )之类的工具,此阶段的AST输出很容易使用。

第三阶段–编译 (Stage 3 – Compilation)

The compilation stage consumes the AST, where it emits opcodes by recursively traversing the tree. This stage also performs a few optimizations. These include resolving some function calls with literal arguments (such as strlen("abc") to int(3)) and folding constant mathematical expressions (such as 60 * 60 * 24 to int(86400)).

编译阶段使用AST,在AST中,它通过递归遍历树来发出操作码。 此阶段还执行一些优化。 这些包括使用文字参数解析某些函数调用 (例如将strlen("abc")int(3) )并将折叠的数学表达式折叠(例如将60 * 60 * 24int(86400) )。

We can inspect the opcode output at this stage in a number of ways, including with OPcache, VLD, and PHPDBG. I’m going to use VLD for this, since I feel the output is more friendly to look at.

我们可以在现阶段以多种方式检查操作码输出,包括使用OPcacheVLDPHPDBG 。 我将为此使用VLD,因为我觉得输出看起来更友好。

Let’s see what the output is for the following file.php script:

让我们看看以下file.php脚本的输出是什么:

if (PHP_VERSION === '7.1.0-dev') {
    echo 'Yay', PHP_EOL;
}

Executing the following command:

执行以下命令:

php -dopcache.enable_cli=1 -dopcache.optimization_level=0 -dvld.active=1 -dvld.execute=0 file.php

Our output is:

我们的输出是:

line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   3     0  E > > JMPZ                                                     <true>, ->3
   4     1    >   ECHO                                                     'Yay'
         2        ECHO                                                     '%0A'
   7     3    > > RETURN                                                   1

The opcodes sort of resemble the original source code, enough to follow along with the basic operations. (I’m not going to delve into the details of opcodes in this article, since that would take several entire articles in itself.) No optimizations were applied at the opcode level in the above script – but as we can see, the compilation phase has made some by resolving the constant condition (PHP_VERSION === '7.1.0-dev') to true.

操作码有点类似于原始源代码,足以跟上基本操作。 (我不会在本文中深入研究操作码的细节,因为这本身会花费几篇文章。)在上面的脚本中,没有在操作码级别应用任何优化-但正如我们所看到的,编译阶段通过将常量条件( PHP_VERSION === '7.1.0-dev' )解析为true做出了一些PHP_VERSION === '7.1.0-dev'

OPcache does more than simply caching opcodes (thus bypassing the lexing, parsing, and compilation stages). It also packs with it many different levels of optimizations. Let’s turn up the optimization level to four passes to see what comes out:

OPcache不仅可以简单地缓存操作码(因此绕过了词法分析,解析和编译阶段)。 它还包含许多不同级别的优化。 让我们将优化级别提高到四遍,以查看结果:

Command:

命令:

php -dopcache.enable_cli=1 -dopcache.optimization_level=1111 -dvld.active=-1 -dvld.execute=0 file.php

Output:

输出:

line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   4     0  E >   ECHO                                                     'Yay%0A'
   7     1      > RETURN                                                   1

We can see that the constant condition has been removed, and the two ECHO instructions have been compacted into a single instruction. These are just a taste of the many optimizations OPcache applies when performing passes over the opcodes of a script. I won’t go through the various optimization levels in this article though, since that would also be an article in itself.

我们可以看到常量条件已被删除,并且两个ECHO指令已被压缩为单个指令。 这些只是在对脚本的操作码执行传递时OPcache所应用的许多优化的尝试。 我不会在本文中介绍各种优化级别,因为那本身也是一篇文章。

阶段4 –解释 (Stage 4 – Interpretation)

The final stage is the interpretation of the opcodes. This is where the opcodes are run on the Zend Engine (ZE) VM. There’s actually very little to say about this stage (from a high-level perspective, at least). The output is pretty much whatever your PHP script outputs via commands such as echo, print, var_dump, and so on.

最后阶段是操作码的解释。 这是在Zend Engine(ZE)VM上运行操作码的地方。 关于这个阶段,实际上几乎没有什么可说的(至少从高层的角度来看)。 无论您PHP脚本通过诸如echoprintvar_dump等命令输出什么,输出几乎都是如此。

So instead of digging into anything complex at this stage, here’s a fun fact: PHP requires itself as a dependency when generating its own VM. This is because the VM is generated by a PHP script, due to it being simpler to write and easier to maintain.

因此,有一个有趣的事实,而不是在此阶段深入研究任何复杂的事情:PHP在生成自己的VM时需要将其自身作为依赖项。 这是因为VM是由PHP脚本生成的,因为它更易于编写且易于维护。

结论 (Conclusion)

We’ve taken a brief look through the four stages that the PHP interpreter goes through when running PHP code. This has involved using various extensions (including tokenizer, php-ast, OPcache, and VLD) to manipulate and view the output of each stage.

我们已经简要介绍了运行PHP代码时PHP解释程序所经历的四个阶段。 这涉及使用各种扩展(包括令牌生成器,php-ast,OPcache和VLD)来操纵和查看每个阶段的输出。

I hope this article has helped to provide you with a better holistic understanding of PHP’s interpreter, as well as shown the importance of the OPcache extension (for both its caching and optimization abilities).

我希望本文能帮助您对PHP的解释器有一个更好的整体了解,并表明OPcache扩展的重要性(就其缓存和优化功能而言)。

翻译自: https://www.sitepoint.com/how-php-executes-from-source-code-to-render/

php页面不渲染显示源代码