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

    自动加载的类型

    总体来说 composer 提供了几种自动加载类型

    1. classmap
    2. psr-0
    3. psr-4
    4. files

    这几种自动加载都会用到,理论上来说,项目代码用 psr-4 自动加载, helper 用 files 自动加载,development 相关用 classmap 自动加载。 psr-0 已经被抛弃了,不过有些历史遗留依然在用,所以偶尔也会看到。

    classmap

    这应该是最最简单的 autoload 模式了。大概的意思就是这样的:

    {
      "classmap": ["src/"]
    }

    然后 composer 在背后就会读取这个文件夹中所有的文件 然后再 vendor/composer/autoload_classmap.php 中怒将所有的 class 的 namespace + classname 生成成一个 key => value 的 php 数组

    <?php
    return [ 
      'App\Console\Kernel' => $baseDir . '/app/Console/Kernel.php'
    ];
    ?>
    

    然后就可以光明正大地用 spl_autoload_register 这个函数来怒做自动加载了。

    好吧 上面的例子其实有点 tricky 就是上面这个 autoload 实际上是根据 prs-4 来生成出来的。不过这不重要,了解底层重要点,我们可以看到所有的所谓的 autoloading 其实可以理解为生成了这么一个 classmap,这是 composer dump-autoload -o 做的事儿。不然的话compoesr 会吭哧吭哧地去动态读取 psr-4 和 prs-0 的内容。

    psr-0

    现在这个标准已经过时了。当初制定这个标准的时候主要是在 php 从 5.2 刚刚跃迁到 5.3+ 有了命名空间的概念。所以这个时候 psr-0 的标准主要考虑到了 <5.2 的 php 中 类似 Acme_Util_ClassName 这样的写法。

    {
      "name": "acme/util",
      "auto" : {
        "psr-0": {
          "Acme\Util\": "src/"
        }
      }
    }

    文档结构是这样的

    vendor/
      acme/
        util/
          composer.json
          src/
            Acme/
              Util/
                ClassName.php
    

    ClassName.php 中是这样的

    <?php
    class Acme_Util_ClassName{}
    ?>
    

    我们可以看到由于旧版本的 php 没有 namespace 所以必须通过 _ 将类区分开。

    这样稍微有点麻烦。指向一个文件夹之后 src 还要在 src 中分成好几层文档树。这样太深了。没有办法,但是似乎想要兼容 _ 的写法仔细想想这是唯一的办法了。(psr-0 要求 autoloading 的时候将 类中的 _ 转义为 ‘')

    所以在 php5.2 版本已经彻底被抛弃的今天, FIG 就怒推出了 psr-4

    psr-4

    这个标准出来的时候一片喷声,大概的意思就是 FIG 都是傻逼么,刚刚出了 psr-0 然后紧跟着进推翻了自己。不过 FIG 也有自己的苦衷,帮没有 namespace 支持的 php5.2 擦了那么久的屁股,在2014年10月21日的早晨,终于丢失了睡眠。

    抛弃了 psr-0 的 composer 从此变得非常清爽。

    最简单来讲就是可以把 prs-4 的 namespace 直接想想成 file structure

    {
      "name": "acme/util",
      "auto" : {
        "psr-4": {
          "Acme\Util\": "src/"
        }
      }
    }
    vendor/
      acme/
        util/
          composer.json
          src/
            ClassName.php
    

    可以看到将 AcmeUtil 指向了 src 之后 psr-4 就会默认所有的 src 下面的 class 都已经有了 AcmeUtil 的 基本 namespace,而 psr-4 中不会将 _ 转义成  所以就没有必要有 psr-0 那么深得文档结构了。

    <?php
    namespace AcmeUtil;
    class ClassName {}
    ?>
    

    file

    然而这还是不够。因为可能会有一些全局的 helper function 的存在。

    这个写法很简单就不多看了。

    {
      "files": [
        "path/to/file.php"
      ]
    }

    autoload_real.php

    好了看了所有的 autoload 类型那么直接怒看一发实现。

    首先映入眼帘的就是一坨,我的是这样的

    ComposerAutoloaderInit64c47026c93126586e44d036738c0862

    为啥?

    因为这个类是全局的啊少年。

    作为模块化大行其道的今天,全局的类总是有那么点奇怪。为了不让这个 autoload 的 helper 污染全局,composer 的仁兄们还是绞尽脑汁怒弄了这么一个奇怪的 hash。这直接就逼迫广大二笔程序员们不要跟这个撞衫。

    好吧,接着往下看。

    主要只有这么一个方法 getLoader

    <?php
    // autoload_real.php @generated by Composer
    
    class ComposerAutoloaderInit64c47026c93126586e44d036738c0862
    {
        private static $loader;
    
        public static function loadClassLoader($class)
        {
            if ('ComposerAutoloadClassLoader' === $class) {
                require __DIR__ . '/ClassLoader.php';
            }
        }
    
        public static function getLoader()
        {
            if (null !== self::$loader) {
                return self::$loader;
            }
    
            spl_autoload_register(array('ComposerAutoloaderInit64c47026c93126586e44d036738c0862', 'loadClassLoader'), true, true);
            self::$loader = $loader = new ComposerAutoloadClassLoader();
            spl_autoload_unregister(array('ComposerAutoloaderInit64c47026c93126586e44d036738c0862', '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) {
                composerRequire64c47026c93126586e44d036738c0862($file);
            }
    
            return $loader;
        }
    }
    
    function composerRequire64c47026c93126586e44d036738c0862($file)
    {
        require $file;
    }
    
    ?>
    

    在讲什么?其实很简单。

    1. 找 ComposerClassLoader 如果不存在就是生成一个实例放在 ComposerAutoloaderInit64c47026c93126586e44d036738c0862 中
    2. 然后将 composer cli 生成的各种 autoload_psr4autoload_classmapautoload_namespaces(psr-0) 全都注册到 ComposerClassLoader 中。
    3. 直接 require 所有在 autoload_files 中的文件

    其中 composerRequire64c47026c93126586e44d036738c0862 要解释下。 为什么这个不直接用 require 而是定义在了类的外边?

    调查 ComposerClassLoader 发现了这么一个注释

    Scope isolated include. Prevents access to $this/self from included files.

    好吧还是怕二笔程序员犯浑。想想一下,如果有人在 autoload_files 中的文件中写了 $this 或者 self 那就屎了。所以当require 一个file的时候我们希望解释器能够成功报错。

    不容易,终于快要胜利了。

    为什么总是要 composer dump-autoload ?

    刚开始接触用 composer 的时候一直被这个问题蛊惑。很不理解为什么总是要打这句命令才能不报错,现在终于知道根结了。

    因为 database 文件夹使用 classmap 来做加载的。所以只有在打了 composer dumpautoload 之后 composer 才会更新 autoload_classmap 的内容。

    怎样可以避免一直打 composer dump-autoload ?

    可以怒用 psr-4 注册一个文件夹这样增减文件就不用再管了。ComposerClassLoader 会自动检查 composer.json 中注册的 psr-4 入口然后根据 psr-4 去自动查找文件。

    生产环境为什么要 composer dump-atoload -o ?

    因为 psr-4 自动加载会再背后跑一些逻辑。具体可以调查 ComposerClassLoader 去看。

    <?php
    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 (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
                            return $file;
                        }
                    }
                }
            }
        }
    
        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (is_file($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 (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }
    
        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }
    
        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }
    }
    ?>
    

    可以看到 psr-4 或者 psr-0 的自动加载都是一件很累人的事儿。基本是个 O(n2) 的复杂度。另外有一大堆 is_file 之类的 IO 操作所以性能堪忧。

    所以给出的解决方案就是空间换时间。

    CompsoerClassLoader 会优先查看 autoload_classmap 中所有生成的注册类。如果在classmap 中没有发现再 fallback 到 psr-4 然后 psr-0

    所以当打了 composer dump-autoload -o 之后,composer 就会提前加载需要的类并提前返回。这样大大减少了 IO 和深层次的 loop。

    转载自http://blog.hans-lizihan.com/php/2015/06/25/php-composer-autoload.html

  • 相关阅读:
    使用 yo 命令行向导给 SAP UI5 应用添加一个新的视图
    SAP Fiori Elements 应用的 manifest.json 文件运行时如何被解析的
    SAP UI5 标准应用的多语言支持
    微软 Excel 365 里如何设置下拉菜单和自动高亮成指定颜色
    SAP Fiori Elements 应用里的 Title 显示的内容是从哪里来的
    本地开发好的 SAP Fiori Elements 应用,如何部署到 ABAP 服务器上?
    如何在 Cypress 测试代码中屏蔽(Suppress)来自应用代码报出的错误消息
    教你一招:让集群慢节点无处可藏
    应用架构步入“无服务器”时代 Serverless技术迎来新发展
    MySQL数据库事务隔离性的实现
  • 原文地址:https://www.cnblogs.com/sjhsszl/p/8623022.html
Copyright © 2011-2022 走看看