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 >
<?php
return array(
  ...
  'f0906e6318348a765ffb6eb24e0d0938' => $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

<?php
namespace App;
class Test{  
}

执行 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;
    }
}
不够直观?
第二、三步原理简化版

<?php

class ClassLoader {

    $map = array(
        // '\\App\\User' => '/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();

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容