Composer 使用和自动加载原理

基本用法

概念

Composer 不是一个包管理器。是的,它涉及 "packages" 和 "libraries",但它在每个项目的基础上进行管理,在你项目的某个目录中(例如 vendor)进行安装。默认情况下它不会在全局安装任何东西。因此,这仅仅是一个依赖管理。

Composer 主要解决以下问题

  • 你有一个项目依赖于若干个库。

  • 其中一些库依赖于其他库。

  • 你声明你所依赖的东西。

  • Composer 会找出哪个版本的包需要安装,并安装它们(将它们下载到你的项目中)。

使用

安装Composer

使用Composer 只需要创建一个 composer.json 文件,其中定义了项目的基本信息和依赖

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

定义好依赖关系后,执行 composer install 即可将依赖的库或项目下载到你项目的vendor文件夹下

也可以直接使用 composer require 命令直接安装依赖。

除了 composer.json 文件,composer 还会生成 composer.lock - 锁文件,锁文件记录着详细的依赖信息,用于多人协同工作时依赖库的同步。

命令行

  • composer init 初始化
  • composer install 安装

  • composer update 更新

  • composer require 声明依赖

  • composer global 全局执行

  • composer search 搜索包

  • composer show 展示

  • composer depends 依赖性检测

(库)资源包

composer.json 结构

包名 name

描述 description

版本 version

安装类型 type

关键字 keywords

项目主页 homepage

版本发布时间 time

许可协议 license

作者 author

Package links

Composer 的自动加载

PHP自动加载

在一般的PHP程序开发中,我们通常使用 requireinclude 引入文件。

这在一般小规模的开发中没什么问题,但是开发大型项目需要多人合作时就会带来一些隐性的问题。如果一个PHP文件需要引入大量的其他的文件时,需要写大量的 require 或 include 语句,这样可能会遗漏或引入没用的文件,从而影响性能。如果各个文件之间有更加复杂的引入关系,就会很容易造成混乱。

__autoload 函数

PHP5 为了解决这个问题,引入了 类的自动加载机制 autload机制可以在类使用的时候才引入对应的包含类的文件,而不是一开始就加载进来,这种加载方式也称为 Lazy loading(惰性加载)


function __autoload() {
    require_once ($classname . ".class.php");
}

__autoload 函数存在的问题

如果一个系统中需要引入大量其他的类库,这些类库可能由不同的开发人员编写的,其类名与实际的磁盘文件的映射规则不尽相同,如果这些类库要全部实现自动加载,则需要在 __autoload 函数中实现所有的映射规则。这样 __autoload 函数的实现会非常复杂和臃肿,甚至可能无法实现。

造成这样结果的主要原因是 __autoload() 是全局函数只能定义一次,不够灵活。如何解决这个问题呢?答案是使用 __autoload 调用堆栈 , 不同的映射关系写到不同的 autoload 函数中,然后统一注册管理,所以PHP5中引入了 Spl Autoload

SPL Autoload

SPL 是 Standard PHP Library(标准PHP库)的缩写。

使用 spl_autoload_register 函数 注册自动加载函数


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

spl_autoload_register() 就是我们上面所说的 __autoload 调用堆栈,我们可以向这个函数注册多个我们自己的 autoload() 函数,当PHP找不到类名时,PHP就会调用这个堆栈,然后去调用自定义的 autoload() 函数,实现自动加载功能。如果我们不向这个函数输入任何参数,那么就会默认注册 spl_autoload() 函数。

PSR 规范

PSR 是PHP标准组织推出的一套PHP开发标准,其中有7套PSR规范已通过表决并推出使用,分别是:

PSR-0 自动加载标准 (已废弃,一些旧的第三方库还有在使用)

PSR-1 基础编码标准

PSR-2 编码风格导向

PSR-3 日志接口

PSR-4 自动加载增强版

PSR-6 缓存接口规范

PSR-7 HTTP 消息接口规范

PSR4 标准

一个完整的类名需具有以下结构

/<命名空间>/<字命名空间>/<类名>

Composer 自动加载过程

执行 composer require 时发生了什么

  • composer 会找到符合 PR4 规范的第三方库的源
  • 将其加载到 vendor 目录下
  • 初始化顶级域名的映射并写入到指定的文件里
  • 写好一个 autoload 函数,并且注册到spl_autoload_register() 里

Composer 实现自动加载的文件

  1. autoload_real.php 自动加载功能的引导类

    • composer 加载类的初始化(顶级命名空间与文件路径映射初始化)和注册
  2. ClassLoader.php composer 加载类

    • composer 自动加载功能的核心类
  3. autoload_static.php 顶级命名空间初始化类

    • 用于给核心类初始化顶级域名空间
  4. autoload_classmap.php 自动加载的最简单形式

    • 有完整的命名空间和文件目录的映射
  5. autoload_files.php 用于加载全局函数的文件

    • 存放各个全局函数所在的文件路径名
  6. autoload_namespaces.php 符合PSR0标准的自动加载文件

    • 存放着顶级命名空间与文件的映射
  7. autoload_psr4.php 符合PSR4标准的自动加载文件

    • 存放着顶级命名空间与文件的映射

Composer 源码分析

启动

很多框架都会在初始化的时候通过 composer 实现自动加载,以下代码以 laravel 为例


define('LARAVEL_START', microtime(true));

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

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

return ComposerAutoloaderInit56e0da52b922ff4669d17341a618d14e::getLoader();

引入composer 的自动加载文件,调用 getLoader 函数,Compoer 正式开始

autoload_real 引导类

在 autoload.php 文件中我们可以看出,程序主要调用了引导类的静态方法 getLoader() 来实现自动加载的注册。

接下来我们来详细的看一下这个函数

  1. 单例

    一开始先实现了一个简单的单例模式,自动加载类只能有一个

    
    if (null !== self::$loader) {
        return self::$loader;
    }
    
  1. 构造 ClassLoader 核心类

    loadClassLoader 方法注册自动加载,并实例化一个自动加载实现的核心类 ClassLoader

    
    spl_autoload_register(array('ComposerAutoloaderInit46b6d6d7d0d5071f29f6ebcb25245e20', 'loadClassLoader'), true, true);
    self::$loader = $loader = new \Composer\Autoload\ClassLoader();
    spl_autoload_unregister(array('ComposerAutoloaderInit46b6d6d7d0d5071f29f6ebcb25245e20', 'loadClassLoader'));
    

    loadClassLoader 方法代码:

    
    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }
    
  1. 初始化核心类对象

    
    $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
    if ($useStaticLoader) {
        require_once __DIR__ . '/autoload_static.php';
      call_user_func(\Composer\Autoload\ComposerStaticInit46b6d6d7d0d5071f29f6ebcb25245e20::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);
        }
    }
    

    这个部分主要是对自动加载类的初始化,给自动加载类定义各顶级命名空间的映射。

    加载顶级命令空间映射主要有两种方式

    1. 通过 autoload_static.php 静态初始化
    2. 调用核心类接口初始化

    autoload_static 静态初始化 PHP 版本必须 >= 5.6,而且不支持 HHVM 虚拟机使用。该方法主要通过 getInitializer方法实现初始化,这个方法的实现也非常简单,最终会将自己类中的顶级命名空间的映射给 ClassLoader 自动加载类。

    
    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
             $loader->prefixLengthsPsr4 = ComposerStaticInit46b6d6d7d0d5071f29f6ebcb25245e20::$prefixLengthsPsr4;
             $loader->prefixDirsPsr4 = ComposerStaticInit46b6d6d7d0d5071f29f6ebcb25245e20::$prefixDirsPsr4;
    
        }, null, ClassLoader::class);
    }
    

至此自动加载类就初始化完成了。

运行第三方库

​ 终于到了最核心的部分了,composer 实现自动加载的秘密,如何把获取到的命名空间转换为对应的文件目录。

​ ClassLoder 的 register() 函数将 loadClass() 函数注册到PHP的 SPL 函数堆栈中,每当PHP遇到不认识的类命名空间时就会自动调用堆栈中的每个函数直到类加载成功或函数调用完为止。所以loadClass() 函数就是类自动加载的关键了。

loadClass() 函数

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

    public function findFile($class)
    {
        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }
    
        $file = $this->findFileWithExtension($class, '.php');
    
        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }
    
        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }
    
        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }
    
        return $file;
    }
    

从代码中可以看到 loadClass() 函数主要调用 findFile 方法,findFile() 方法主要找到每个类对应的文件所在的位置。

寻找对应的目录文件主要分为两个部分 classMapfindFileWithExtension 。classMap 实现较为简单,就是 直接看命名空间是否在映射数组中即可。findFileWithExtension 的实现较为复杂,他需要根据 PSR0 和 PSR4 标准找到对应的文件。

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