preface

For PHP frameworks, whether they are Yichi, Symfony or Laravel, everyone has been working on them. The vendor folder and the entry file (index. PHP or app. PHP) that reside in the framework are also met daily. But are you really familiar with these files/folders? How does a complete project evolve from a clean framework? What is the role of each part in the framework of the building?

In the last chapter we talked about Dependency Injection, I don’t know if everyone understood it. It’s okay not to understand, but today’s chapter is completely unrelated to the last chapter.

Second, the Composer

Now we move on to the next topic, which is the tool Composer. Everyone is familiar with this tool, it is very convenient to install plug-ins. But is it clear how he works? What was a mediocre class, how did it get loaded in? The composer says, we’ve appointed it, and autoload will do it.

2.1 __autoload

This is a particularly important point of knowledge. We often see it in the framework’s entry file (__autoload and spl_autoload_register). Now of course you can only see spl_auto_register). But when asked what these two methods do and how to do it, most people will be stunned.

What are these two functions? What’s convenient about auto-loading?

Include and require are two basic ways to introduce files in PHP. Use Include and Require directly in small-scale development. However, in large projects, this can result in a large number of include and require stacks. Do you think I am tired of writing hundreds of include in a file?

Such code is not elegant, it is inefficient to execute, and it is difficult to maintain.

To solve this problem, some frameworks provide a configuration list of import files to import during object initialization. But this only makes the code a little cleaner, and the introduction is still pretty bad. After PHP5, with the improvement of PHP’s object-oriented support, the __autoload function really made automatic loading possible.

Here I would like to add two points that are not relevant to the current chapter:

  • Include and require have the same functionality. The difference is that include only generates a warning when something goes wrong, whereas require throws an error to terminate the script.
  • The only difference between include_once and include is that include_once checks to see if the file has already been imported, and if it has, it will not be imported again.

The simplest way to implement automatic loading is to use the __autoload magic method. When you reference a class that doesn’t exist, __autoload is called, and your class name is passed as an argument. As for the specific logic of the function, this needs to be implemented by the user. Using this property, create an automatic loading mechanism. First create an autoload.php to do a simple test:

Function __autoload($class) {/* echo $class; /* echo $class; } new HelloWorld();} new HelloWorld(); /** * Output HelloWorld with error message * FATAL ERROR: Class 'HelloWorld' not found */ As you can see from this simple example, what the system does during Class instantiation is something like this: If (class exists($class, false)) {return new $class(); if (class_exists($class, false)) {$class(); } // check whether the autoload function is user-defined if (function_exists('__autoload')) {__autoload($class); } if (class_exists($class, false)) {return new $class(); } else {// throw new Exception('Class Not Found'); }}

Now that we understand how the __autoload function works, let’s use it to implement automatic loading.

First create a class file (it is recommended that the file name be the same as the class name) with the following code:

Class [className] {function __construct() {echo '<h1>'.__class__. '</h1>'; }}

(I have created a HelloWorld class for demonstration purposes.) Next we need to define the logic for __autoload so that it can be automatically loaded:

Function __autoload($class) {$file = $class. '. PHP '; if (file_exists($file)) { include $file; } new HelloWorld();} new HelloWorld(); /** * output <h1>HelloWorld</h1> */

It looks nice, doesn’t it? Using this __autoload, you can write a mechanism to automatically load classes. But have you ever tried to write two __autoloads in one file? Don’t worry, the result is an error. In a large frame, do you guarantee that you only have one __autoload? Isn’t that a lot of trouble?

Don’t worry, spl_autoload_register() is ready to go. But before we get into that, let’s talk about another important concept: namespaces.

2.3 Namespaces

Namespaces are nothing new; many languages (such as C++) have supported them for a long time. It’s just that PHP was a late starter, not supported until after PHP 5.3. Namespaces are simply identifiers whose primary purpose is to resolve naming conflicts. Just like in everyday life, there are many people with the same name, how do you tell them apart? That would require some additional labeling. It seems a good idea to use your work unit as a logo, so you don’t have to worry about the embarrassment of “name collision”.

Here, let’s do a small task to introduce Robin Li, CEO of Baidu:

The namespace baidu; Class function __construct() {echo 'Baidu '; }}

This is the basic information of Li Yanhong. Namespace is his unit identity and class is his name. Namespaces are declared with the keyword NAMESPACE. If a file contains namespaces, it must declare namespaces before all other code.

New Baidu \ Robin Li (); // new \ Baidu \ Li Yanhong (); // Fully qualified class name

Under normal circumstances, whether they introduce “Robin Li of Baidu” or “Robin Li of Baidu Inc.”, they will understand. A qualified class name is equivalent to a fully qualified class name if the current namespace is not declared. Because if no space is specified, the default is global ().

The namespace Google; New Baidu \ Robin Li (); // bb0 \ Baidu \ Li () new \ Baidu \ Li (); // Baidu \ Robin Li (actual results)

If you are at Google and you are introducing Robin Li to their employees, be sure to refer to him as “Robin Li of Baidu”. Otherwise, he would think Baidu is a division of Google, and Li is just one of its employees. This example shows the difference between using a qualified class name and a fully qualified class name in a namespace. (fully qualified class name = current namespace + qualified class name)

/* import namespace */ use baidu \ Li Yanhong; New Yanhong Li (); // Baidu \ Robin Li (Actual results) /* Set alias */ Use Baidu \ Robin Li AS CEO; new CEO(); // Baidu \ Robin Li (Actual results) /* Any situation */ new \ Baidu \ Robin Li (); // Baidu \ Robin Li (actual results)

In the first case, someone already knows Robin Li, you just need to say his name directly, and he will know who you are referring to. In the second case, Robin Li is their CEO. If you say CEO directly, he can immediately respond. Using namespaces just prefixes the class names, makes them less likely to conflict, and still does not automatically import them. Without a file, the __autoload function is fired, passing in the qualified Class name as an argument, before throwing a “Class Not Found” error. So the above example is based on the fact that you have introduced the relevant file manually, otherwise the system will throw a “Class ‘not found'”.

2.4 spl_autoload_register

Next let’s implement auto-loading with namespaces. This is done using spl_autoload_register(), which requires your PHP version number greater than 5.12. The spl_autoload_register function registers all incoming functions (either as callbacks or function names) in the SPL’s __autoload queue and removes the default __autoload() function from the system. Once spl_autoload_register() is called, instead of automatically calling __autoload(), the system calls all the functions registered with spl_autoload_register() in order when an undefined class is called.

Now let’s create a Linux class that uses OS as its namespace (it is recommended that the file name be the same as the class name) :

namespace os; Function __construct() {echo '<h1>'.__class__. '</h1>'; }}

Next, create a new PHP file in the same directory and use spl_autoload_register to auto-load it as a function callback: spl_autoload_register

Spl_autoload_register (function ($class) {// $class_map = array($class_map = array($class_map = array($class_map = array($class_register)) 'os\\Linux' => './Linux.php', ); $file = $class_map[$class]; /* if (file_exists($file)) {include $file; }}); new \os\Linux();

Here we use an array to hold the relationship between the class name and the file path, so that when the class name is passed in, the auto-loader knows which file to import to load the class.

However, once the number of files becomes large, the mapping array becomes very long, which can be quite troublesome to maintain. If you follow a common convention for naming, you can let the auto-loader automatically parse and determine where the class file is located. PSR-4, which will be introduced next, is one of the most widely adopted conventions.

2.4 PSR – 4 specification

PSR-4 is a specification for automatic loading of corresponding classes by file paths. The specification specifies that a fully qualified class name must have the following structure:

\< top-level namespace >(\< subnamespace >)*\< class name >

If we continue with the analogy above, the top-level namespace is the company, the subnamespace is the job, and the class name is the person. The standard title for Robin Li is “Robin Li, CEO of Baidu Inc.”

The PSR-4 specification must have a top-level namespace, the meaning of which is to represent a particular directory (file base directory). The subnamespace represents the path of the class file relative to the base directory (relative path), and the class name is the same as the file name (note the difference in case).

For example, in the fully qualified class name \app\view\news\Index, if app stands for C: Baidu, then the path to the class is C: Baidu\view\news\ index.php

Let’s write a simple Demo with the example of parsing \app\view\news\Index:

$class = 'app\view\news\Index'; / * mapping top-level namespace path * / $vendor_map = array (' app '= >' C: \ Baidu,); $vendor = substr($class, 0, strpos($class, '\\')); $vendor_dir = $vendor_map[$vendor]; $rel_path = dirname(substr($class, strlen($vendor))); $file_name = basename($class). '. PHP '; $vendor_dir. $rel_path. DIRECTORY_SEPARATOR. $file_name;

This Demo shows the process of converting qualified class names to paths. Now let’s implement the autoloader in a canonical object-oriented way.

First we create a file called index. PHP, which is in the \app\ MVC \view\home directory:

namespace app\mvc\view\home; class Index { function __construct() { echo '<h1> Welcome To Home </h1>'; }}

Next we are creating a load class (no namespace required), which is in the directory:

Public static $vendorMap = array('app' => __DIR__. DIRECTORY_SEPARATOR. 'app',); Public static function autoload($class) {$file = self::findFile($class); if (file_exists($file)) { self::includeFile($file); If ($class) {$vendor = substr($class, 0, strpos($class, '\\')); if ($class, 0, strpos($class, '\\')); $vendorDir = self::$vendorMap[$vendor]; $filePath = substr($class, strlen($vendor)). '. PHP '; Return STRTR ($vendorDir. $filePath, '\\', DIRECTORY_SEPARATOR); Private static function includeFile($file) {if (is_file($file)) {include $file; }}}

Finally, register autoload from the Loader class with the spl_autoload_register function:

include 'Loader.php'; Spl_autoload_register ('Loader::autoload'); // Register to auto-load new \app\ MVC \view\home\Index(); // Instantiate an unreferenced class /** * output: <h1> Welcome To Home </h1> */

2.4 composer

With all that said, here comes Composer. I won’t go into the installation details here. Let’s look at the vendor/composer file for details

vendor
----autoload_classmap.php
----autoload_files.php
----autoload_namespace.php
----autoload_psr4.php
----autoload_real.php
----autoload_static.php
----ClassLoader.php
----install.json 
autoload.php

So let me first look at vendor/autoload.php:

<? php // autoload.php @generated by Composer require_once __DIR__ . '/composer' . '/autoload_real.php'; return ComposerAutoloaderInitff1d77c91141523097b07ee2acc23326::getLoader(); It performs a automatically generated class of ComposerAutoloaderInitff1d77c91141523097b07ee2acc23326 getLoader method. Let's follow up with autoload_real.php. public static function getLoader() { if (null ! == self::$loader) { return autoload_real.phpself::$loader; } spl_autoload_register(array('ComposerAutoloaderInitff1d77c91141523097b07ee2acc23326', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInitff1d77c91141523097b07ee2acc23326', 'loadClassLoader')); $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { $loader->set($namespace, $path); } $map = require __DIR__ . '/autoload_psr4.php'; foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } $loader->register(true); $includeFiles = require __DIR__ . '/autoload_files.php'; foreach ($includeFiles as $file) { composerRequireff1d77c91141523097b07ee2acc23326($file); } return $loader; }

PHP, autoload_namespaces. PHP, autoload_psr4.php, autoload_classmap.php, autoload_files.php, etc. After relevant processing (setPsr4), finally register. So let’s follow the Register method:

    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

This function is a one-line, but straightforward one. It calls PHP’s own spl_autoload_register function directly to register the method that handles __autoload, which is called loadClass. Then follow the loadClass method:

public function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; }}

The name of the function gives you a rough idea of the flow: If $class corresponds to this $file, include it. So look inside the findFile method:

Public function findFile ($class) {/ / work around for PHP 5.3.0-5.3.2 https://bugs.php.net/50731 if (= = '\ \' $class[0]) { $class = substr($class, 1); } // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative) { return false; } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if ($file === null && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if ($file === null) { // Remember that this class does not exist. return $this->classMap[$class] = false; } return $file; }

Find the file by the class name and lock it into the findFileWithexTenignon method. Again, follow the FindFileWithexTenignization method:

private function findFileWithExtension($class, $ext) { // PSR-4 lookup $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { if (0 === strpos($class, $prefix)) { foreach ($this->prefixDirsPsr4[$prefix] as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { return $file; } } } } } // PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } // PSR-0 lookup if (false ! == $pos = strrpos($class, '\\')) { // namespaced class name $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // PEAR-like class name $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; } if (isset($this->prefixesPsr0[$first])) { foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } } } } // PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } // PSR-0 include paths. if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return  $file; }}

The final implementation converts the class name, such as the namespace \ class, to a path such as the directory name/class name. PHP, and returns the full path.

I found that autoload in Composer is slightly different from spl_autoload, which comes with PHP, when it comes to including files. That is, spl_autoload looks for the file name of type.inc, but composer does not.

You can also see that although the name of the configuration file is autoload_psr4.php, automatic loading in psr0 format is also supported. The biggest difference between the two is that PSR0 uses “_” instead of “” between directories.

Having said so much above, it’s time to summarize. From __autoload to spl_autoload_register to composer and psr4 methods. What is the purpose of all this official PHP and community design? They are designed to solve the inconvenience of include files. The spl_autoload_register register is used to automatically include the files in the spl_autoload_register. But we can’t just write it, we have to have rules, so we have PSR4.