PHP - 为什么代码中对象实例化的不同位置会产生不同的结果?

PHP - Why do different position of object instiation in code give different results?

请注意以下所有示例中include_once语句的位置:

这个有效:

include_once __DIR__ . '/vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;    

$request = Request::createFromGlobals();

echo $request->getMethod();

我认为这行不通,但确实如此:

use Symfony\Component\HttpFoundation\Request;

include_once __DIR__ . '/vendor/autoload.php';

$request = Request::createFromGlobals();

echo $request->getMethod();

但这失败了:

use Symfony\Component\HttpFoundation\Request;    

$request = Request::createFromGlobals();

include_once __DIR__ . '/vendor/autoload.php';

echo $request->getMethod();

这也是:

include_once __DIR__ . '/vendor/autoload.php';

$request = Request::createFromGlobals();

use Symfony\Component\HttpFoundation\Request;

echo $request->getMethod();

我猜只要代码中 useinclude_once 在 class 是 used/instantiated 之前,代码就可以正常工作。

对吗?

流程的详细解释将不胜感激。

use 语句不调用自动加载器,或检查 class 是否存在,它只是将 "name" 拉入当前命名空间。在您尝试使用它之前,不会调用自动加载器,PHP 也不会查找 class 以查看它是否存在。

PHP 脚本的执行分为两个阶段:首先编译脚本,如果没有语法错误,编译器会生成一系列所谓的 "opcodes"。然后,解释器启动并开始执行脚本;这意味着它一个一个地获取操作码,解释它们并执行由它们编码的操作。

只有主脚本在开始执行前编译,包含的文件(使用 include 函数之一)在执行期间编译。

让我们看看您的脚本会发生什么。

include_once

include/include_once/require/require_once 语句生成的代码告诉解释器:"stop the execution of the current script here, load and compile this file and, if it doesn't contain syntax errors, execute its code before continuing this script".

就是这样,包含的文件不会在主脚本的编译阶段被读取,而只有在运行时需要时(并且如果)才被读取。在这段代码中:

if (false) {
    include 'a.php';
}

从未读取文件 a.php 的内容;更重要的是,解释器甚至不关心文件是否存在。它仅在 include 语句即将执行时才开始关心 a.php 但由于 if (false) 测试,这从未发生过。

使用

use 语句用于创建别名。这样的别名只是程序员和编译器之间的约定,编译器不会为其生成任何代码。

通过使用语句:

use Symfony\Component\HttpFoundation\Request;

程序员告诉编译器:"hey, I want you to know that later in this file when you find the token Request I mean \Symfony\Component\HttpFoundation\Request but I prefer to use only Request instead because it's shorter".

use 语句不会在生成的代码中留下任何痕迹。它们在运行时不存在。代码:

use Symfony\Component\HttpFoundation\Request;
$request = new Request();

等同于:

$request = new \Symfony\Component\HttpFoundation\Request();

为他们生成相同的代码;编译时,第一种形式"transformed"变成第二种形式

自动加载器

文件vendor/autoload.php的内容(可能)是Composer生成的自动加载器。当代码到达对未知 class 的引用时由解释器执行的 autoloader is a callable (function or class method) registered using spl_autoload_register()。自动加载器接收完整的 class 名称(带有命名空间),其目的是使 class 可用。它通常知道在哪里可以找到 class(在文件中)。可以注册多个自动加载器,解释器一个接一个地调用它们,直到 class 可用或全部失败。

它是如何运行的

让我们替换您的代码变体中的别名,看看我们得到了什么。

变体 #1 和 #2 产生相同的代码:

include_once __DIR__ . '/vendor/autoload.php';
$request = \Symfony\Component\HttpFoundation\Request::createFromGlobals();
echo $request->getMethod();

运行期间:

  • include_once 语句加载并注册自动加载器;
  • $request = \Symfony\Component\HttpFoundation\Request::createFromGlobals() - class \Symfony\Component\HttpFoundation\Request 未知,解释器调用包含声明 class 的文件的自动加载器;然后 class 的方法 createFromGlobals 被执行并且它 returns 一个 \Symfony\Component\HttpFoundation\Request;
  • 类型的对象
  • $request 不是 null 并且它的 class 实现了方法 getMethod();该方法被调用,它的值returns被echo显示,大家都很高兴。

变体 #3:

$request = \Symfony\Component\HttpFoundation\Request::createFromGlobals();
include_once __DIR__ . '/vendor/autoload.php';
echo $request->getMethod();

运行方式:

  • $request = \Symfony\Component\HttpFoundation\Request::createFromGlobals(); - class \Symfony\Component\HttpFoundation\Request 尚未声明并且没有注册自动加载器 - 解释器无法找到 class,那里没办法继续了。

    PHP Fatal error: Class 'Symfony\Component\HttpFoundation\Request' not found.

变体#4:

include_once __DIR__ . '/vendor/autoload.php';
$request = Request::createFromGlobals();
echo $request->getMethod();

别名没有被替换,因为它是在使用后声明的。

运行期间:

  • include_once 语句加载并注册自动加载器;
  • $request = Request::createFromGlobals(); - class Request 尚未定义;解释器调用自动加载器,但自动加载器找不到它;口译员无法继续。

    Fatal error: Class 'Request' not found