Composer 自动加载原理

程序地址
index.php
require __DIR__.'/../vendor/autoload.php';

跟踪:index.php > /vendor/autoload.php
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit148a9da910429c7c016377bec97d275e::getLoader();

到达 composer 工作区


O3dzaRYtmb.png!large.png

什么是自动加载?

$userModel = new \App\User();
或
use App\User;
$userModel = new User();
composer 自动加载的流程

实现自动加载的过程可简单分为三步:

  • 1.类文件归纳
  • 2.类文件提取
  • 3.类文件查找

一.类文件归纳 驱动:composer

根据加载标准,一共有四种加载标准(files,classmap,psr-4,psr-0),将映射关系分门别类写入不同的文件,以数组保存,比如 psr-4 标准的加载形式,写入 autoload_psr4.php
当在控制台执行 composer require | update 引入一个组件时包,读取组建包的 composer.json中的 "autoload"配置,分别按照每个组建包配置的自动加载规则,扫描组件中的文件,将其写入不同加载标准的 php 文件数组中。

下面演示 laravel 四种声明加载标准的组件,是如何归纳的。

"autoload": {
        "files": [
            "src/Illuminate/Foundation/helpers.php"
        ],
        "classmap": [
            "hamcrest"
        ]
        "psr-4": {
            "App\\": "app/"
        },
        "psr-0": {
            "Mockery": "library/"
        }
    },
1. files 写入 autoload_files.php

文件预加载,框架启动时便被 include ,通常文件中提供一些函数方法方便我们使用,如经常用的 dd()
"files:["src/Illuminate/Foundation/helpers.php"]"写入/composer/autoload_files.php

autoload_files.php >
 $vendorDir . '/laravel/framework/src/Illuminate/Foundation/helpers.php',
);
2.classmap 写入 autoload_classmap.php

直接映射文件真实路径。 这是简单粗暴的,因此这种方式效率是最高的。
"classmap": ["hamcrest"], 写入 /composer/autoload_classmap.php

autoload_classmap.php >
return array(
    'Hamcrest\\Arrays\\IsArray' => $vendorDir . '/hamcrest/hamcrest-php/hamcrest/Hamcrest/Arrays/IsArray.php',
    'Hamcrest\\Arrays\\IsArrayContaining' => $vendorDir . '/hamcrest/hamcrest-php/hamcrest/Hamcrest/Arrays/IsArrayContaining.php',

    ...// hamcrest 文件夹下所有文件
);

use Hamcrest\\Arrays\\IsArray; 时,可直接在 classmap 的数组中找到它,是不是很粗暴呢。

3. psr-4 写入 autoload_psr4.php

这是最常用的加载标准。"psr-4": {"App\\": "app/"}写入 autoload_psr4.php

/composer/autoload_psr4.php >
return array(
    ...
    'App\\' => array($baseDir . '/app'),// App 称作 prefix
);

只要是 app/ 目录下的文件,且名命空间符合 psr-4 标准的类文件都能被自动加载。如 use App\Test => app/Test.php。而 classmap 方式不可以,这样你可以在 app/ 下自由的增加 / 删除类文件了。

这里提一下,前面说 classmap 方式是最高效的,而 composer dump-autoload 可以将通过 psr-4 规范加载的文件 “落盘”,即写入 autoload_classmap.php 。从而起到加速的作用,官方建议生产环境时执行 composer dump-autoload --optimize来优化项目的自动加载速度。
为了说明 composer dump-autoload的作用,我在 app 目录下建立 Test.php

执行 composer dump-autoload后,发现在 autoload_classmap.phpautoload_static.php(稍后再提及)中找到了它。

/composer/autoload_classmap.php && autoload_static.php >
return array(
    ...
    'App\\Test' => __DIR__ . '/../..' . '/app/Test.php',
    'App\\User' => $baseDir . '/app/User.php',
)

composer dump-autoload --optimize 的作用是进行优化(optimize),清理无效索引空间另外在 /composer生成了 user缓存文件。

4.psr-0 写入 autoload_namespaces.php

和 psr-4 类似,只是加载规则有所不同。官方已弃用,但 laravel 有的组建包还是在用的,composer 仍然支持向下兼容。"psr-0": {"Mockery": "library/"}写入 autoload_namespaces.php

/composer/autoload_namespaces.php >
return array(
    ...
    'Mockery' => array($vendorDir . '/mockery/mockery/library'),
);

注:autoload_static.php 它包含了四个文件的全部映射,是为了精简篇幅这里不做介绍了

二、类文件提取 驱动:/composer/autoload_real.php

从四个文件中 或 autoload_static.php 中(因它包含了四个文件的全部映射)将全部映射提取到实现自动加载的类(ClassLoader)中,由 ClassLoader 查找类的映射,实现自动加载,

index.php > /vendor/autoload.php > /composer/autoload_real.php >
重要概念:当前类的任务是把所有映射注入到ClassLoader中,由 ClassLoader 类实现自动加载。为了更好的说明,以下为简化版代码。
class ComposerAutoloaderInitccb56ced66f82d50b9e1d3fd28a6ab26{
    getLoader(){
        ----STEP1 实例化 ClassLoader-------
        require __DIR__ . '/ClassLoader.php';
        $loader = new ClassLoader();

        ----STEP2 提取映射并注入 ClassLoader-------
        if ($useStaticLoader) {
            //若符合运行环境,优先从 autoload_static.php 提取映射(见前面 autoload_static.php 的介绍)
            require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitccb56ced66f82d50b9e1d3fd28a6ab26::getInitializer($loader));

        } else {
            // 否则就从分别从三个文件中提取出来并注入 ClassLoader
            //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
            $classMap = require __DIR__ . '/autoload_classmap.php';
            if ($classMap) {
                $loader->addClassMap($classMap);
            }
        }

        ----STEP3  STEP2完毕,启动 ClassLoader 的自动加载方法-------  
        $loader->register(true); //spl_autoload_register

        ----STEP4  处理预加载文件-------  
        //处理 autoload_files.php 中的预加载文件,由于这些文件需要立即加载,它和类的自动加载是不同的,只要加载后,文件中的函数就能用。
        //同理,能从 autoload_static.php 提取,优先从这个文件提取。
        if ($useStaticLoader) {
            $includeFiles = Composer\Autoload\ComposerStaticInitccb56ced66f82d50b9e1d3fd28a6ab26::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            require $file;
        }

        //最后一句
        return $loader;
    };
}

三、 实现自动加载 驱动:/composer/ClassLoader.php

ClassLoader.php >
class ClassLoader{
    /**
     * 将此实例注册为自动加载程序
     * -------------------------------------------
     * new \App\User();时将 '\App\User'类命传递给 loadClass('App/User')方法
     * 第二参数true debug
     * $prepend为true,将 loadClass() 放在自动加载函数栈的第一个,即优先处理。
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

    /**
    * 加载给定的类或接口
    */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            includeFile($file);
            return true;
        }
    }

    /**
    * 查找定义类的文件的路径
    * (二)类文件提取 已将所有映射注入到此类中的数组属性中
    * 从数组中根据 Key 值取出
    * 但并不是那么简单,本人没有太过研究
    */
    public function findFile($class){
        //省略代码
        return $file;
    }
}
不够直观?
第二、三步原理简化版

 '/www/wwwroot/laravel/app/User.php',
        // '\\App\\Test' => '/www/wwwroot/laravel/app/Test.php',
    );

    public function set($namespace, $path){
        $map[$namespace] = $path;
    }

    public function register() {
        spl_autoload_register(array($this, 'loadClass'));
    }

    public function loadClass($class) {

        if ($file = $this->findFile($class)) {
            include $file;
            return true;
        }

    }

    public function findFile($class) {
        return isset($map[$class]) ? $map[$class] : false;
    }

}

$loader = new ClassLoader();

$loader->register();

$map = [
    '\\App\\User' => '/www/wwwroot/laravel/app/User.php',
    '\\App\\Test' => '/www/wwwroot/laravel/app/Test.php',
];

foreach ($map as $namespace => $path) {
    $loader->set($namespace, $path);
}

$user = new \App\User();

你可能感兴趣的:(Composer 自动加载原理)