Laravel Composer automatic loading mechanism

outline

  • PHP autoloading function
    1.1 Origin of PHP autoloading function
    1.2 PHP autoload function u autoload()
    Problems with 1.3 u autoload() Function
    1.4 SPL Autoload
  • PSR Specification
  • The automatic loading process of comoposer
  • composer source analysis
    4.1 Startup
    4.2 Composer autoload file

1, PHP autoloading function

1.1 Origin of PHP autoloading function

In PHP development, if you want to introduce a Class from outside, you usually use include and require methods to include the files that define this Class.It's not a big problem when it's being developed on a small scale.However, in large development projects, using this method can cause some implicit problems: if a PHP file needs to use many other classes, it requires a lot of require/include statements, which can cause omissions or include unnecessary class files.If you have a large number of files that require other classes, it can be a nightmare to ensure that each file contains the correct class file, and require or incloud can be expensive to perform.

PHP5 provides a solution to this problem, which is the class autoload mechanism.The autoload mechanism, also known as Lazy loading, allows PHP programs to automatically include class files when using classes instead of including all class files in the first place.

In summary, the autoloading feature offers several advantages:

  1. You do not need include / require before using classes
  2. The include / require file is only used when classes are used, which implements lazy loading, avoiding include / require redundant files.
  3. Separation of logical and physical files is achieved without regard to the actual disk address of the class introduced.

1.2 PHP autoload function u autoload()

  • Beginning with PHP5, when we use a class, if we find that the class is not loaded, we automatically run the u autoload() function, which we customize in our program, in which we can load the classes we need to use.Here is a simple example:

    <?php
    
    function __autoload($classname) {
            require_once ($classname . ".class.php");
    }
  • In our simple example, we construct a class file name directly by adding the class name with the extension.class.php and then load it using require_once.

    From this example, we can see that u autoload does at least three things:

    1. Determine the class file name based on the class name;
    2. Determines the disk path where the class file is located;
    3. Load classes from disk files into the system.
  • The third step is the easiest, just use include / require.To achieve the first step, the second step requires that you agree on a method for mapping the class name to the disk file at development time so that we can find its corresponding disk file based on the class name.
  • When there are a large number of class files to include, we just need to determine the rules, and then in the u autoload() function, we can achieve the lazy load effect by matching the class name to the actual disk file.
  • If you want to know more about the autoload autoloading process, you can check the manual: PHP autoload function description

Problems with 1.3 u autoload() Function

  • If many other class libraries are needed in a system implementation, they may have been written by different developers with different class names than the actual disk file mapping rules.In order to automatically load the class library files, all the mapping rules must be implemented in the u autoload() function, which may make the u autoload() function complex or even impossible to implement.Ultimately, the u autoload() function may become very bulky, which, if implemented, will have a significant negative impact on future maintenance and system efficiency.
  • So where did the problem occur?The problem arises when u autoload() is a global function that can only be defined once, which is not flexible enough, so all the logical rules for class names and file names need to be implemented in one function, causing the function to become bloated.So how to solve this problem?The answer is to use a u autoload call stack, write different mapping relationships into different u autoload functions, and then register for unified management, which is SPL Autoload introduced by PHP5.

1.4 SPL Autoload

SPL is the abbreviation of Standard PHP Library.It is an extended standard library introduced by PHP5, including spl autoload-related functions and interfaces or classes for various data structures and iterators.The spl autoload-related functions are specifically visible spl_autoload in php

<?php

// _u autoload function
//
// function __autoload($class) {
//     include 'classes/' . $class . '.class.php';
// }


function my_autoloader($class) {
    include 'classes/' . $class . '.class.php';
}

spl_autoload_register('my_autoloader');


// Defined autoload function in class

// Static method
class MyClass {
  public static function autoload($className) {
    // ...
  }
}

spl_autoload_register(array('MyClass', 'autoload'));

// Non-static method
class MyClass {
  public function autoload($className) {
    // ...
  }
}

$instance = new MyClass();
spl_autoload_register(array($instance, 'autoload'));

spl_autoload_register() is the u autoload call stack we mentioned above. We can register many of our own autoload() functions with this function. When PHP cannot find the class name, PHP will call this stack, and then call the custom autoload() function to achieve autoload function.If we don't enter any parameters into this function, the spl_autoload() function is registered by default.

 

2, PSR specification

The specification associated with automatic loading is PSR4, which is introduced before PSR4.The organization that invented and introduced the PSR standard is PHP-FIG. Its website is www.php-fig.org.Founded in 2009 by several open source framework developers, many other members have been selected since then, although not "official" organizations, but representing a small segment of the community.The purpose of the organization is to unify the coding specifications for each project with minimal restrictions and to avoid the frustration of programmers being hampered by their own style of development. The PSR was invented and summarized. PSR is the abbreviation of PHP Standards Recommendation. So far, there are 14 sets of PSR specifications, of which 7 have been voted and introduced.Use:

PSR-0  Autoloading standards (obsolete, some old third-party libraries still in use)

PSR-1 Basic Coding Standard

PSR-2 Encoding Style Wizard

PSR-3 Logging Interface

PSR-4 autoloaded enhanced version, replacing PSR-0

PSR-6 Cache Interface Specification

PSR-7 HTTP Message Interface Specification

Detailed specifications can be viewed PHP Standard Specification

 

2.1 PSR4 standard

At the end of 2013, PHP-FIG introduced the fifth specification, PSR-4.

PSR-4 specifies how file paths are specified to automatically load class definitions, and where files are automatically loaded.

2.1.1 A complete class name needs to have the following structure:

\<Namespace>\<Subnamespace>\<Class Name>

  • A complete class name must have a top-level namespace, called a "vendor namespace";
  • A complete class name can have one or more child namespaces;
  • The complete class name must have a final class name;
  • Slidelines in any part of a complete class name have no special meaning;
  • The complete class name can be composed of any upper and lower case letter.
  • All class names must be case sensitive.

2.2.2 Load the appropriate file based on the complete class name

  • In a complete class name, remove the leading namespace separator, one or more consecutive namespaces and subnamespaces in front, which must correspond to at least one File Base Directory as a Namespace Prefix.
  • Subnamespaces immediately after the namespace prefix must match the corresponding File Base Directory, where the namespace delimiter will act as the directory delimiter.
  • The class name at the end must have the same name as the corresponding file suffixed with.php.
  • The implementation of an autoloader must not throw exceptions, trigger error information at any level, or have no return value.

2.2.3 cases

PSR-4 Style

Class name: ZendAbc
Namespace prefix: Zend
File base directory: /usr/include/Zend/
File path: /usr/include/Zend/Abc.php
Class name: SymfonyCoreRequest
Namespace prefix: SymfonyCore
File base directory:. /vendor/Symfony/Core/
File path:. /vendor/Symfony/Core/Request.php

directory structure

-vendor/
| -vendor_name/
| | -package_name/
| | | -src/
| | | | -ClassName.php       # Vendor_Name\Package_Name\ClassName
| | | -tests/
| | | | -ClassNameTest.php   # Vendor_Name\Package_Name\ClassNameTest

3, Composer automatic loading process

3.1 What did Composer do

  • 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 packages need to be installed and install them (download them to your project).

For example, if you are creating a project, you need to do some unit tests.You decide to use phpunit.In order to add it to your project, all you need to do is describe the dependencies of the project in the composer.json file.

 {
   "require": {
     "phpunit/phpunit":"~6.0",
   }
 }

Then, after composer require, we can only use the phpunit class directly within the project.

3.2 What happened when composer require was executed

  • composer will find sources for third-party libraries that conform to the PR4 specification
  • Load it into the vendor directory
  • Initialize mapping of the top-level domain name and write it to the specified file

(e.g.'PHPUnit\Framework\Assert'=> u DIR_u.'/.'.'.'.'.'/phpunit/phpunit/src/Framework/Assert.php')

  • Write an autoload function and register it with spl_autoload_register()

Off-topic topic: Many frameworks have already helped us write the top-level domain name mapping. We just need to create a new file in the framework, write the namespace in the new file, and use our namespace anywhere.

4, Composer Source Analysis

Next, we'll look at how composer implements the PSR4 standard's autoloading capabilities by analyzing the source code.

Many frameworks use composer to assist in automatic loading when they are initialized. For Laravel, for example, the first sentence of the entry file index.php uses composer to implement automatic loading.

4.1 Startup

<?php
  define('LARAVEL_START', microtime(true));

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

Remove autoload.php from vendor directory:

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

  return ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29::getLoader();

Here's where Composer really starts

4.2 Composer autoload file

First, let's get a general overview of the source files that Composer uses to load automatically.

  1. autoload_real.php: Boot class for the autoload function.

    • Initialization of the composer load class (top-level namespace and file path mapping initialization) and registration (spl_autoload_register()).
  2. ClassLoader.php: composer loads the class.

    • Core classes for composer autoload functionality.
  3. autoload_static.php: Top-level namespace initialization class,

    • Used to initialize the top-level namespace for core classes.
  4. autoload_classmap.php: The simplest form of automatic loading,

    • Has complete namespace and file directory mappings;
  5. autoload_files.php: The file used to load the global function.

    • Store the file path names where the global functions are located.
  6. autoload_namespaces.php: PSR0-compliant autoload file,

    • Stores mappings from top-level namespaces to files;
  7. autoload_psr4.php: PSR4-compliant autoload file,

    • Stores mappings from top-level namespaces to files;

autoload_real boot class

In the autoload.php file in the vendor directory, we can see that the program mainly calls the static method getLoader() to boot the class. Let's look at this function next.

<?php
    public static function getLoader()
    {
      if (null !== self::$loader) {
          return self::$loader;
      }

      spl_autoload_register(
        array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'), true, true
      );

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

      spl_autoload_unregister(
        array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader')
      );

      $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');

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

          call_user_func(
          \Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::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 for automatic loading of core class objects******************/
      $loader->register(true);

      /***********************Automatically loading global functions********************/
      if ($useStaticLoader) {
          $includeFiles = Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files;
      } else {
          $includeFiles = require __DIR__ . '/autoload_files.php';
      }

      foreach ($includeFiles as $fileIdentifier => $file) {
          composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);
      }

      return $loader;
    }

I divide the autoload boot class into five parts.

Part I - Single Cases

The first part is simple, it is the most classic single-case mode, and there can only be one autoloaded class.

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

Part Two - Constructing ClassLoader Core Classes

The second part, new, is an autoloaded core class object.

<?php
  /***********************Getting autoloaded core class objects********************/
  spl_autoload_register(
    array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'), true, true
  );

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

  spl_autoload_unregister(
    array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader')
  );

loadClassLoader() function:

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

From the program, we can see that composer first registered a function with the PHP autoloading mechanism, which require s the ClassLoader file.The function was destroyed after new successfully exported the core class ClassLoader() in the file.

Part Three - Initializing Core Class Objects

<?php
  /***********************Initialize automatic loading of core class objects******************/
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
  if ($useStaticLoader) {
     require_once __DIR__ . '/autoload_static.php';

     call_user_func(
       \Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::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 the auto-loading class, mainly the initialization of the top-level namespace mapping for the auto-loading core class.

There are two ways to initialize:

  1. Use autoload_static for static initialization;
  2. Initialize the core class interface.

autoload_static static initialization (PHP >= 5.6)

Static initialization only supports PHP5.6 and above and does not support HHVM virtual machines.We went into autoload_static.php and found that this file defines a class for static initialization called ComposerStaticInit7b790917ce8899df9af8ed53631a1c29, which still adds hash values to avoid conflicts.This class is simple:

<?php
  class ComposerStaticInit7b790917ce8899df9af8ed53631a1c29{
     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
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixLengthsPsr4;

          $loader->prefixDirsPsr4
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixDirsPsr4;

          $loader->prefixesPsr0
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixesPsr0;

          $loader->classMap
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$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 is worth noting that this function returns an anonymous function, why?The reason is that variables prefixLengthsPsr4, prefixDirsPsr4, and so on, in the ClassLoader class are private.These privates can be assigned to member variables in the ClassLoader class by using the binding capabilities of anonymous functions.

About Anonymous Functions Binding function.

Next comes the key to namespace initialization.

classMap (Namespace Mapping)

<?php
  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',
  ...)

The direct mapping of the full name of the namespace to the directory is straightforward and rough, resulting in a fairly large array.

PSR4 standard top-level namespace mapping array:

<?php
  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',
    ),
  ...)

The PSR4 standard top-level namespace mapping uses two arrays, the first prefix index is the first letter of the namespace, then the top-level namespace, but ultimately not the file path, but the length of the top-level namespace.Why?

Because the PSR4 standard replaces the top-level namespace with a top-level namespace directory, it is important to obtain the length of the top-level namespace.

Explain the role of these arrays:

If we look for the namespace Symfony\Polyfill\Mbstring\example, we get it by prefix index and string matching

<?php
    'Symfony\\Polyfill\\Mbstring\\' => 26,

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

<?php
  '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 the directory u DIR_u.'/.'.'.'/symfony/polyfill-mbstring, and we get u DIR_.'/.'.'.'.'.'.'.'.'.'/symfony/polyfill-string/example.php, verify that the file exists on disk first, and then iterate through it if it does not exist.If it is not found after traversal, the load fails.

ClassLoader interface initialization (PHP < 5.6)

If the PHP version is less than 5.6 or the HHVM virtual machine environment is used, then the interface of the core class is used for initialization.

<?php
    // 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);
    }

Mapping of PSR4 Standard

Top-level namespace mapping for autoload_psr4.php

<?php
    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'),
    ...
    )

PSR4 Standard Initialization Interface:

<?php
    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;
        }
    }

To summarize the top-level namespace mapping process above:

(prefix->top-level namespace, top-level namespace->top-level namespace len gt h)
(top-level namespace - > directory)

These two mapping arrays.You can also see autoload_static, prefixDirsPsr4 below.

Namespace Mapping

autoload_classmap:

<?php
public static $classMap = array (
    'App\\Console\\Kernel'
        => __DIR__ . '/../..' . '/app/Console/Kernel.php',

    'App\\Exceptions\\Handler'
        => __DIR__ . '/../..' . '/app/Exceptions/Handler.php',
    ...
)

addClassMap:

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

Static initialization of the autoloading core class ClassLoader is done here!

In fact, there are five parts. The two really important parts are initialization and registration.Initialize the directory mapping responsible for the top-level namespace, and register the namespace mapping rules responsible for implementing below the top level.

Part 4 - Registration

Finished with the start and initialization of the Composer autoload function, after the start and initialization, the autoload core class object has been mapped to the top-level namespace and the corresponding directory, that is, if there is a namespace'App\Console\Kernel', we can find its corresponding class file location.So when was it triggered to look for it?

This is the core of composer autoloading, so let's review the autoloading boot classes first:

 public static function getLoader()
 {
    /***************************Classic Singleton Pattern********************/
    if (null !== self::$loader) {
        return self::$loader;
    }
    
    /***********************Getting autoloaded core class objects********************/
    spl_autoload_register(array('ComposerAutoloaderInit
    7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'), true, true);
    
    self::$loader = $loader = new \Composer\Autoload\ClassLoader();
    
    spl_autoload_unregister(array('ComposerAutoloaderInit
    7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'));

    /***********************Initialize automatic loading of core class objects******************/
    $useStaticLoader = PHP_VERSION_ID >= 50600 && 
    !defined('HHVM_VERSION');
    
    if ($useStaticLoader) {
        require_once __DIR__ . '/autoload_static.php';

        call_user_func(\Composer\Autoload\ComposerStaticInit
        7b790917ce8899df9af8ed53631a1c29::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 for automatic loading of core class objects******************/
    $loader->register(true);

    /***********************Automatically loading global functions********************/
    if ($useStaticLoader) {
        $includeFiles = Composer\Autoload\ComposerStaticInit
        7b790917ce8899df9af8ed53631a1c29::$files;
    } else {
        $includeFiles = require __DIR__ . '/autoload_files.php';
    }
    
    foreach ($includeFiles as $fileIdentifier => $file) {
        composerRequire
        7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);
    }

    return $loader;
} 

Now let's begin the fourth part of the bootstrapping class: registering to automatically load core class objects.Let's look at the register() function for the core class:

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

The secret is in the loadClass() function that automatically loads the core class ClassLoader:

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

            return true;
        }
    }

This function is responsible for converting the contents below the top-level namespace to the corresponding directory according to the PSR standard, that is, the section'ConsoleKernel'in'App\ConsoleKernel' mentioned above into a directory, and the section on how to do so in the "Run" section below.The core class ClassLoader registers the loadClass() function with spl_autoload_register() in PHP SPL.This way, whenever PHP encounters an unknown namespace, PHP automatically calls the loadClass() function registered with spl_autoload_register and finds the file corresponding to the namespace.

Automatic Loading of Global Functions

Composer can load not only namespaces automatically, but also global functions.How can it be achieved?Write global functions into specific files, requiring each other before the program runs.This is the fifth step of composer autoloading to load global functions.

if ($useStaticLoader) {
    $includeFiles = Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files;
} else {
    $includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
    composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);
}

As with the initialization of core classes, global function autoloading can be divided into two types: static and normal. Static loading only supports PHP5.6 or above and does not support HHVM.

Static Initialization:

ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files:

public static $files = array (
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
...
);

Normal Initialization

autoload_files:

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
    
return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
   ....
);

Actually, it is not much different from static initialization.

Loading global functions

class ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29{
  public static function getLoader(){
      ...
      foreach ($includeFiles as $fileIdentifier => $file) {
        composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);
      }
      ...
  }
}

function composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file)
 {
    if (empty(\$GLOBALS['__composer_autoload_files'][\$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}

Part 5 - Operations

This brings you to the heart of the matter, the truth of composer autoloading, and the secret of how namespaces can be turned into directory files via composer.
As mentioned earlier, ClassLoader's register() function registers the loadClass() function with PHP's SPL function stack, and each function in the function stack is called whenever PHP encounters an unknown namespace until the namespace is loaded successfully.So the loadClass() function is the key to automatic loading.

Look at the loadClass() function:

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

        return true;
    }
}

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;
}

We see loadClass(), which calls the findFile() function primarily.FindFile () parses a namespace in two main parts: the classMap and findFileWithExtension () functions.ClassMap is simple, just look directly at whether the namespace is in the map array.The trouble is the findFileWithExtension() function, which contains implementations of the PSR0 and PSR4 standards.It is also worth noting that includeFile() is still an external function, not a member function of ClassLoader, after finding the path successfully, and works the same way to prevent users from writing $this or self.Also, if the namespace starts with \, remove \ and match again.

Look at the findFileWithExtension function:

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;
    }
}

Final summary

Here is an example of how the code above works:

If we write a new phpDocumentor\Reflection\Element() in our code, PHP calls loadClass -> findFile -> findFileWithExtension via SPL_autoload_register.The steps are as follows:

  • Convert\to the file separator/, with the suffix php, to $logicalPathPsr4, phpDocumentor/Reflection//Element.php;
  • Search the prefixLengthsPsr4 array using the first letter p of the namespace as the prefix index and find the following array:
        p' => 
            array (
                'phpDocumentor\\Reflection\\' => 25,
                'phpDocumentor\\Fake\\' => 19,
          )
  • Traverse the array to get two top-level namespaces phpDocumentor\Reflection\ and phpDocumentor\Fake\
  • Find the phpDocumentor\Reflection\Element in this array, the phpDocumentorReflection\top-level namespace, and the length is 25.
  • The directory mapping of phpDocumentor\Reflection\ in the prefixDirsPsr4 mapping array is as follows:
    'phpDocumentor\\Reflection\\' => 
        array (
            0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',
            1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',
            2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src',
        ),
  • Iterate through this mapping array to get three directory mappings;
  • Check that the directory + file separator //+ substr (&dollar; logicalPathPsr4, &dollar; length) file exists and returns when it exists.Here's it
    '__DIR__/../phpdocumentor/reflection-common/src + substr(phpDocumentor/Reflection/Element.php,25)'
  • If it fails, use the directory inside the fallbackDirsPsr4 array to continue to determine if there are files

That's why composer loads automatically!

Keywords: Programming PHP encoding JSON Laravel

Added by Dolemite50 on Sat, 14 Dec 2019 10:54:58 +0200