Composer自动加载机制

来源:互联网 发布:淘宝食品主图 编辑:程序博客网 时间:2024/05/16 17:49

众所周知 composer 是现代 PHP 项目的基石, 与古老的 pear 不同, composer 并不是一款专注于系统级别 php 管理的包管理系统,而是基于项目的一个库管理系统。这就好比 npm install -g 和 npm install 的区别。而且最主要的是 pear 不太能跟上时代的潮流,在大家都在用 psr-* 的时候 pear 依然我行我素自成一体。

好吧,可能这是好事,但是也是坏事。好事是很多优秀的包都从 pear 发家致富,比如 PHP_CodeSnifferPHP_Unit等等。但是随着时代的发展,php社区也渐渐地从其他社区汲取到了一些精华,慢慢地向前发展。最近的 laravel 就是直接扔进了 composer。因为 psr-4 这个规范真是不能再爽更多。这真的是我用各种包用得最顺手的一套命名规范了。

扯远了,扯回 vendor/composer/autoload_real.php 这个核心 composer 文件。

--laravel 学院

总体来说 composer 提供了几种自动加载类型

  1. classmap
  2. psr-0
  3. psr-4
  4. files

这几种自动加载都会用到,理论上来说,项目代码用 psr-4 自动加载, helper 用 files 自动加载,development相关用 classmap 自动加载。 psr-0 已经被抛弃了,不过有些历史遗留依然在用,所以偶尔也会看到。


autoload_real.php

好了看了所有的 autoload 类型那么直接怒看一发实现。

首先映入眼帘的就是一坨,我的是这样的

ComposerAutoloaderInit64c47026c93126586e44d036738c0862

为啥?

因为这个类是全局的啊少年。

作为模块化大行其道的今天,全局的类总是有那么点奇怪。为了不让这个 autoload 的 helper 污染全局,composer 的仁兄们还是绞尽脑汁怒弄了这么一个奇怪的 hash。这直接就逼迫广大二笔程序员们不要跟这个撞衫。



可以看到 psr-4 或者 psr-0 的自动加载都是一件很累人的事儿。基本是个 O(n2) 的复杂度。另外有一大堆 is_file 之类的 IO 操作所以性能堪忧。

所以给出的解决方案就是空间换时间。

Compsoer\ClassLoader 会优先查看 autoload_classmap 中所有生成的注册类。如果在classmap 中没有发现再 fallback 到 psr-4 然后 psr-0

所以当打了 composer dump-autoload -o 之后,composer 就会提前加载需要的类并提前返回。这样大大减少了 IO 和深层次的 loop。

下面解释一下各类中包含的意思:

1、autoload.PHP 
要使用Composer的自动加载,首先需要引入该文件

<?php// autoload.php @generated by Composer// 引入autoload_real.phprequire_once __DIR__ . '/composer' . '/autoload_real.php';// 下面一大长串是在我们安装composer时由Composer自动生成的,我们需要关注的是它调用的autoload_real.php里的静态方法 getLoader()return ComposerAutoloaderInit85b4dbf6b714d62ec745fcf3dd1a5041::getLoader();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2、autoload_real.php

<?php// autoload_real.php @generated by Composerclass ComposerAutoloaderInit85b4dbf6b714d62ec745fcf3dd1a5041{    private static $loader;    // 在实例化ClassLoader时调用该函数    public static function loadClassLoader($class)    {       // 该函数在getLoader方法里被注册为__autoload的实现,在实例化类时,如果类不存在,会自动调用该方法    // 如果实例化的函数是Composer\Autoload\ClassLoader 则引入该类        if ('Composer\Autoload\ClassLoader' === $class) {            require __DIR__ . '/ClassLoader.php';        }    }    public static function getLoader()    {        if (null !== self::$loader) {            return self::$loader;        }// 注册 loadClassLoader函数作为 __autoload 的实现   spl_autoload_register(array('ComposerAutoloaderInit85b4dbf6b714d62ec745fcf3dd1a5041', 'loadClassLoader'), true, true);        // 实例化该方法时会自动调用上述方法注册的函数,        // loadClassLoader函数里引入 ClassLoader类    self::$loader = $loader = new \Composer\Autoload\ClassLoader();        spl_autoload_unregister(array('ComposerAutoloaderInit85b4dbf6b714d62ec745fcf3dd1a5041', 'loadClassLoader'));        // PSR-0 的规则        $map = require __DIR__ . '/autoload_namespaces.php';        foreach ($map as $namespace => $path) {            $loader->set($namespace, $path);        }    // PSR-4 的规则        $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);        }    // 注册给定的函数作为 __autoload 的实现,具体参考下文        // Class-Map部分        $loader->register(true);    // Files方式 直接加载需要访问的文件        $includeFiles = require __DIR__ . '/autoload_files.php';        foreach ($includeFiles as $fileIdentifier => $file) {            composerRequire85b4dbf6b714d62ec745fcf3dd1a5041($fileIdentifier, $file);        }        return $loader;    }}function composerRequire85b4dbf6b714d62ec745fcf3dd1a5041($fileIdentifier, $file){    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {        // 加载需要调用的文件        require $file;        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

3、ClassLoader.php (部分核心代码)

① PSR-0 规则

     /**     * 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     */    // $prefix、$paths分别相当于autoload_namespaces.php里返回数组的key和value值    public function set($prefix, $paths)    {           if (!$prefix) {            $this->fallbackDirsPsr0 = (array) $paths;        } else {        // $prefix[0]取得是首个字符            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

② PSR-4规则

    /**     * 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);            // PSR-4 命名空间以 \ 结尾,如果不是,则不符合规则            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;        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

③ Class-map方式

    /**     * @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 this instance as an autoloader.     *     * @param bool $prepend Whether to prepend the autoloader or not     */    public function register($prepend = false)    {   // $prepend为true时,spl_autoload_register() 会添加函数到队列之首,而不是队列尾部。 (具体请参考php文档)         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;        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

什么时候才会去调用上述注册的loadClass函数呢?举例来说

<?php require './vendor/autoload.php';// 当我们实例化TestController的时候,就会自动调用spl_autoload_register注册的函数loadClass,在loadClass中又会去调用findFile方法去查找类文件所在的位置,然后require引入$test = new TestController;echo $test->show();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

至此Composer自动加载流程已讲述完毕。具体的代码还需各位自己去研究。不得不惊叹,有了Composer真是太方便了!

0 1