实现 PSR-0和PSR-4的类自动加载器并带案例说明

大家在阅读文档 或者使用一些第三方的框架或者软件的时候,都听过或者看过里面要求说实现了psr0或者psr4的规范。

我也一直在查资料,找痕迹。现在我的理解是,其实这2个规范就是对类的装载,实现自动寻路径


首先我们看下 PSR0

我写代码实现了它的自动加载器

这是加载器代码

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/14
 * Time: 22:27
 */
class Psr0{
    /* psr-0 规范说明
     * 1.一个完整的标准的类文件格式是这样的 \vendor\namespace\class
     * 2.每个命名空间必须有一个顶级命名空间 子命名空间可以有多个 或者没有
     * 3.加载文件的时候 命名空间分隔符会被转换为 DIRECTORY_SEPARATOR
     * 4.类名如果带'_',都会转换为DIRECTORY_SEPARATOR,_的拼接部分必须是类目录的子目录部分并且是一一对应
     * 打个比方 Tik_Tb_Order.php  其中 Tik和Tb必须是该类文件所在的子目录。
     * 5.加载的文件后缀必须以.php结尾
     * 6.verdor namespace class必须由大小写字母组合而成
     */
    public static function autoload($className)
    {
        //去掉最左边的\
        $className = ltrim($className,'\\');
        //获取命名空间
        $position = strrpos($className,'\\');
        $strnamespaces = substr($className,0,$position);
        //获取类名
        $class = substr($className,$position+1);
        $namespaces = explode('\\',$strnamespaces);
        //组装类路径。。psr0规定 命名空间分隔符要被DIRECTORY_SEPARATOR替换。
        $file_path = '';
        foreach ($namespaces as $namespace)
        {
            $file_path .= $namespace.DIRECTORY_SEPARATOR;
        }
        //类名下划线处理
        $class_file = str_replace('_',DIRECTORY_SEPARATOR,$class);
        //最终的file
        $file = './vendor/'.$file_path.$class_file.'.php';
        include $file;
    }
}

然后我的文件目录


我的调用代码 是 start.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/14
 * Time: 22:22
 */
header("Content-type: text/html; charset=utf-8");
require 'Psr0.php';
//定义类的自动加载器
spl_autoload_register('Psr0::autoload');
//测试
$three = new \Com\Three();
$one = new \Com\One\One();
$two = new \Com\One\Fki_Tb_Two();
$three->sh();
$one->sh();
$two->sh();

这是运行效果



看懂了没 没的话 我带大家分析一波。

1.首先 psr0规定 必须有个组织名 我这里是vendor 然后得有个顶级命名空间 我这里是Com

打开我的three.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/14
 * Time: 22:49
 */
namespace Com;

class Three{
    public function sh()
    {
        echo '我是three'.'<br>';
    }
}

我这个类就在当前顶级命名空间下 且没有子命名空间 当然也可以被加载到。


再看one.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/15
 * Time: 10:30
 */
namespace Com\One;
class One{
    public function sh()
    {
        echo '我是one'.'<br>';
    }
}

我这里是设置了子命名空间 为One 根据psr0规范。子命名空间必须是类的文件系统加载的子目录。所以也能通过。

我们再看fki_Tb_two.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/15
 * Time: 10:34
 */
namespace Com\One;

class Fki_Tb_Two{
    public function sh(){
        echo '我是tow'.'<br>';
    }
}

这个类名带下划线 psr0规范说  下划线最后的一部分才是类名。其余部分充当该类被加载的子目录部分,并且和子目录名字母大小一一对应 在本例中。根据我上面目录结构图,它是这个类的2个上一级目录名。所以也能被加载。


我们再看psr4

我先贴出加载器代码

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/14
 * Time: 22:27
 */
//单命名空间
class Psr4_1{
    public static function autoload($className)
    {
        /*
         * Psr-4规范说明
         * 1.合法的类的完全限定名格式是 \namespacename\subnamespace\class
         * 2.必须有一个顶级的命名空间
         * 3.必须有一个终止类名
         * 4.下划线在类中无特殊意义
         * 5.子命名空间必须对于从文件系统载入类文件的一个子目录
         * 6.子目录和子命名空间必须大小写和字母一一对应。子命名空间分割符表示子目录分隔符
         * 7.
         */



        //设置命名空间目录映射
        $namespace_prefix='Lib1\\Red1';
        $base_dir='./Lib1/Red';

        //去掉最左边的\
        $className = ltrim($className,'\\');
        //获取最右边的分隔符位置 用来做命名空间和class的分界点
        $position = strrpos($className,'\\');
        //获取命名空间部分
        $strnamespaces = substr($className,0,$position);
        //判断前缀是否存在 。0表示是合法的,false直接返回错误
        $is_exsists = strpos($strnamespaces,$namespace_prefix);
        if($is_exsists!==0)
        {
            return;
        }
        //获取子命名空间部分
        $Len = strlen($namespace_prefix);
        $str_sub_namespace = substr($strnamespaces,$Len);
        $str_sub_namespace_path = str_replace('\\',DIRECTORY_SEPARATOR,$str_sub_namespace);
        //获取类名
        $class = substr($className,$position+1);

        //拼接目录
        $file = $base_dir.$str_sub_namespace_path.DIRECTORY_SEPARATOR.$class.'.php';
        include $file;

    }
}

在贴出调用代码

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/14
 * Time: 22:22
 */
header("Content-type: text/html; charset=utf-8");
require 'Psr4_1.php';
//定义类的自动加载器
spl_autoload_register('Psr4_1::autoload');

$red = new \Lib1\Red1\Red();

$green = new \Lib1\Red1\Sub\Green();

$red_green = new \Lib1\Red1\Red_Green();

$red->sh();

$green->sh();

$red_green->sh();

效果图



再是目录结构图



我分析一下吧。

首先 psr4是要求命名空间前缀和base_dir有个设置好的对应关系。

我这个psr4_1是一个单命名空间的加载器。稍后我发一个批量的。先拿这个单的说明

我们打开red.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/15
 * Time: 14:05
 */
namespace Lib1\Red1;

class Red{
    public function sh(){
        echo '我是red<br>';
    }
}


这里 我们的命名空间 符合之前预设置好的映射关系。使用能被加载。


再看一个带子命名空间的例子

green.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/15
 * Time: 15:24
 */
namespace Lib1\Red1\Sub;

class Green{
    public function sh(){
        echo '我是green<br>';
    }
}

这里的子命名空间的意思 就是他不在预设值的命名空间前缀里面 也就是没包含他。

那么要让加载器加载到他  我们必须把这个Sub也就是子命名空间 对应一个子目录。也就是在设置好的那个映射关系basedir后面添加一个子目录  要求字母大小一一对应即可。

详情请看我的目录结构图 

psr4规范说明 类名的下划线格式 并没有实际的意义..这个和psr0是区别的。

看red_green.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/15
 * Time: 14:05
 */
namespace Lib1\Red1;

class Red_Green{
    public function sh(){
        echo '我是red_green<br>';
    }
}

把这个当成是一个普通的类文件进行加载

明白这个原理后  我贴出多命名空间支持的psr4加载器 也就是我的psr4_2.php

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/14
 * Time: 22:27
 */
//支持多命名空间部署
class Psr4_2{
    private static $_classMapper = '';
    public static function addClassMapper($prefix,$dir)
    {
        self::$_classMapper[$prefix] = $dir;
    }
    public static function autoload($className)
    {
        //去掉最左边的\
        $className = ltrim($className,'\\');
        //获取最右边的分隔符位置 用来做命名空间和class的分界点
        $position = strrpos($className,'\\');
        //获取命名空间部分
        $strnamespaces = substr($className,0,$position);

        //判断前缀是否存在 。0表示是合法的,false直接返回错误
        foreach (self::$_classMapper as $mapperkey=> $classmapper)
        {
            $namespace_prefix = $mapperkey;

            $is_exsists = strpos($strnamespaces,$namespace_prefix);

            if($is_exsists!==false)
            {
                //获取子命名空间部分
                $Len = strlen($namespace_prefix);
                $str_sub_namespace = substr($strnamespaces,$Len);
                $str_sub_namespace_path = str_replace('\\',DIRECTORY_SEPARATOR,$str_sub_namespace);
                //获取类名
                $class = substr($className,$position+1);
                //拼接目录
                $base_dir = self::$_classMapper[$namespace_prefix];
                $file = $base_dir.$str_sub_namespace_path.DIRECTORY_SEPARATOR.$class.'.php';
                include $file;
                return;
            }
        }
    }
}

我在贴出我的调用代码

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/14
 * Time: 22:22
 */
header("Content-type: text/html; charset=utf-8");
require 'Psr4_2.php';
spl_autoload_register('Psr4_2::autoload');
//定义类的自动加载器
Psr4_2::addClassMapper('Lib1\Red1','./Lib1/Red');
Psr4_2::addClassMapper('Lib2','./Lib2');
Psr4_2::addClassMapper('Lib3\Hei','./lib3/hei/src');

$red = new \Lib1\Red1\Red();
$red->sh();

$green = new \Lib1\Red1\Sub\Green();
$green->sh();

$red_green = new \Lib1\Red1\Red_Green();
$red_green->sh();

$bule = new \Lib2\Bule();
$bule->sh();

$ming = new \Lib1\Red1\sub\Subb\Ming();
$ming->sh();

$fi_cc = new \Lib2\Fi_CC();
$fi_cc->sh();

$hei = new \Lib3\Hei\Hei();
$hei->sh();

$req = new \Lib3\Hei\Req\Req();
$req->sh();

效果图




抽几个具有代表性的分析一下 给大家看。

看我的req.php


文件代码

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/4/15
 * Time: 17:56
 */

namespace Lib3\Hei\Req;

class Req{
    public function sh(){
        echo '我是req<br>';
    }
}

也是存在一个子命名空间的问题 我们只要指定对应的子目录就能实现对他的加载。

总结一下  :根据psr4的自动加载规则 我们可以根据配置好的命名空间前缀。快速定位类的文件位置。还有一点,为了开发的便捷性 我们可以配置多个命名空间前缀 ,使类文件避免深度索引。


阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页