zoukankan      html  css  js  c++  java
  • composer的自动加载机制(autoload)

    composer的出现真是让人们眼前一亮,web开发从此变成了一件很『好玩』的事情,开发一个CMS就像在搭积木,从packagist中取出『积木』搭建在自己的代码中,一点一点搭建出一个属于自己的王国。
    从此以后我基本就抛弃了require和include函数,一个项目中,这两个函数只可能出现一次,那就是require '../vendor/autoload.php'
    那么,既然抛弃了传统的文件包含方法,我们使用所有类库都将用namespace和composer自带的autoload。可是,我们自己编写的函数库与类库,怎么用composer的方法来自动加载呢?

    这就要从composer.json说起,我们需要通过修改这个文件来达到目的。
    composer.json相当于是composer的配置文件,这个配置文件中有一个autoload段,比如我的一个项目:

    其中又包含主要的两个选项: files 和 psr-4。
    files就是需要composer自动帮我们加载的函数库(不含类),只要在后面的数组中将函数库的文件路径写入即可。
    psr-4顾名思义,是一个基于psr-4(http://www.php-fig.org/psr/psr-4/)规则的类库自动加载对应关系,只要在其后的对象中,以 "命名空间": "路径" 的方式写入自己的类库信息即可。
    修改完成后,只要执行一下composer update,即可完成对应工作。

    之后,我们在项目中,用如下方式即可加载自定义类库:

    new MrgoonAliSmsAliSms();

    composer的autoload将会自动包含”./mrgoon/aliyun-sms/src/.AliSms.php”,并找到其中的MrgoonAliSms命名空间下的AliSms类。

    我们来深挖一下,探索一下autoload的原理。
    在我们修改完composer.json并执行update后,将会修改./vender/composer/autoload_psr4.php,比如我的某个项目,其中增加了这样一个对应关系:

    这其实就是我刚刚在.json中添加的对应关系,他等于将.josn的配置文件,换成了php的形式。
    那么我看到vendor/autoload.php:

    <?php
    
    // autoload.php @generated by Composer
    
    require_once __DIR__ . '/composer' . '/autoload_real.php';
    
    return ComposerAutoloaderInitff1d77c91141523097b07ee2acc23326::getLoader();

    其执行了一个自动生成的类ComposerAutoloaderInitff1d77c91141523097b07ee2acc23326中的getLoader方法。
    跟进:

        public static function getLoader()
        {
            if (null !== self::$loader) {
                return self::$loader;
            }
    
            spl_autoload_register(array('ComposerAutoloaderInitff1d77c91141523097b07ee2acc23326', 'loadClassLoader'), true, true);
            self::$loader = $loader = new ComposerAutoloadClassLoader();
            spl_autoload_unregister(array('ComposerAutoloaderInitff1d77c91141523097b07ee2acc23326', 'loadClassLoader'));
    
            $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);
    
            $includeFiles = require __DIR__ . '/autoload_files.php';
            foreach ($includeFiles as $file) {
                composerRequireff1d77c91141523097b07ee2acc23326($file);
            }
    
            return $loader;
        }

    可以明显看到,他将autoload_namespaces.php、autoload_psr4.php、autoload_classmap.php、autoload_files.php等几个配置文件包含了进来,并进行了相关处理(setPsr4),最后注册(register)。
    那么我们跟进register方法:

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

    这函数就一行,但简单明了,直接调用php自带的spl_autoload_register函数,注册处理__autoload的方法,也就是loadClass方法。再跟进loadClass方法:

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

    从函数名字就可以大概知道流程:如果存在$class对应的这个$file,则include进来。
    那么进findFile方法里看看吧:

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

    通过类名找文件,最终锁定在findFileWithExtension方法中。
    不过发现了一个小宝藏:在php5.3.0~5.3.2版本下,类名的第一个字符是的小bug,也许以后挖漏洞会用上。
    还是跟进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;
            }
        }

    最终实现将命名空间类这样的类名,给转换成目录名/类名.php这样的路径,并返回完整路径。
    我发现composer的autoload与php自带的spl_autoload,在包含文件时有一点小区别。那就是,spl_autoload会查找.inc类型的文件名,但composer不会。
    另外也可以发现,虽然配置文件的名字是autoload_psr4.php,但实际上psr0格式的自动加载也是支持的。二者最大的不同就是psr0中用”_”来代替目录间的””。

  • 相关阅读:
    JS实现倒计时
    jQuery图片展示插件Galleria
    几个常用的方法
    整理了一个多数据库通用操作类
    Oracle 伪列、取前几条记录、日期判断等
    第十九章 10头节点类,中间节点类和尾节点类 简单
    第十八章 38总结 简单
    第十九章 代码重用 6按别名传递book对像 简单
    第十九章 代码重用 3类书类 简单
    第十九章 代码重用 4为图书重载GetAuthor函数 简单
  • 原文地址:https://www.cnblogs.com/yimingwang/p/10137141.html
Copyright © 2011-2022 走看看