Autoload source code implementation of composer – startup and initialization

Time:2020-3-9

Preface

In the last article, we discussed the automatic loading principle of PHP, the namespace of PHP, the psr0 and psr4 standards of PHP. With these knowledge, we can write programs that can be loaded automatically according to the psr4 standard. But why do we write it ourselves? Especially with composer, the God like package manager?


Introduction to composer automatic loading

brief introduction

Composer is a dependency management tool for PHP. It allows you to declare the code base that your project depends on, and it will install them for you in your project. For details, please refer to the Chinese website of composer.
Composer composer will solve the problem for you as follows:

  • You have a project that depends on several libraries.
  • Some of these libraries depend on others.
  • You declare what you depend on.
  • Composer will find out which version of the package needs to be installed and install them (download them to your project).

For example, you are creating a project and you need a library for logging. You decide to use monolog. To add it to your project, all you need to do is create a composer. JSON file that describes the project’s dependencies.

  {
  "require": {
  "monolog/monolog": "1.2.*"
  }
  }

                    !
                                  .
  
   so what should we do when we want to write our own namespace? Very simply, we just need to name our namespace according to the psr4 standard, place our files, and write the mapping between the top-level domain name and the specific directory in composer, then we can enjoy the convenience of composer.
                            , of course, if there is a very good framework, we will be surprised to find that we don’t.
                       .

Composer automatically loads files

First, let’s get a general idea of the source files used by composer to load automatically.

  1. Autoload? Real.php: the boot class for the autoload function. The tasks are the initialization (top-level namespace and file path mapping initialization) and registration (SPL ﹣ autoload ﹣ register()) of the composer load class.
  2. Classloader.php: composer loads classes. The core class of composer auto loading function.
  3. Autoload_static.php: the top-level namespace initialization class, which is used to initialize the top-level namespace for the core class.
  4. Autoload_classmap.php: the simplest form of autoload, with complete mapping of namespace and file directory;
  5. Autoload_files.php: it is used to load the files of global functions and store the file path names of each global function;
  6. Autoload_namespaces.php: psr0 compliant autoload file, which stores the mapping between top-level namespace and file;
  7. Autoload ﹤ psr4.php: it is an auto load file that conforms to psr4 standard, and stores the mapping between top-level namespace and file;

Auto loading source code analysis of composer under laravel framework — start


The initialization of laravel framework needs the help of composer automatic loading, so the first sentence of index.php, the entry file of laravel, is to use composer to realize the automatic loading function.

  require __DIR__.'/../bootstrap/autoload.php';

Let’s go to autoload.php in the bootstrap Directory:

  define('LARAVEL_START', microtime(true));

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

Then go to autoload.php in the vendor Directory:

  require_once __DIR__ . '/composer' . '/autoload_real.php';

  return ComposerAutoloaderInit
  832ea71bfb9a4128da8660baedaac82e::getLoader();

   why does the framework go around bootstrap / autoload.php? Personally, laravel is designed to support or expandArbitrarilyThere are third-party libraries that are automatically loaded.
Well, we’re finally going to see where composer really wants to be. Autoload? Real is a guide class of auto load function. This class is not responsible for specific function logic, but only does two things: initialize auto load class and register auto load class.
                            ̀? Because this is to prevent repeated conflicts between user-defined class name and this class, a hash value is added to the class name. In fact, we are more familiar with another approach, which is to define a namespace instead of a class name. Why not define a namespace here? Personal understanding: namespace is generally for reuse, and this class only needs to run once, and it will not be used in the future. It is more appropriate to use hash value.

Analysis of auto loading source code of composer under laravel framework


In the autoload.php file in the vendor directory, we can see that the program mainly calls the static method getloader () of the boot class. Let’s take a look at this function.

public static function getLoader()
  {
  /***************************Classic singleton mode********************/
  if (null !== self::$loader) {
  return self::$loader;
  }

  /***********************Get auto load core class object********************/
  spl_autoload_register(array('ComposerAutoloaderInit
  832ea71bfb9a4128da8660baedaac82e', 'loadClassLoader'), true, true);

  self::$loader = $loader = new \Composer\Autoload\ClassLoader();

  spl_autoload_unregister(array('ComposerAutoloaderInit
  832ea71bfb9a4128da8660baedaac82e', 'loadClassLoader'));

  /***********************Initialize auto load core class object********************/
  $useStaticLoader = PHP_VERSION_ID >= 50600 &&
  !defined('HHVM_VERSION');

  if ($useStaticLoader) {
  require_once __DIR__ . '/autoload_static.php';

  call_user_func(\Composer\Autoload\ComposerStaticInit
  832ea71bfb9a4128da8660baedaac82e::getInitializer($loader));

  } else {
  $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);
  }
  }

  /***********************Register to automatically load core class objects********************/
  $loader->register(true);

  /***********************Auto load global function********************/
  if ($useStaticLoader) {
  $includeFiles = Composer\Autoload\ComposerStaticInit
  832ea71bfb9a4128da8660baedaac82e::$files;
  } else {
  $includeFiles = require __DIR__ . '/autoload_files.php';
  }

  foreach ($includeFiles as $fileIdentifier => $file) {
  composerRequire
  832ea71bfb9a4128da8660baedaac82e($fileIdentifier, $file);
  }

  return $loader;
  }

As you can see from the above, I divided the auto load guide class into five parts.

Part I – single example

The first part is very simple. It is the most classic singleton mode. There can only be one auto loading class.

  if (null !== self::$loader) {
  return self::$loader;
  }

Part 2: construct the classloader core class

In the second part, new is an auto loading core class object.

/***********************Get auto load core class object********************/
  spl_autoload_register(array('ComposerAutoloaderInit
  832ea71bfb9a4128da8660baedaac82e', 'loadClassLoader'), true, true);

  self::$loader = $loader = new \Composer\Autoload\ClassLoader();

  spl_autoload_unregister(array('ComposerAutoloaderInit
  832ea71bfb9a4128da8660baedaac82e', 'loadClassLoader'));

Loadclassloader() function:

public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}

From the program, we can see that composer first registers a function with the PHP auto loading mechanism, which requires the classloader file. The function is destroyed after the core class classloader () in the file is successfully new. Why don’t you just ask for it? Why bother? The reason is that some users are afraid of defining a composerautoloadclassloader namespace, which results in the automatic loading of error files. So why not use a hash like a boot class? Because this class can be reused, the framework allows users to use this class.

Part 3 – initializing core class objects

/***********************Initialize auto load core class object********************/
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
  if ($useStaticLoader) {
  require_once __DIR__ . '/autoload_static.php';

  call_user_func(\Composer\Autoload\ComposerStaticInit
  832ea71bfb9a4128da8660baedaac82e::getInitializer($loader));
  } else {
  $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);
  }
  }

This part is the initialization of auto loading classes, mainly initializing top-level namespace mapping for auto loading core classes. There are two methods of initialization: (1) use autoload? Static for static initialization; (2) call the core class interface for initialization.

Autoload? Static static initialization

Static initialization only supports php5.6 and above and does not support hhvm virtual machine. We go deep into the file autoload_static.php and find that this file defines a class for static initialization. Its name is composerstaticinit832ea71bb9a4128da8660baedaac82e, and the hash value is added to avoid conflicts. This class is simple:

  class ComposerStaticInit832ea71bfb9a4128da8660baedaac82e{
public static $files = array(...);
public static $prefixLengthsPsr4 = array(...);
public static $prefixDirsPsr4 = array(...);
public static $prefixesPsr0 = array(...);
public static $classMap = array (...);

  public static function getInitializer(ClassLoader $loader)
  {
  return \Closure::bind(function () use ($loader) {
  $loader->prefixLengthsPsr4 = ComposerStaticInit832ea71bfb9a4128da8660baedaac82e::$prefixLengthsPsr4;
  $loader->prefixDirsPsr4 = ComposerStaticInit832ea71bfb9a4128da8660baedaac82e::$prefixDirsPsr4;
  $loader->prefixesPsr0 = ComposerStaticInit832ea71bfb9a4128da8660baedaac82e::$prefixesPsr0;
  $loader->classMap = ComposerStaticInit832ea71bfb9a4128da8660baedaac82e::$classMap;

  }, null, ClassLoader::class);
  }

   the core of this static initialization class is the getinitializer() function, which maps the top-level namespace in its own class to the classloader class. It’s worth noting that this function returns an anonymous function. Why? The reason is that the prefixlengthspsr4, prefixdirspsr4, etc. in classloader class are all private… Ordinary functions cannot assign values to private member variables of a class. By using the binding function of anonymous function, anonymous function can be transformed into member function of classloader class. About the binding function of anonymous function.
   next is the key to the initialization of the top-level namespace.

The simplest classmap:

  public static $classMap = array (
  'App\\Console\\Kernel' => __DIR__ . '/../..' . '/app/Console/Kernel.php',
  'App\\Exceptions\\Handler' => __DIR__ . '/../..' . '/app/Exceptions/Handler.php',
  'App\\Http\\Controllers\\Auth\\ForgotPasswordController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/ForgotPasswordController.php',
  'App\\Http\\Controllers\\Auth\\LoginController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/LoginController.php',
  'App\\Http\\Controllers\\Auth\\RegisterController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/RegisterController.php',
  ...)

Simple, direct namespace full name and directory mapping, no top-level namespace… Simple and crude also makes the array quite large.

Psr0 top level namespace mapping:

public static $prefixesPsr0 = array (
  'P' =>
  array (
  'Prophecy\\' =>
  array (
  0 => __DIR__ . '/..' . '/phpspec/prophecy/src',
  ),
  'Parsedown' =>
  array (
  0 => __DIR__ . '/..' . '/erusev/parsedown',
  ),
  ),
  'M' =>
  array (
  'Mockery' =>
  array (
  0 => __DIR__ . '/..' . '/mockery/mockery/library',
  ),
  ),
  'J' =>
  array (
  'JakubOnderka\\PhpConsoleHighlighter' =>
  array (
  0 => __DIR__ . '/..' . '/jakub-onderka/php-console-highlighter/src',
  ),
  'JakubOnderka\\PhpConsoleColor' =>
  array (
  0 => __DIR__ . '/..' . '/jakub-onderka/php-console-color/src',
  ),
  ),
  'D' =>
  array (
  'Doctrine\\Common\\Inflector\\' =>
  array (
  0 => __DIR__ . '/..' . '/doctrine/inflector/lib',
  ),
  ),
  );

To quickly find the top-level namespace, we use the first letter of the namespace as the prefix index. The usage of this mapping is obvious. If we have a namespace such as parsedown / example, first find it through the initial P

  'P' =>
  array (
  'Prophecy\\' =>
  array (
  0 => __DIR__ . '/..' . '/phpspec/prophecy/src',
  ),
  'Parsedown' =>
  array (
  0 => __DIR__ . '/..' . '/erusev/parsedown',
  ),
  )

This array, and then we will traverse this array to compare it with parsedown / example. We find that the first prophecy does not match the second parsedown, and then we get the mapping directory:(There may be more than one mapping directory)

  array (
  0 => __DIR__ . '/..' . '/erusev/parsedown',
  )

We’ll then iterate through the array and try_DIR_. ‘/..’. ‘/ erusev / parsedown / parsedown / example.php exists. If it doesn’t exist, it will traverse the array (this example array has only one element). If it doesn’t, it will fail to load.

Psr4 standard top level namespace mapping array:

  public static $prefixLengthsPsr4 = array(
  'p' =>
  array (
  'phpDocumentor\\Reflection\\' => 25,
  ),
  'S' =>
  array (
  'Symfony\\Polyfill\\Mbstring\\' => 26,
  'Symfony\\Component\\Yaml\\' => 23,
  'Symfony\\Component\\VarDumper\\' => 28,
  ...
  ),
  ...);

  public static $prefixDirsPsr4 = array (
  'phpDocumentor\\Reflection\\' =>
  array (
  0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',
  1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',
  2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src',
  ),
  'Symfony\\Polyfill\\Mbstring\\' =>
  array (
  0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
  ),
  'Symfony\\Component\\Yaml\\' =>
  array (
  0 => __DIR__ . '/..' . '/symfony/yaml',
  ),
  ...)

                           . Why? Because as we said in the previous article, the file directory of psr4 standard is more flexible and concise. The top-level namespace directory in psr0 can be directly added to the front of the namespace to get the path (parsedown / example = >_DIR_ .’/..’ . ‘/erusev/parsedown/Parsedown/example.php)The psr4 standard replaces the top-level namespace with the top-level namespace directory (parsedown / example = >_DIR_ .’/..’ . ‘/erusev/parsedown/example.php), so it’s important to get the length of the top-level namespace.
   specific usage: if we look for the namespace symfony \ Polyfill \ mbstring \ example, like psr0, we get it through prefix index and string matching

  'Symfony\\Polyfill\\Mbstring\\' => 26,

For this record, the key is the top-level namespace and the value is the length of the namespace. After getting the top-level namespace, go to the $prefixdirspsr4 array to get its mapping directory array:(Note that there may be more than one mapping directory)

  'Symfony\\Polyfill\\Mbstring\\' =>
  array (
  0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
  )

Then we can replace the first 26 characters of the namespace symfony \ Polyfill \ mbstring \ example with a directory_DIR_‘/..’. ‘/ symfony / Polyfill mbstring, we get_DIR_. ‘/..’. ‘/ symfony / polyfill-mbstring / example.php, first verify whether the file exists on the disk, and then traverse if it does not. If not found after traversal, the load fails.
  
                  !!!

Classloader interface initialization


If the PHP version is lower than 5.6 or the hhvm virtual machine environment is used, the interface of the core class should be used for initialization.

//PSR0 standard
  $map = require __DIR__ . '/autoload_namespaces.php';
  foreach ($map as $namespace => $path) {
  $loader->set($namespace, $path);
  }

  //PSR4 standard
  $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);
  }

PSR0 standard

autoload_namespaces:

  return array(
  'Prophecy\\' => array($vendorDir . '/phpspec/prophecy/src'),
  'Parsedown' => array($vendorDir . '/erusev/parsedown'),
  'Mockery' => array($vendorDir . '/mockery/mockery/library'),
  'JakubOnderka\\PhpConsoleHighlighter' => array($vendorDir . '/jakub-onderka/php-console-highlighter/src'),
  'JakubOnderka\\PhpConsoleColor' => array($vendorDir . '/jakub-onderka/php-console-color/src'),
  'Doctrine\\Common\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib'),
);

Psr0 standard initialization interface:

  public function set($prefix, $paths)
  {
  if (!$prefix) {
  $this->fallbackDirsPsr0 = (array) $paths;
  } else {
  $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
  }
  }

Very simply, the psr0 standard takes the first letter of the namespace as the index. An index corresponds to multiple top-level namespaces, and a top-level namespace corresponds to multiple directory paths. For the specific form, see the $prefixes psr0 of autoload ﹣ static. If there is no top-level namespace, only one pathname is stored to try to load later.

PSR4 standard

autoload_psr4

  return array(
  'XdgBaseDir\\' => array($vendorDir . '/dnoegel/php-xdg-base-dir/src'),
  'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
  'TijsVerkoyen\\CssToInlineStyles\\' => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'),
  'Tests\\' => array($baseDir . '/tests'),
  'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
  ...
  )

Initialization interface of psr4 standard:

  public function setPsr4($prefix, $paths)
  {
  if (!$prefix) {
  $this->fallbackDirsPsr4 = (array) $paths;
  } else {
  $length = strlen($prefix);
  if ('\\' !== $prefix[$length - 1]) {
  throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
  }
  $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
  $this->prefixDirsPsr4[$prefix] = (array) $paths;
  }
  }

The psr4 initialization interface is also simple. If there is no top-level namespace, save the directory directly. If there is a namespace, make sure that the top-level namespace is \, and then save (prefix =) top-level namespace, top-level namespace =) top-level namespace length and (top-level namespace =) directory respectively. For the specific form, you can see the prefixlengthspsr4 and $prefixdirspsr4 of the autoload [static].

Fool namespace mapping

autoload_classmap:

  public static $classMap = array (
  'App\\Console\\Kernel' => __DIR__ . '/../..' . '/app/Console/Kernel.php',
  'App\\Exceptions\\Handler' => __DIR__ . '/../..' . '/app/Exceptions/Handler.php',
  ...
  )

addClassMap:

public function addClassMap(array $classMap)
  {
  if ($this->classMap) {
  $this->classMap = array_merge($this->classMap, $classMap);
  } else {
  $this->classMap = $classMap;
  }
  }

The simplest is the mapping between the entire namespace and the directory.

epilogue

Actually, I would like to write it down, but it will be too long, so I will put the registration and operation of auto loading into the next article. Let’s review. This article mainly talks about: (1) how the framework starts the composer automatic loading; (2) the composer automatic loading is divided into five parts;
                   . Initialize the directory mapping responsible for the top-level namespace, and register the namespace mapping rules responsible for implementing the following top-level.

Written with StackEdit.

Original link: https://www.zhanggaoyuan.com/article/30

Original title: [autoload source implementation of composer – startup and initialization]

This site uses the “signature – non-commercial use 4.0 International (CC by-nc 4.0)” creative sharing agreement. For reprint or use, please sign and indicate the source.
This article is automatically published by artipub