PHP management relies on of dependency relational tool Composer auto loading (autoload)

  • 2021-07-13 04:34:36
  • OfStack

For example, suppose our project wants to use the logging tool monolog, we need to tell composer in composer. json that we need it:


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

Then execute:


php composer.phar install

OK, now that the installation is finished, how should I use it? Composer automatically generates an autoload file, you only need to reference it


require '/path/to/vendor/autoload.php';

Then you can use the third-party class library very conveniently. Does it feel great? For the monolog we need, we can use it like this:


use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// create a log channel
$log = new Logger('name');
$log->pushHandler(new StreamHandler('/path/to/log/log_name.log', Logger::WARNING));
// add records to the log
$log->addWarning('Foo');
$log->addError('Bar');

What did Composer do in the process? It generates an autoloader, and then according to the autoload configuration of each package, it helps us to load automatically. (If you don't know much about this part of autoload, you can read my previous article.)
) Let's take a look at how Composer works.

For auto-loading of third-party packages, Composer provides support in four ways, namely, auto-loading of PSR-0 and PSR-4 (which was also introduced in one of my articles), generating class-map, and directly including files.

PSR-4 is recommended for composer because it is easier to use and brings a simpler directory structure. This is configured in composer. json:


{
  "autoload": {
    "psr-4": {
      "Foo\\": "src/",
    }
  }
}

key and value define namespace and the mapping to the corresponding path. According to the rules of PSR-4, when you try to automatically load the class "Foo\\ Bar\\ Baz", you look for the file "src/Bar/Baz. php" and load it if it exists. Note that "Foo\\"
Does not appear in the file path, which is a different point from PSR-0. If PSR-0 has this configuration, it will look for

"src/Foo/Bar/Baz.php"

This file.

Also note that in the configuration of PSR-4 and PSR-0, the namespace delimiter at the end of "Foo\\" must be added and escaped to prevent the unexpected occurrence that "Foo" matches "FooBar".

After composer is installed or updated, the configuration of psr-4 is converted to Map with namespace being key and dir path being value, and written into the generated vendor/composer/autoload_psr4. php file.


{
  "autoload": {
    "psr-0": {
      "Foo\\": "src/",
    }
  }
}

Finally, this configuration is also written into the generated Map

vendor/composer/autoload_namespaces.php

In the file.

Class-map mode is through configuring the specified directory or file, and then when Composer is installed or updated, it will scan class in the file ending with. php or. inc in the specified directory, generate the mapping of class to the specified file path, and add it to the newly generated vendor/composer/autoload_classmap. php file.


{
  "autoload": {
    "classmap": ["src/", "lib/", "Something.php"]
  }
}

For example, if there is an BaseController class under src/, in the autoload_classmap. php file, this configuration is generated:

'BaseController' = > $baseDir . '/src/BaseController.php'

Files means manually specifying files for direct loading. For example, we have a series of global helper functions, which can be put into an helper file and then loaded directly


{
  "autoload": {
    "files": ["src/MyLibrary/functions.php"]
  }
}

It generates 1 array containing the files specified in these configurations and writes to the newly generated

vendor/composer/autoload_files.php

File for autoloader to load directly.

Let's take a look at the code of composer autoload


<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit73612b48e6c3d0de8d56e03dece61d11
{
  private static $loader;
  public static function loadClassLoader($class)
  {
 if ('Composer\Autoload\ClassLoader' === $class) {
   require __DIR__ . '/ClassLoader.php';
 }
  }
  public static function getLoader()
  {
 if (null !== self::$loader) {
   return self::$loader;
 }
 spl_autoload_register(array('ComposerAutoloaderInit73612b48e6c3d0de8d56e03dece61d11', 'loadClassLoader'), true, true);
 self::$loader = $loader = new \Composer\Autoload\ClassLoader();
 spl_autoload_unregister(array('ComposerAutoloaderInit73612b48e6c3d0de8d56e03dece61d11', 'loadClassLoader'));
 $vendorDir = dirname(__DIR__); //verdor No. 1 3 Square class library provider directory 
 $baseDir = dirname($vendorDir); // Directory of the whole application 
 $includePaths = require __DIR__ . '/include_paths.php';
 array_push($includePaths, get_include_path());
 set_include_path(join(PATH_SEPARATOR, $includePaths));
 $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) {
   composerRequire73612b48e6c3d0de8d56e03dece61d11($file);
 }
 return $loader;
  }
}
function composerRequire73612b48e6c3d0de8d56e03dece61d11($file)
{
  require $file;
}

First, initialize the ClassLoader class, and then register/load directly with the four loading methods mentioned above. Some core codes of ClassLoader are as follows:


/**
  * @param array $classMap Class to filename map
  */
 public function addClassMap(array $classMap)
 {
  if ($this->classMap) {
   $this->classMap = array_merge($this->classMap, $classMap);
  } else {
   $this->classMap = $classMap;
  }
 }
 /**
  * Registers a set of PSR-0 directories for a given prefix,
  * replacing any others previously set for this prefix.
  *
  * @param string  $prefix The prefix
  * @param array|string $paths The PSR-0 base directories
  */
 public function set($prefix, $paths)
 {
  if (!$prefix) {
   $this->fallbackDirsPsr0 = (array) $paths;
  } else {
   $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
  }
 }
 /**
  * Registers a set of PSR-4 directories for a given namespace,
  * replacing any others previously set for this namespace.
  *
  * @param string  $prefix The prefix/namespace, with trailing '\\'
  * @param array|string $paths The PSR-4 base directories
  *
  * @throws \InvalidArgumentException
  */
 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;
  }
 }
 /**
  * Registers this instance as an autoloader.
  *
  * @param bool $prepend Whether to prepend the autoloader or not
  */
 public function register($prepend = false)
 {
  spl_autoload_register(array($this, 'loadClass'), true, $prepend);
 }
 /**
  * Loads the given class or interface.
  *
  * @param string $class The name of the class
  * @return bool|null True if loaded, null otherwise
  */
 public function loadClass($class)
 {
  if ($file = $this->findFile($class)) {
   includeFile($file);
   return true;
  }
 }
 /**
  * Finds the path to the file where the class is defined.
  *
  * @param string $class The name of the class
  *
  * @return string|false The path if found, false otherwise
  */
 public function findFile($class)
 {
  // This is PHP5.3.0 - 5.3.2 Adj. 1 A bug  See https://bugs.php.net/50731
  if ('\\' == $class[0]) {
   $class = substr($class, 1);
  }
  // class map  Find the mode 
  if (isset($this->classMap[$class])) {
   return $this->classMap[$class];
  }
  //psr-0/4 Find the mode 
  $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;
 }
 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;
  }
 }

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 */
function includeFile($file)
{
 include $file;
}


Related articles: