WWW.lllT.neT下边由composer实例教程频道给大伙儿深入浅出分析 composer 的全自动载入基本原理,期待对必须的小伙伴有些协助!

导言

PHP 自5.3的版本号以后,早已重焕再生,类名、特性(trait)、闭包、插口、PSR 标准、及其 composer 的发生早已让 PHP 变成了一门智能化的开发语言。PHP 的生态体系也一直在演变,而 composer 的存在也是完全的转变了过去搭建 PHP 运用的方法,我们可以依据 PHP 的运用要求混和配搭最好的 PHP 部件。自然这也归功于 PSR 标准的明确提出。


考试大纲

  • PHP 全自动载入作用
  • PSR 标准
  • comoposer 的全自动载入全过程
  • composer 源代码剖析

一、PHP 全自动载入作用

PHP 全自动载入作用的来历

在 PHP 开发设计流程中,假如想要从外界引进一个 Class ,通常会应用 includerequire 方式,去把界定这一 Class 的文件包含进去。这一在小规模纳税人开发设计的情况下,没有什么问题。但在大中型的开发设计新项目中,应用这类方法会产生一些暗含的问题:假如一个 PHP 文档必须应用许多其他类,那麼就要许多的 require/include 句子,那样有可能会 导致忽略 或是 包括进多余的类文档。假如很多的材料都要应用其他的类,那麼要确保每一个文档都包括恰当的类文档肯定是一个恶梦, 更何况 require或 incloud 的能力成本非常大。

PHP5 为这个问题给予了一个解决方法,这就是 类的全自动载入(autoload)体制autoload体制 可以促使 PHP 程序流程有可能在应用类时才全自动包括类文档,而不是一开始就将全部的类文档include进去,这类策略也称之为 Lazy loading (可塑性载入)

  • 汇总起來,全自动载入作用产生了几个优势:

    1. 应用类以前不用 include / require
    2. 应用类的过程中才会 include / require 文档,完成了 lazy loading ,防止了 include / require 不必要文档。
    3. 不用考虑到引进 类的具体硬盘详细地址 ,完成了逻辑性和实体线文档的分离出来。

PHP 全自动载入涵数 ._autoload()

  • 从 PHP5 逐渐,在我们在应用一个类时,假如发觉这一类沒有载入,便会自启动 ._autoload() 涵数,这一涵数是我们在程序流程中自定的,在这个涵数中我们可以载入必须采用的类。下边是个简洁的实例:

    <?php
    
    function ._autoload($classname) {
            require_once ($classname . ".class.php");
    }
  • 在咱们这一简易的事例中,大家立即将类名再加上后缀名 .class.php 组成了类文件夹名称,随后应用 require_once 将其载入。

    从这一事例中,我们可以看得出 ._autoload 最少要做三件事儿:

    1. 依据类名明确类文件夹名称;
    2. 明确类文档所属的硬盘途径;
    3. 将类从硬盘文档中载入到操作系统中。
  • 第三步非常简单,只要应用 include / require 就可以。要完成第一步,第二步的作用,务必在开发设计时承诺类名与硬盘文档的投射方式,仅有那样大家能够依据类名寻找它相应的硬盘文档。
  • 当有大批量的类材料要包括的情况下,大家只需明确相对应的标准,随后在 ._autoload() 涵数中,将类名与具体的硬盘文档相匹配起來,就可以完成 lazy loading 的实际效果
  • 假如想详尽的掌握有关 autoload 全自动载入的全过程,可以查询指南材料:PHP autoload涵数表明

._autoload() 涵数存在的不足

  • 假如在一个系统软件的完成中,假如要应用许多其他的类库,这种类库可能是由不一样的开发者撰写的, 其类名与具体的硬盘文档的投射标准各有不同。这时假如要完成类元件库的全自动载入,就务必 在 ._autoload() 涵数里将全部的投射标准所有完成,那样的话 ._autoload() 涵数有可能会比较复杂,乃至没法完成。最终也许会造成 ._autoload() 涵数十分松垮,这时就算可以完成,也会给未来的维护保养和整体高效率产生较大的不良影响。
  • 那麼问题发生在哪儿呢?问题发生在 ._autoload() 是全局性涵数只有界定一次 ,不足灵便,因此全部的类名与文件夹名称相匹配的逻辑关系标准都需要在一个涵数里边完成,导致这一涵数的松垮。那麼怎样来处理这个问题呢?参考答案便是应用一个 ._autoload调用局部变量 ,不一样的投射关联写到不一样的 ._autoload涵数 中去,随后统一申请注册统一管理方法,这一便是 PHP5 引进的 SPL Autoload

SPL Autoload

  • SPL是 Standard PHP Library(规范PHP库)的简称。它是 PHP5 引进的一个拓展标准库,包含 spl autoload 有关的涵数及其各种各样算法设计和迭代器的端口或类。spl autoload 有关的涵数实际由此可见 php中spl_autoload
<?php

// ._autoload 涵数
//
// function ._autoload($class) {
//     include 'classes/' . $class . '.class.php';
// }


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

spl_autoload_register('my_autoloader');


// 界定的 autoload 涵数在 class 里

// 静态方法
class MyClass {
  public static function autoload($className) {
    // ...
  }
}

spl_autoload_register(array('MyClass', 'autoload'));

// 非静态方法
class MyClass {
  public function autoload($className) {
    // ...
  }
}

$instance = new MyClass();
spl_autoload_register(array($instance, 'autoload'));

spl_autoload_register() 便是大家上边所讲的._autoload调用局部变量,我们可以向这一涵数申请注册好几个我们自己的 autoload() 涵数,当 PHP 找不着类名时,PHP便会读取这一局部变量,随后去读取自定的 autoload() 涵数,完成全自动载入作用。如果我们不向这一涵数键入一切主要参数,那麼便会默认设置申请注册 spl_autoload() 涵数。


二、PSR 标准

与全自动载入有关的标准是 PSR4,在说 PSR4 以前先介绍一下 PSR 规范。PSR 规范的创造发明和发布机构是:PHP-FIG,它的平台网站是:www.php-fig.org。由几个开源框架的开发人员创立于 2009 年,从那逐渐也选择了许多别的组员进去,尽管并不是 “官方网” 机构,但也象征了小区中很大的一块。机构的意义取决于:以最少水平的限定,来统一每个新项目的编号标准,防止每家自主发展趋势的设计风格阻拦了程序猿开发设计的困惑,因此大家创造发明和汇总了 PSR,PSR 是 PHP Standards Recommendation 的简称,截至目前为止,一共有 14 套 PSR 标准,在其中有 7 套PSR标准已根据决议并发布应用,分别是:

PSR-0 全自动载入规范(已废旧,一些旧的第三方库也有在应用)

PSR-1 基本编码方式

PSR-2 编号设计风格指导

PSR-3 日志插口

PSR-4 全自动载入的增强版,更换掉了 PSR-0

PSR-6 缓存文件接口规范

PSR-7 HTTP 信息接口规范

实际详尽的规范标准可以查询PHP 技术标准

PSR4 规范

2013 年末,PHP-FIG 发布了第 5 个标准——PSR-4。

PSR-4 标准了怎样特定文件路径进而全自动载入类界定,与此同时标准了全自动载入文档的部位。

1)一个完全的类名需具备下列构造:

<类名><子类名><类名>

  • 详细的类名务必要有一个顶尖类名,被称作 "vendor namespace";
  • 详细的类名可以有一个或个子类名;
  • 详细的类名务必有一个不可能的类名;
  • 详细的类名中随意一部分中的下降线全是沒有特殊含义的;
  • 详细的类名可以由随意英文大小写构成;
  • 全部类名城务必是大小写字母敏感性的。

2)依据系统的类名加载相对应的文档

  • 详细的类名中,除掉最前头的类名分节符,前边持续的一个或好几个类名跟子类名,做为「类名作为前缀」,其务必与最少一个「文档基文件目录」相对性应;
  • 紧接着类名作为前缀后的子类名 务必 与相对应的「文档基文件目录」相符合,在其中的类名分节符将做为文件目录分节符。
  • 结尾的类名务必与相匹配的以 .php 为后缀名的文档同名的。
  • 全自动加载器(autoloader)的完成一定不能抛出异常、一定不能开启任一级其他错误报告及其不应该有传参。

3) 事例

PSR-4设计风格

类名:ZendAbc
类名作为前缀:Zend
文档基文件目录:/usr/includes/Zend/
文件路径:/usr/includes/Zend/Abc.php
类名:SymfonyCoreRequest
类名作为前缀:SymfonyCore
文档基文件目录:./vendor/Symfony/Core/
文件路径:./vendor/Symfony/Core/Request.php

文件目录构造

-vendor/
| -vendor_name/
| | -package_name/
| | | -src/
| | | | -ClassName.php       # Vendor_NamePackage_NameClassName
| | | -tests/
| | | | -ClassNameTest.php   # Vendor_NamePackage_NameClassNameTest

Composer全自动载入全过程

Composer 干了什么事儿

  • 你有一个新项目取决于多个库。
  • 在其中一些库取决于别的库。
  • 你申明你所依靠的物品。
  • Composer 会找到哪一个版本号的包必须组装,并组装他们(将他们在线下载到你的新项目中)。

比如,你已经建立一个新项目,必须做一些单元测试卷。你决策应用 phpunit 。为了更好地将它加上到你的新项目中,你所必须做的也是在 composer.json 文档里叙述新项目的相互依赖。

 {
   "require": {
     "phpunit/phpunit":"~6.0",
   }
 }

随后在 composer require 以后大家如果在新项目里边立即 use phpunit 的类就可以应用。

实行 composer require 时有什么故事

  • composer 会寻找合乎 PR4 标准的第三方库的源
  • 将其载入到 vendor 文件目录下
  • 复位顶级域名的投射并载入到规定的文档里

(如:'PHPUnit\Framework\Assert' => ._DIR._ . '/..' . '/phpunit/phpunit/src/Framework/Assert.php'

  • 写好一个 autoload 涵数,而且申请注册到 spl_autoload_register()里

题外话:如今许多架构都早已帮大家写好啦顶尖域名映射了,大家只要在架构里边新建文件,在新创建的资料中写好类名,就可以在任何地方 use 大家的类名了。


Composer 源代码剖析

下边大家根据对源代码的剖析一起来看看 composer 是怎样完成 PSR4规范 的全自动载入作用。

许多架构在复位的过程中会引进 composer 来帮助全自动载入的,以 Laravel 为例子,它通道文档 index.php 第一句便是运用 composer 来达到全自动载入作用。

运行

<?php
  define('LARAVEL_START', microtime(true));

  require ._DIR._ . '/../vendor/autoload.php';

去 vendor 文件目录下的 autoload.php

<?php
  require_once ._DIR._ . '/composer' . '/autoload_real.php';

  return ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29::getLoader();

这儿便是 Composer 真真正正逐渐的地儿了

Composer全自动载入文档

最先,大家先大概了解一下Composer全自动载入常用到的源代码。

  1. autoload_real.php: 全自动载入作用的帮助类。

    • composer 载入类的复位(顶尖类名与文件路径投射复位)和申请注册(spl_autoload_register())。
  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 规范的全自动载入文档,

    • 储放着顶尖类名与文档的投射;

autoload_real 正确引导类


在 vendor 文件目录下的 autoload.php 文档中我们可以看得出,程序流程关键读取了正确引导类的静态方法 getLoader() ,大家继续看一下这一涵数。

<?php
    public static function getLoader()
    {
      if (null !== self::$loader) {
          return self::$loader;
      }

      spl_autoload_register(
        array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'), true, true
      );

      self::$loader = $loader = new ComposerAutoloadClassLoader();

      spl_autoload_unregister(
        array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader')
      );

      $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');

      if ($useStaticLoader) {
          require_once ._DIR._ . '/autoload_static.php';

          call_user_func(
          ComposerAutoloadComposerStaticInit7b790917ce8899df9af8ed53631a1c29::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);
          }
      }

      /***********************申请注册全自动载入关键类目标********************/     $loader->register(true);

      /***********************全自动载入全局性涵数********************/
      if ($useStaticLoader) {
          $includeFiles = ComposerAutoloadComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files;
      } else {
          $includeFiles = require ._DIR._ . '/autoload_files.php';
      }

      foreach ($includeFiles as $fileIdentifier => $file) {
          composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);
      }

      return $loader;
    }

我将全自动载入正确引导类分成 5 个一部分。

第一部分——单例模式

第一部分非常简单,便是个最传统的单例设计模式,全自动载入类只有有一个。

<?php
  if (null !== self::$loader) {
      return self::$loader;
  }

第二一部分——结构ClassLoader关键类

第二一部分 new 一个全自动载入的关键类目标。

<?php
  /***********************得到全自动载入关键类目标********************/
  spl_autoload_register(
    array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'), true, true
  );

  self::$loader = $loader = new ComposerAutoloadClassLoader();

  spl_autoload_unregister(
    array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader')
  );

loadClassLoader()涵数:

<?php
public static function loadClassLoader($class)
{
    if ('ComposerAutoloadClassLoader' === $class) {
        require ._DIR._ . '/ClassLoader.php';
    }
}

从程序流程里边我们可以看得出,composer 先向 PHP 全自动载入体制申请注册了一个涵数,这一涵数 require 了 ClassLoader 文档。取得成功 new 出该文件中关键类 ClassLoader() 后,又消毁了该涵数。

第三一部分 —— 复位关键类目标

<?php
  /***********************复位全自动载入关键类目标********************/
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
  if ($useStaticLoader) {
     require_once ._DIR._ . '/autoload_static.php';

     call_user_func(
       ComposerAutoloadComposerStaticInit7b790917ce8899df9af8ed53631a1c29::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 开展静态数据复位;
  2. 读取关键类插口复位。

autoload_static 静态数据复位 ( PHP >= 5.6 )

静态数据复位只适用 PHP5.6 以上版本号而且不兼容 HHVM vm虚拟机。大家深层次 autoload_static.php 这一文档发觉这一文档确定了一个用以静态数据复位的类,名称叫 ComposerStaticInit7b790917ce8899df9af8ed53631a1c29,依然为了防止矛盾而加了 hash 值。这一类非常简单:

<?php
  class ComposerStaticInit7b790917ce8899df9af8ed53631a1c29{
     public static $files = array(...);
     public static $prefixLengthsPsr4 = array(...);
     public static $prefixDirsPsr4 = array(...);
     public static $prefixesPsr0 = array(...);
     public static $classMap = array (...);

    public static function getInitializer(ClassLoader $loader)
    {
      return Closure::bind(function () use ($loader) {
          $loader->prefixLengthsPsr4
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixLengthsPsr4;

          $loader->prefixDirsPsr4
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixDirsPsr4;

          $loader->prefixesPsr0
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixesPsr0;

          $loader->classMap
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$classMap;

      }, null, ClassLoader::class);
  }

这一静态数据复位类的关键便是 getInitializer() 涵数,它将自身类中的顶尖类名投射给了 ClassLoader 类。特别注意的是这一涵数回到的是一个匿名函数,为什么呢?缘故便是 ClassLoader类 中的 prefixLengthsPsr4prefixDirsPsr4这些自变量全是 private的。运用匿名函数的关联作用就可以将这种 private 自变量赋给 ClassLoader 类 里的成员变量。

有关匿名函数的关联作用。

下面便是类名复位的重要了。

classMap(类名投射)

<?php
  public static $classMap = array (
      'App\Console\Kernel'
              => ._DIR._ . '/../..' . '/app/Console/Kernel.php',

      'App\Exceptions\Handler'
              => ._DIR._ . '/../..' . '/app/Exceptions/Handler.php',

      'App\Http\Controllers\Auth\ForgotPasswordController'
              => ._DIR._ . '/../..' . '/app/Http/Controllers/Auth/ForgotPasswordController.php',

      'App\Http\Controllers\Auth\LoginController'
              => ._DIR._ . '/../..' . '/app/Http/Controllers/Auth/LoginController.php',

      'App\Http\Controllers\Auth\RegisterController'
              => ._DIR._ . '/../..' . '/app/Http/Controllers/Auth/RegisterController.php',
  ...)

立即类名全称与文件目录的投射,简单直接,也造成这一二维数组非常的大。

PSR4 规范顶尖类名投射二维数组:

<?php
  public static $prefixLengthsPsr4 = array(
      'p' => array (
        'phpDocumentor\Reflection\' => 25,
    ),
      'S' => array (
        'Symfony\Polyfill\Mbstring\' => 26,
        'Symfony\Component\Yaml\' => 23,
        'Symfony\Component\VarDumper\' => 28,
        ...
    ),
  ...);

  public static $prefixDirsPsr4 = array (
      'phpDocumentor\Reflection\' => array (
        0 => ._DIR._ . '/..' . '/phpdocumentor/reflection-common/src',
        1 => ._DIR._ . '/..' . '/phpdocumentor/type-resolver/src',
        2 => ._DIR._ . '/..' . '/phpdocumentor/reflection-docblock/src',
    ),
       'Symfony\Polyfill\Mbstring\' => array (
        0 => ._DIR._ . '/..' . '/symfony/polyfill-mbstring',
    ),
      'Symfony\Component\Yaml\' => array (
        0 => ._DIR._ . '/..' . '/symfony/yaml',
    ),
  ...)

PSR4 规范顶尖类名投射用了2个二维数组,第一个是用类名第一个英文字母做为作为前缀数据库索引,随后是 顶尖类名,可是最后并并不是文件路径,反而是 顶尖类名的长短。为什么呢?

由于 PSR4 规范是用顶尖类名文件目录更换顶尖类名,因此得到顶尖类名的长短很重要。

实际表明这种二维数组的功效:

倘若大家找 SymfonyPolyfillMbstringexample 这一类名,根据作为前缀数据库索引和字符串匹配大家取得了

<?php
    'Symfony\Polyfill\Mbstring\' => 26,

这条纪录,键是顶尖类名,值是类名的长短。取得顶尖类名后去 $prefixDirsPsr4二维数组 获得它的投射文件目录二维数组:(留意投射文件目录很有可能不仅一条)

<?php
  'Symfony\Polyfill\Mbstring\' => array (
              0 => ._DIR._ . '/..' . '/symfony/polyfill-mbstring',
          )

随后大家就可以将类名 Symfony\Polyfill\Mbstring\example 前26字符换成文件目录 ._DIR._ . '/..' . '/symfony/polyfill-mbstring ,大家就获得了._DIR._ . '/..' . '/symfony/polyfill-mbstring/example.php,先认证硬盘上这一文档是不是存有,假如未找到然后解析xml。假如解析xml后沒有寻找,则加载失败。

ClassLoader 插口复位( PHP < 5.6 )


假如PHP版本号小于 5.6 或是应用 HHVM vm虚拟机自然环境,那麼就需要应用关键类的端口开展复位。

<?php
    // PSR0 规范
    $map = require ._DIR._ . '/autoload_namespaces.php';
    foreach ($map as $namespace => $path) {
       $loader->set($namespace, $path);
    }

    // PSR4 规范
    $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);
    }

PSR4 规范的投射

autoload_psr4.php 的顶尖类名投射

<?php
    return array(
    'XdgBaseDir\'
        => array($vendorDir . '/dnoegel/php-xdg-base-dir/src'),

    'Webmozart\Assert\'
        => array($vendorDir . '/webmozart/assert/src'),

    'TijsVerkoyen\CssToInlineStyles\'
        => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'),

    'Tests\'
        => array($baseDir . '/tests'),

    'Symfony\Polyfill\Mbstring\'
        => array($vendorDir . '/symfony/polyfill-mbstring'),
    ...
    )

PSR4 规范的复位插口:

<?php
    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;
        }
    }

汇总下上边的顶尖类名投射全过程:

( 作为前缀 -> 顶尖类名,顶尖类名 -> 顶尖类名长短 )
( 顶尖类名 -> 文件目录 )

这两个投射二维数组。实际方式还可以查询下边的 autoload_static 的 $prefixLengthsPsr4 、 $prefixDirsPsr4 。

类名投射

autoload_classmap:

<?php
public static $classMap = array (
    'App\Console\Kernel'
        => ._DIR._ . '/../..' . '/app/Console/Kernel.php',

    'App\Exceptions\Handler'
        => ._DIR._ . '/../..' . '/app/Exceptions/Handler.php',
    ...
)

addClassMap:

<?php
    public function addClassMap(array $classMap)
    {    if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

全自动载入关键类 ClassLoader 的静态数据复位到这儿就完成了!

实际上说成5一部分,真真正正关键的就两一部分——复位与申请注册。复位承担高层类名的文件目录投射,申请注册承担完成高层下列的类名投射标准。

第四一部分 —— 申请注册


说完了 Composer 全自动载入作用的运行与复位,通过运行与复位,全自动载入关键类目标早已取得了顶尖类名与相对应文件目录的投射,换句话说,如果有类名 'AppConsoleKernel,大家早已可以寻找它相应的类文档地理位置。那麼,它是什么时候被开启去找的呢?

这就是 composer 全自动载入的关键了,大家先回望一下全自动载入正确引导类:

 public static function getLoader()
 {
    /***************************传统单例设计模式********************/
    if (null !== self::$loader) {
        return self::$loader;
    }
    /***********************得到全自动载入关键类目标********************/
    spl_autoload_register(array('ComposerAutoloaderInit
    7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'), true, true);
    self::$loader = $loader = new ComposerAutoloadClassLoader();
    spl_autoload_unregister(array('ComposerAutoloaderInit
    7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'));

    /***********************复位全自动载入关键类目标********************/
    $useStaticLoader = PHP_VERSION_ID >= 50600 && 
    !defined('HHVM_VERSION');
    if ($useStaticLoader) {
        require_once ._DIR._ . '/autoload_static.php';

        call_user_func(ComposerAutoloadComposerStaticInit
        7b790917ce8899df9af8ed53631a1c29::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);
        }
    }

    /***********************申请注册全自动载入关键类目标********************/
    $loader->register(true);

    /***********************全自动载入全局性涵数********************/
    if ($useStaticLoader) {
        $includeFiles = ComposerAutoloadComposerStaticInit
        7b790917ce8899df9af8ed53631a1c29::$files;
    } else {
        $includeFiles = require ._DIR._ . '/autoload_files.php';
    }
    foreach ($includeFiles as $fileIdentifier => $file) {
        composerRequire
        7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);
    }

    return $loader;
}

如今公司逐渐正确引导类的第四一部分:申请注册全自动载入关键类目标。大家一起来看看关键类的 register() 涵数:

public function register($prepend = false)
{
    spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}

实际上秘密都是在全自动载入关键类 ClassLoader 的 loadClass() 涵数上:

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

            return true;
        }
    }

这一涵数承担依照 PSR 规范将高层类名下列的具体内容变为相匹配的文件目录,也就是上边所讲的将 'AppConsoleKernel 中' ConsoleKernel 这一段变为文件目录,对于如何转的在下面 “运作”的一部分讲。关键类 ClassLoader 将 loadClass() 涵数申请注册到PHP SPL中的 spl_autoload_register() 里边去。那样,每每PHP碰到一个不认识的类名的情况下,PHP会全自动读取申请注册到 spl_autoload_register 里边的 loadClass() 涵数,随后寻找类名相匹配的文档。

全局性涵数的全自动载入

Composer 不仅可以全自动载入类名,还能够载入全局性涵数。如何完成的呢?把全局性涵数写到特殊的文档里边去,在程序执行前逐个 require就可以了。这一便是 composer 全自动载入的第五步,载入全局性涵数。

if ($useStaticLoader) {
    $includeFiles = ComposerAutoloadComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files;
} else {
    $includeFiles = require ._DIR._ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
    composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);
}

跟关键类的复位一样,全局性涵数全自动载入也分成二种:静态数据复位和一般复位,静态数据载入只适用PHP5.6以上而且不兼容HHVM。

静态数据复位:

ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files:

public static $files = array (
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => ._DIR._ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => ._DIR._ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
...
);

一般复位

autoload_files:

$vendorDir = dirname(dirname(._FILE._));
$baseDir = dirname($vendorDir);
return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
   ....
);

实际上跟静态数据复位差别并不大。

载入全局性涵数

class ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29{
  public static function getLoader(){
      ...
      foreach ($includeFiles as $fileIdentifier => $file) {
        composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);
      }
      ...
  }
}

function composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file)
 {
    if (empty($GLOBALS['._composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['._composer_autoload_files'][$fileIdentifier] = true;
    }
}

第五一部分 —— 运作

到这儿,总算赶到了关键的关键—— composer 全自动载入的实情,类名怎样根据 composer 变为相匹配文件目录文档的秘密就在这里一章。
前边说过,ClassLoader 的 register() 涵数将 loadClass() 涵数申请注册到 PHP 的 SPL 涵数局部变量中,每每 PHP 碰到不认识的类名时便会调用函数局部变量的每一个涵数,直到载入类名取得成功。因此 loadClass() 涵数便是全自动载入的重要了。

看下 loadClass() 涵数:

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

        return true;
    }
}

public function findFile($class)
{
    // work around for PHP 5.3.0 - 5.3.2 http://www.lllt.net/img/20211203/00ynhlppsgy.php
    if ('\' == $class[0]) {
        $class = substr($class, 1);
    }

    // class map lookup
    if (isset($this->classMap[$class])) {
        return $this->classMap[$class];
    }
    if ($this->classMapAuthoritative) {
        return false;
    }

    $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;
}

大家见到 loadClass() ,关键读取 findFile() 涵数。findFile() 在分析类名的情况下关键分成两一部分:classMap 和 findFileWithExtension() 涵数。classMap 非常简单,直接看类名是不是在投射二维数组中就可以。不便的是 findFileWithExtension() 涵数,这一涵数涵盖了 PSR0 和 PSR4 规范的完成。也有个非常值得大家留意的是搜索途径取得成功后 includeFile() 依然是外边的涵数,并并不是 ClassLoader 的友元函数,基本原理跟上边一样,避免有客户写 $this 或 self。也有便是假如类名是以开始的,要除掉随后再配对。

看下 findFileWithExtension 涵数:

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;
    }
}

最终总结

大家根据举例来说下上边编码的步骤:

如果我们在编码中写出 new phpDocumentorReflectionElement(),PHP 会根据 SPL_autoload_register 读取 loadClass -> findFile -> findFileWithExtension。流程如下所示:

  • 将 变为文档分节符/,再加上后缀名php,变为 $logicalPathPsr4, 即 phpDocumentor/Reflection//Element.php;
  • 运用类名第一个字母p做为作为前缀数据库索引检索 prefixLengthsPsr4 二维数组,查到下边这一二维数组:
        p' => 
            array (
                'phpDocumentor\Reflection\' => 25,
                'phpDocumentor\Fake\' => 19,
          )
  • 解析xml这一二维数组,获得2个高层类名 phpDocumentorReflection 和 phpDocumentorFake
  • 在这个二维数组中搜索 phpDocumentorReflectionElement,找到 phpDocumentorReflection 这一高层类名而且长短为25。
  • 在prefixDirsPsr4 投射二维数组中获得phpDocumentorReflection 的文件目录投射为:
    'phpDocumentor\Reflection\' => 
        array (
            0 => ._DIR._ . '/..' . '/phpdocumentor/reflection-common/src',
            1 => ._DIR._ . '/..' . '/phpdocumentor/type-resolver/src',
            2 => ._DIR._ . '/..' . '/phpdocumentor/reflection-docblock/src',
        ),
  • 解析xml这一投射二维数组,获得三个文件目录投射;
  • 查询 “文件目录 文档分节符// substr(&dollar;logicalPathPsr4, &dollar;length)”文档是不是存有,存有即回到。这儿便是
    '._DIR._/../phpdocumentor/reflection-common/src substr(phpDocumentor/Reflection/Element.php,25)'
  • 假如不成功,则运用 fallbackDirsPsr4 二维数组里边的文件目录再次分辨是不是存有文档

以上便是 composer 全自动载入的基本原理分析!

以上便是分析composer的全自动载入基本原理的详尽具体内容,大量请关心自学java网其他相关文章!

WWW.lllT.neT
标签: 工具使用
下载本文:分析composer的全自动载入基本原理.doc

声明:有的资源来自网络转载,版权归原作者所有,如有侵犯到您的权益请联系邮箱:our333@126.com我们将配合处理!

原文地址:分析composer的全自动载入基本原理发布于2021-12-16 11:18:02

相关推荐