composer 解析

composer 代码解析

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

//引入核心文件
return ComposerAutoloaderInit20b8779524fa70783a930f4c075cc373::getLoader();

        //经典单例模式
        if (null !== self::$loader) {
            return self::$loader;
        }

        //获得自动加载的核心对象
        spl_autoload_register(array('ComposerAutoloaderInit20b8779524fa70783a930f4c075cc373', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        spl_autoload_unregister(array('ComposerAutoloaderInit20b8779524fa70783a930f4c075cc373', 'loadClassLoader'));

        //初始化自动加载的核心对象
        $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\ComposerStaticInit20b8779524fa70783a930f4c075cc373::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 = Composer\Autoload\ComposerStaticInit20b8779524fa70783a930f4c075cc373::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequire20b8779524fa70783a930f4c075cc373($fileIdentifier, $file);
        }

        return $loader;

第一部分 获得自动加载核心对象

加载文件中的函数为自动加载的方法

  • 主要的作用还是用来加载核心类
    • 先注册一个自动加载方法 目的 引入文件ClassLoader.php
    • 实例核心类 ClassLoader
    • 销毁刚注册的自动加载方法
        /***********获得自动加载核心对象***********/
        spl_autoload_register(array('ComposerAutoloaderInit77f35ced74620cc6137022e9523af296', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        spl_autoload_unregister(array('ComposerAutoloaderInit77f35ced74620cc6137022e9523af296', 'loadClassLoader'));
    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

第二部分 初始化自动加载核心对象

主要有两种方式,但添加的内容都是一样的。

  • 第一种就是静态初始化内容。
  • 第二种就是加载文件的方式。
         /*********** 初始化 自动加载核心对象***********/
        $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\ComposerStaticInit77f35ced74620cc6137022e9523af296::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);
            }
        }

引入autoload_static.php

静态初始化只支持 PHP5.6 以上版本并且不支持HHVM 虚拟机。
查看文件主要的是getInitializer 方法 并返回一个匿名函数,
为什么是匿名函数呢?
是因为类中的$files $prefixLengthsPsr4 等字段都是static的 所以使用\Closure::bind 绑定给闭包的类作用域 把字段赋值给$loader

class ComposerStaticInit77f35ced74620cc6137022e9523af296
{
    public static $files = array();
    public static $prefixLengthsPsr4 = array ();
    public static $prefixDirsPsr4 = array();
    public static $classMap = array();
    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInit77f35ced74620cc6137022e9523af296::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInit77f35ced74620cc6137022e9523af296::$prefixDirsPsr4;
            $loader->classMap = ComposerStaticInit77f35ced74620cc6137022e9523af296::$classMap;

        }, null, ClassLoader::class);
    }
}

第三部分 注册自动加载核心类对象

只有一行主要的就是把函数中的方法设置为自动加载方法添加函数到队列之首

  • 在代码层面中调用的扩展依靠的就是它
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

第四部分 自动加载全局函数

加载用到的函数并循环引入它

        if ($useStaticLoader) {
            $includeFiles = Composer\Autoload\ComposerStaticInit77f35ced74620cc6137022e9523af296::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequire77f35ced74620cc6137022e9523af296($fileIdentifier, $file);
        }

function composerRequire77f35ced74620cc6137022e9523af296($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}

为什么要在类的外面进行引入?

网上的说法是

因为怕有人在全局函数所在的文件写 this 或者self。

假如includeFiles有个 app/helper.php 文件,这个 helper.php 文件的函数外有一行代码:this->foo(),如果引导类在 getLoader() 函数直接 require(file),那么引导类就会运行这句代码,调用自己的 foo() 函数,这显然是错的。
事实上 helper.php 就不应该出现 thisself 这样的代码,这样写一般都是用户写错了的,一旦这样的事情发生,第一种情况:引导类恰好有 foo() 函数,那么就会莫名其妙执行了引导类的 foo();第二种情况:引导类没有 foo() 函数,但是却甩出来引导类没有 foo() 方法这样的错误提示,用户不知道自己哪里错了。
require| 语句放到引导类的外面,遇到 this 或者 self,程序就会告诉用户根本没有类,thisself 无效,错误信息更加明朗。

第二个问题,为什么要用 hash 作为 fileIdentifier

上面的代码明显可以看出来这个变量是用来控制全局函数只被 require一次的,那为什么不用require_once 呢?事实上require_once
require 效率低很多,使用全局变量 GLOBALS 这样控制加载会更快。
但是其实也带来了一些问题,如果存在两个自动加载,而且全局函数的相对路径不一致,很容易造成 hash 不相同,
但是文件相同的情况,导致重复定义函数。所以在使用 composer 的时候最好要统一自动加载和依赖机制,最好不要多重自动加载。

运行

每当 PHP 遇到不认识的命名空间时就会调用函数堆栈的每个函数,直到加载命名空间成功。
所以 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() 函数,这个函数包含了 PSR0PSR4 标准的实现。
还有个值得我们注意的是查找路径成功后 includeFile() 仍然类外面的函数,并不是 ClassLoader 的成员函数,原理跟上面一样,防止有用户写 $thisself
还有就是如果命名空间是以 \ 开头的,要去掉 \然后再匹配。

    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            $subPath = $class;
            while (false !== $lastPos = strrpos($subPath, '\\')) {
                $subPath = substr($subPath, 0, $lastPos);
                $search = $subPath . '\\';
                if (isset($this->prefixDirsPsr4[$search])) {
                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        if (file_exists($file = $dir . $pathEnd)) {
                            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;
        }

        return false;
    }

下面我们通过举例来说下上面代码的流程:

如果我们在代码中写下 ‘PhpOffice\PhpSpreadsheet\IOFactory’,PHP 会通过 SPL 调用 loadClass->findFile->findFileWithExtension。
首先默认用 php 作为文件后缀名调用 findFileWithExtension 函数里,利用 PSR4 标准尝试解析目录文件,
如果文件不存在则继续用 PSR0 标准解析,如果解析出来的目录文件仍然不存在,
但是环境是 HHVM 虚拟机,继续用后缀名为 hh 再次调用 findFileWithExtension 函数,
如果不存在,说明此命名空间无法加载,放到 classMap 中设为 false,以便以后更快地加载。
对于 PhpOffice\PhpSpreadsheet\IOFactory,当尝试利用 PSR4 标准映射目录时,步骤如下:

PSR4 标准加载

  • 首先将 ‘’ 转为’/’ 加上文件后缀 即 PhpOffice\PhpSpreadsheet\IOFactory.php
  • 在利用首字母作为索引查看 prefixLengthsPsr4 是否存在
       'P' => 
       array (
           'Psr\\SimpleCache\\' => 16,
           'Prophecy\\' => 9,
           'PhpOffice\\PhpSpreadsheet\\' => 25,
       ),

存在然后就拿去 PhpOffice\PhpSpreadsheet\IOFactory 获取顶级命名空间及长度为 PhpOffice\PhpSpreadsheet 和 24
prefixDirsPsr4 里面查找 PhpOffice\PhpSpreadsheet\

        'PhpOffice\\PhpSpreadsheet\\' => 
        array (
            0 => __DIR__ . '/..' . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet',
        ),

循环这个数组 查找 dir+/IOFactory.php 是否存在如果存在就 直接放回文件路径

                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        if (file_exists($file = $dir . $pathEnd)) {
                            return $file;
                        }

如果没有找到就 在整个 prefixDirsPsr4 进行 dir+/IOFactory.php 查询

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

PSR0 标准加载

如果 PSR4 标准加载失败,则要进行 PSR0 标准加载:

  • 首先将 ‘\ ’ ‘_ ’ 转为’/’ 加上文件后缀 即 PhpOffice\PhpSpreadsheet\IOFactory.php
  • 获取到 psr-0 顶级命名空间后 跟 psr-4 进行一样的操作
  • 如果失败,则利用 fallbackDirsPsr0 数组里面的目录继续判断是否存在文件,具体方法是“目录+文件分隔符//+logicalPathPsr0”
  • 如果仍然找不到,则利用 stream_resolve_include_path(),在当前 include 目录寻找该文件,如果找到返回绝对路径。

如果都没有找到 放入missingClasses 方便下次直接查找。

需要了解的函数

函数内容
PHP_SAPI判断执行方式
php_sapi_nameweb 服务的接口类型
dirname返回路劲中的目录部分
is_file判断给定文件名是否为一个正常的文件
property_exists检查对象或类是否具有该属性
call_user_func把第一个参数作为回调函数调用
strrpos计算指定字符串在目标字符串中最后一次出现的位置
file_exists检查文件或目录是否存在
function_exists如果给定的函数已经被定义就返回 TRUE

参考文献

Composer的Autoload源码实现——注册与运行
深入解析 composer 的自动加载原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值