autoload之composer分析

这里要介绍的不是composer.phar,而且由php composer.phar install生成的整一个composer文件下的autoload的结构。

想在项目总引入composer进行包管理,都会说将需要的包填到composer.json文件,然后执行composer install,会在vender文件夹下生成autoload.php,项目中引入这个文件就可以实现类的自动加载了。

那引入autoload.php之后到底做了帮我们做了什么,我们一起来看看。


首先查看autoload.php:

<?php

// autoload.php @generated by Composer

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

return ComposerAutoloaderInit621850f9ac0e7f3f7c90ee4affc93eee::getLoader();

这里是require了一个compsoer/autoload_real.php文件,然后返回了一个getLoader()静态方法,看来我们要进去这个方法里看一下。

(ps: autoload_real.php 这个名字中也能看出,这才是真正实现autoload机制的地方)

<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit621850f9ac0e7f3f7c90ee4affc93eee
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    public static function getLoader()
    {
        //如果成员$loader已经赋值就直接返回,典型的单例模式
        if (null !== self::$loader) {
            return self::$loader;
        }

        //将ComposerAutoloaderInit621850f9ac0e7f3f7c90ee4affc93eee类中的loadClassLoader方法注册为自动类加载处理函数
        spl_autoload_register(array('ComposerAutoloaderInit621850f9ac0e7f3f7c90ee4affc93eee', 'loadClassLoader'), true, true);
        //使用新注册的loadClassLoader方法加载\Composer\Autoload\ClassLoader类,并实例化一个对象
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        //注销loadClassLoader
        spl_autoload_unregister(array('ComposerAutoloaderInit621850f9ac0e7f3f7c90ee4affc93eee', 'loadClassLoader'));

        //如果php版本大于5.6并且未定义HHVM_VERSION常量,返回true
        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
        if ($useStaticLoader) {
            //autoload_static.php 记录了namespace+类名与类所在文件的映射关系
            require_once __DIR__ . '/autoload_static.php';

            //降映射关系加载到$loader里
            call_user_func(\Composer\Autoload\ComposerStaticInit621850f9ac0e7f3f7c90ee4affc93eee::getInitializer($loader));
        } else {
            //设置单独namespace对于的文件夹位置的映射关系
            $map = require __DIR__ . '/autoload_namespaces.php';
            foreach ($map as $namespace => $path) {
                $loader->set($namespace, $path);
            }

            //符合psr4命名规范的namespace对于的文件夹位置的映射关系
            $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);

        //需要预先require的文件
        if ($useStaticLoader) {
            $includeFiles = Composer\Autoload\ComposerStaticInit621850f9ac0e7f3f7c90ee4affc93eee::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequire621850f9ac0e7f3f7c90ee4affc93eee($fileIdentifier, $file);
        }

        return $loader;
    }
}

function composerRequire621850f9ac0e7f3f7c90ee4affc93eee($fileIdentifier, $file)
{
    //循环导入文件,并记录到$GLOBALS['__composer_autoload_files']变量中
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

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

到这里,对autoload的初始化就完成了,接下来是对一个类的查找工作。

在composer/ClassLoader.php 

public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }
使用本类中的loadClass()方法来处理未找到的类:

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

            return true;
        }
    }
这方法就是先查找该类是否存在,存在则include进来。再看看findFile()方法是如何查找并返回类的:

/**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
        //如果类名以\开头,则从第二字符开始截取类名
        if ('\\' == $class[0]) {
            $class = substr($class, 1);
        }

        // class map lookup
        //在$this->classMap中查找$class,找到就返回文件名
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        //是否有权利访问classMap
        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;
    }

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

        //取类的首字母
        $first = $class[0];
        //prefixLengthsPsr4中根据首字母进行了分类,可查看autoload_static.php中的prefixLengthsPsr4成员变量
        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
        //自定义的psr4规则
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        //如果是符合psr0规则的,则替换_为/
        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;
        }

        //prefixesPsr0中根据首字母进行了分类,可查看autoload_static.php中的prefixesPsr0成员变量
        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
        //用户添加符合psr0的规则
        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;
        }
    }

顺序是先查classMap,因为大部分类的映射关系都在这个变量里,然后下面findFileWithExtension()方法里是以一些其他维度进行查询。

整体class的autoload的init和find就是这样,好像主要的工作还是在php composer.phar install的时候已经完成了,这个时候就已经理出了类名和类所在文件的映射关系。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李昂的数字之旅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值