PSR4规范 及composer autoload的实现

PSR (Proposing  a Standards Recommondation) PHP 编码规范。

https://learnku.com/docs/psr/psr-4-autoloader/1608

https://www.php-fig.org/psr/psr-4/

PSR -4  自动加载规范

\<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>

ii 全限定类名可以有一个或者多个子命名空间名称。

iv下划线在全限定类名中没有任何特殊含义(在 PSR-0 中下划是有含义的)。

vi 所有类名的引用必须区分大小写。

全限定类名的加载过程:

 

最终的类名与以.php 结尾的文件名保持一致,这个文件的名字必须和最终的类名相匹配(意思就是如果类名是 FooController,那么这个类所在的文件名必须是 FooController.php)。

. 范例

下表显示了与给定的全限定类名、命名空间前缀和根目录相对应的文件的路径。

Fully Qualified Class Name

Namespace Prefix

Base Directory

Resulting File Path

\Acme\Log\Writer\File_Writer

Acme\Log\Writer

./acme-log-writer/lib/

./acme-log-writer/lib/File_Writer.php

\Aura\Web\Response\Status

Aura\Web

/path/to/aura-web/src/

/path/to/aura-web/src/Response/Status.php

\Symfony\Core\Request

Symfony\Core

./vendor/Symfony/Core/

./vendor/Symfony/Core/Request.php

\Zend\Acl

Zend

/usr/includes/Zend/

/usr/includes/Zend/Acl.php

PSR-0 的发展史

在 PHP 5.2 之前,PSR-0 的类命名标准和自动加载标准是以被广泛使用的 Horde/PEAR 约定为准。这个约定里要求将所有的 PHP 类文件放在一个主目录中, 并使用下划线连接的字符串来表示命名空间,如下所示:

面向 - 包的自动加载

  1. 实现者 必须 使用两个以上的命名空间层级: 一个 vendor 名, vendor 内的包名。 这两个顶级名称组合被简称为 vendor-package 或者 vendor-package namespace.)
  2. vendor-package namespace 可以 映射到任意目录。 完全限定类名的其余部分,必须映射 命名空间名称 同名目录, 类名必须映射到 .php 结尾的 同名文件。
  3. 允许 vendor-package namespace 可以映射到任何目录, 也可能是 多个目录

  4. 结束遵从 类名中下划线 作为 目录分隔符 的做法。

4.1 被选中的方案

   具体来说,它禁止抛出异常和错误,主要有这两方面考虑:

  1. PHP 中 自动加载器 设计 是可堆叠的,如果一个自动加载器 不能加载,则其他的 仍有机会继续加载。
  2. class_exists()  和 interface_exists()  允许“在尝试自动加载后仍然找不到类” 的 存在, 一个用例是 :若自动加载器抛出异常将使得 class_exists() 不可用, 从互操作性的角度来看这是无法接受的。

缺点:

  • 不能像 PSR-0 仅仅通过类名就能确定它在文件系统的具体位置 (这种 "类 - 到 - 文件" 约定继承自 Horde/PEAR)。

例子:

https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md

闭包:

/**
 *
 * 在 用 SPL的方式 注册了 这个 autoload 函数后,下面的 一行 将会引起 函数 去尝试
 *  从 /path/to/project/src/Baz/Qux 加载  \Foo\Bar\Baz\Qux  类
 *
 *  new \Foo\Bar\Baz\Qux
 *
 * @param string $class fully-qualified 类 名
 * @return void
 */
spl_autoload_register(function($class){
    //project-specific 命名空间 前缀
    $prefix = 'Foo\\Bar\\';
    
    //命名空间前缀 指向的/映射的  base 目录
    $base_dir = __DIR__.'/src/';
    
    //判断 这个类 是否使用了 命名空间 前缀
    $len = strlen($prefix);
    if(strncmp($prefix, $class, $len) !== 0 ){
        //这个类不归我管, 下一个 registered 去处理吧
        return;
    }
    
    //获取相对 的 类名
    $relative_class = substr($class, $len);
    
    /**
     * 使用 base 目录 将 命名空间前缀 给替换了
     * 接着,用 目录分隔符 /  替换 命名空间分隔符,
     * 最好 追加 .php
     */
    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
    
    //如果文件存在,就 require 它
    if(file_exists($file)) {
        require $file;
    }
});

类的实现方式:

<?php

namespace Example;
/**
 *
 * 允许 多个 base 目录 对应 一个 命名空间前缀(single namespace prefix)
 *
 *     /path/to/packages/foo-bar/
 *         src/
 *             Baz.php             # Foo\Bar\Baz
 *             Qux/
 *                 Quux.php        # Foo\Bar\Qux\Quux
 *         tests/
 *             BazTest.php         # Foo\Bar\BazTest
 *             Qux/
 *                 QuuxTest.php    # Foo\Bar\Qux\QuuxTest
 *
 * 添加 class files的 路径  给 " \Foo\Bar " 命名空间前缀  如下:
 *  <?php
 *  // 实例化 loader
 *  $loader = new \Example\Psr4AutoloaderClass;
 *
 *  //注册 autoloader
 *  $loader->register();
 *
 *  // 注册 base 目录s 给 命名空间前缀(namespace prefix)
 *  $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/src');
 *  $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/tests');
 *
 *  下面的 line 将引起 autoloader 去尝试 从 /path/to/packages/foo-bar/src/Qux/Quux.php  加载
 *  \Foo\Bar\Qux\Quux 类
 *
 *  <?php
 *  new \Foo\Bar\Qux\Quux;
 *
 *  下面的 line 将引起 autoloader 去尝试 从 /path/to/packages/foo-bar/tests/Qux/QuuxTest.php 加载
 *  \Foo\Bar\Qux\QuuxTest 类
 *
 *  <?php
 *  new \Foo\Bar\Qux\QuuxTest;
 *
 *
 */
class Psr4AutoloaderClass
{
    /**
     * 一个关联数组, key 是 namespace prefix , value 是 base 目录s 的数组
     *
     * @var array
     */
    protected $prefixes = array();
    
    /**
     * 用 SPL autoloader stack 注册 loader
     */
    public function register()
    {
        spl_autoload_register(array($this, 'loadClass'));
    }
    
    /**
     * 添加一个 base 目录 给 一个 namespace prefix
     *
     *
     * @param string $prefix   The namespace prefix
     * @param string $base_dir  存放 class files 的 base 目录
     * @param bool $prepend  如果 true , 把 base 目录 追加到 stack 的前面; 这个引起 它被 第一个搜索到
     */
    public function addNamespace($prefix, $base_dir, $prepend = false)
    {
        //正常化 namespace prefix
        $prefix = trim($prefix, '\\') . '\\';
        
        // 用 trailing separator 正常化 base 目录
        $base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';
        
        //初始化 namespace prefix 数组
        if(!isset($this->prefixes[$prefix])){
            $this->prefixes[$prefix] = array();
        }
        
        // 给 namespace prefix 持有 base 目录
        if($prepend) {
            array_unshift($this->prefixes[$prefix], $base_dir);
        } else {
            array_push($this->prefixes[$prefix], $base_dir);
        }
        
    }
    
    /**
     * 给定 一个 class name 加载 class file
     *
     * @param string $class The fully-qualified class name.
     * @return mixed  成功时 返回 mapped file name .  失败时 返回 false
     */
    public function loadClass($class)
    {
        // 当前 namespace prefix
        $prefix = $class;
        
        // 从后往前处理 $class 的 namespace names
        // class name 到 mapped filed name
        while( false !== $pos = strrpos($prefix, '\\')){
            //在 prefix 保留 trailing namespace separator
            $prefix = substr($class, 0, $pos + 1);
            
            // 剩余的 是 relative class name
            $relative_class = substr($class, $pos + 1);
            
            // 对于 prefix 和 relative class  试着 去加载 一个 mapped file
            $mapped_file = $this->loadMappedFile($prefix, $relative_class);
            if($mapped_file) {
                return $mapped_file;
            }
            
            //为了 下一次的迭代(strrpos) 移除掉 trailing namespace separator
            $prefix = rtrim($prefix, '\\');
        }
        
        // 没有 找到 mapped file
        return false;
    }
    
    /**
     * 给定 一个namespace prefix 和 relative_class.
     *
     *
     * @param string $prefix  The namespace prefix
     * @param string $relative_class   The relative class name.
     * @return mixed Boolean false : 如果 没有 mapped file 可以被加载; 或者 mapped file 被 加载了
     */
    protected function loadMappedFile($prefix, $relative_class)
    {
        if(!isset($this->prefixes[$prefix])){
            return false;
        }
        
        foreach ($this->prefixes[$prefix] as $base_dir){
            // 用 base 目录 替换  namespace prefix
            // 用目录separators 替换 namespace separators
            // 在 relative class name 中追加 .php
            $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
            
            //如果 mapped file 存在,require 它
            if($this->requireFile($file)){
                return $file;
            }
        }
        
        // 没找到
        return false;
    }
    
    /**
     *
     * @param string $file 要去require 的file
     * @return bool True :如果存在; fale : 如果没有
     */
    protected function requireFile($file)
    {
        if(file_exists($file)){
            require $file;
            return true;
        }
        return false;
    }
    
    
}

composer autoload 实现

可重点关注下 autoload_real.php,  autoload_static.php, ClassLoader.php

autoload_static.php 中 类  ComposerStaticInit.......

属性 public static $files  = array(....)    多是一些函数文件(.php)   ;php < 5.6 时 用的是autoload_files.php 这个文件

       这个数组里的 不管用不用,一开始就被加载了(require); 一旦加载后就:

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

属性 public static  $prefixLengthsPsr4 = array('y' => ...., 'p' => .... )

        作用1 : 为 一个namespace prefix 是否注册了,提供快速的判断  如: ClassLoader.php

    if (isset($this->prefixLengthsPsr4[$first])) {

.....

       }

       目前还看不到 其中value  ( namespace pefiex 长度) 的使用的地方

属性 public static  $prefixDirsPsr4 = array( ....)  这个数组 key  就是 namespace prefix , value 就是 base_dir;

属性 public static $prefixesPsr0 = array(...)

属性 public static $classMap = array(...)    这里边都是 看着不常见的类

 

ClassLoader.php 中 

属性 public  $fallbackDirsPsr4   ; 那些 无 namespace prefix 的但符合psr4  类的命名规则的  base_dir 存放的地方。

            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge(
                    (array) $paths,
                    $this->fallbackDirsPsr4
                );

文件

autoload_classmap.php,

autoload_namespaces.php,

autoload_psr4.php  有什么作用?

几个文件每个文件里 包含一个数组 对应 autoload_static 里边的属性; 因为 php5.6 之前 不能给静态属性赋值的原因所以 几个属性被拆成几个文件了

        $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\ComposerStaticInitba82d674722f42b6a4ef99f6ae94aec5::getInitializer($loader));
        } else {
。。。。。。。

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值