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

Please note the position of the include_once statement in all the following examples:

This works:

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

use Symfony\Component\HttpFoundation\Request;    

$request = Request::createFromGlobals();

echo $request->getMethod();

I thought this wouldn't work, but it does:

use Symfony\Component\HttpFoundation\Request;

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

$request = Request::createFromGlobals();

echo $request->getMethod();

But this fails:

use Symfony\Component\HttpFoundation\Request;    

$request = Request::createFromGlobals();

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

echo $request->getMethod();

So does this:

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

$request = Request::createFromGlobals();

use Symfony\Component\HttpFoundation\Request;

echo $request->getMethod();

I am guessing as long as use and include_once are there in code before the class is used/instantiated, the code would work fine.

Is that right?

A detailed explanation of the flow would be much appreciated.

The execution of a PHP script happens in two phases: first the script is compiled and, if there are no syntax errors, the compiler generates a sequence of so-called "opcodes". Then, the interpreter kicks in and starts the script execution; this means it takes the opcodes one by one, interprets them and performs the operations encoded by them.

Only the main script is compiled before starting the execution, the files included (using one of the include functions) are compiled during the execution.

Let's see what happens with your script.

include_once

The include/include_once/require/require_once statements generate code that tells the interpreter: "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".

That's it, the included files are not read during the compilation phase of the main script but only when (and if) they are needed during runtime. In this fragment of code:

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

the content of file a.php is never read; even more, the interpreter doesn't even care if the file exists or not. It starts caring about a.php only when the include statement is about to be executed but this never happens because of the if (false) test.

use

The use statement is used to create an alias. Such an alias is only an agreement between the programmer and the compiler, the compiler doesn't generate any code for it.

By using the statement:

use Symfony\Component\HttpFoundation\Request;

the programmer tells the compiler: "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".

The use statements don't let any trace in the generated code. They do not exist at runtime. The code:

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

is the same as:

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

The same code is generated for them; during the compilation, the first form is "transformed" into the second one.

The autoloader

The content of file vendor/autoload.php is (probably) the autoloader generated by Composer. An autoloader is a callable (function or class method) registered using spl_autoload_register() that is executed by the interpreter when the code reaches a reference to an unknown class. The autoloader receives the full class name (with namespace) and its purpose is to make the class available. It usually knows where to find the class (in a file). Multiple autoloaders can be registered and the interpreter calls them one by one until the class becomes available or all of them fail.

How it runs

Let's replace the alias in your variants of code and see what we get.

Variants #1 and #2 produce the same code:

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

During runtime:

  • the include_once statement loads and registers the autoloader;
  • $request = \Symfony\Component\HttpFoundation\Request::createFromGlobals() - the class \Symfony\Component\HttpFoundation\Request is not known, the interpreter invokes the autoloader that includes the file that declares the class; then the method createFromGlobals of the class is executed and it returns an object of type \Symfony\Component\HttpFoundation\Request;
  • $request is not null and its class implements the method getMethod(); the method is called, the value it returns is displayed by echo, everybody is happy.

Variant #3:

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

How it runs:

  • $request = \Symfony\Component\HttpFoundation\Request::createFromGlobals(); - the class \Symfony\Component\HttpFoundation\Request was not declared yet and there is no autoloader registered - the interpreter cannot find the class, there is no way to continue.

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

Variant #4:

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

The alias is not replaced because it is declared after it is used.

During runtime:

  • the include_once statement loads and registers the autoloader;
  • $request = Request::createFromGlobals(); - the class Request was not defined yet; the interpreter calls the autoloader but the autoloader fails finding it; the interpreter cannot continue.

    Fatal error: Class 'Request' not found

The use statement doesnt invoke the autoloader, or check if the class even exists, it just pulls that "name" into the current namespace. Until you try to use it, the autoloader is not invoked, nor does PHP look for the class to see if it even exists.