更多优质内容
请关注公众号

PHP设计模式篇(二) 类自动加载-张柏沛IT博客

正文内容

PHP设计模式篇(二) 类自动加载

栏目:PHP 系列:PHP设计模式 发布时间:2022-03-15 20:54 浏览量:1212

php的__autoload魔术方法和spl_autoload_register()可以做到在不手动引入类文件的情况下自动加载类。


- 魔术方法__autoload()

__autoload()方法的用法如下:

<?php  
    function __autoload($className){  
        $dirPath "./person/";  
        $filePath = rtrim($dirPath'/') . '/' . $className '.php';  
        if (is_file($filePath)){  
            include($filePath);  
        }  
    }  
  
    $person new Person("zbp"24);   // 使用不存在的(未引入的)类时,就会自动调用__autoload()方法
    $person->detail();  
?>  



- 自动类加载注册函数 spl_autoload_register

spl_autoload_register(autoload_function,throw,prepend)

使用这个函数的时候,他会在内部创建一个SPL __autoload 函数队列,并将注册的 autoload_function放到这个函数队列中。

一旦调用未引入的类文件的时候,系统会按照顺序遍历执行这个队列中的所有函数直到成功引入这个类文件。


· autoload_function

这是一个函数【方法】名称,可以是字符串或者数组(调用类方法使用)。这个函数(方法)要做的就是,把要用到的类文件包含include(requeire)进来。


· throw

此参数设置了 autoload_function 无法成功注册时, spl_autoload_register()是否抛出异常。


· prepend

如果是 true,spl_autoload_register() 会添加函数到队列之首,而不是队列尾部。



我们用 spl_autoload_register() 来重新实现以下刚刚的程序:

<?php  
    function autoload($className){  
        $dirPath "./person/";  
        $filePath = rtrim($dirPath'/') . '/' . $className '.php';  
        if (is_file($filePath)){  
            include($filePath);  
        }  
    }  
  
    spl_autoload_register('autoload'truetrue);  
    $person new Person("zbp"24);  
    $person->detail();  
?>  


__autoload 的缺点是无法定义多个自动类加载方法而sql_autoload_register可以。


例如,我在一个入口文件想用自动加载类的功能去加载位于两个目录下的两个类文件 /a/a.php 和 /b/b.php 而且 我不知道a.php是位于a目录,b.php位于b目录。此时__autoload就要写一系列的if…else…去判断这位于哪个目录,如果类不存在a这个目录就尝试去b目录找。

而如果使用sql_autoload_register可以直接定义两个自定义的加载类方法,一个去include a目录,一个去include b目录。



- psr4规范

上述例子还不是 spl_autoload_register 的正确使用方式。应该结合psr4规范来进行类的自动引入。

PSR4是一种自动加载规范,老版本是PSR0,尽管thinkPHP支持PSR4和PSR0的自动加载方式,但是默认也是优先进行PSR4加载,如果失败,再进行PSR0的加载。


PSR4规范要求一个完整的类名应该具有完整的命名空间,这些命名空间和该类所在的目录路径对应。


具体要求如下:

a、使用一个类时(如new或者用一个类调用静态方法),需要使用完整的类名。完整的类名必须有一个根命名空间,可以有多个子命名空间 如

new \Vendor\Cake\Name\ClassName() 是对的;

new ClassName()是错的;

use \Vendor\Cake\Name\ClassName 再 new ClassName()是对的。


b、完整的类名最右边必须有一个类名,类名的下划线没有特殊含义,类名 必须 与对应的以 .php 为后缀的文件同名;


c、自动加载器(autoloader)的实现 一定不可抛出异常、一定不可触发任一级别的错误信息以及 不应该有返回值。


PSR-4和PSR-0最大的区别是对下划线(underscore)的定义不同。PSR-4中,在类名中使用下划线没有任何特殊含义。而PSR-0则规定类名中的下划线_会被转化成目录分隔符。


下面看看结合了 psr4 的 自动加载器(类)如何实现:

<?php  
    class autoloadClass{  
        // 指定顶级命名空间所在的目录,这个目录是一个基础目录或者说根目录  
        private static $vendorMap = [  
            'app' => __DIR__ . DIRECTORY_SEPARATOR .'..' . DIRECTORY_SEPARATOR . 'app'    // DIRECTORY_SEPARATOR常量就是 / 或者 \ 分隔符,根据所在的操作系统而定  
        ];  
  
        // $className 此时是带了命名空间的类名  
        // PS:由于需要传入 autoload() 的 $className 是带命名空间的类名,所以类必须用namespace制定命名空间,而且use的时候也必须使用命名空间  
        public static function autoload($className){  
            $filePath self::findFile($className);   
            if (is_file($filePath)){  
                include($filePath);  
            }  
        }  
  
        // 根据带有命名空间的类名寻找类所在的路径  
        private static function findFile($className){  
            // 获取顶级命名空间第一个\所在的位置  
            $pos = strpos($className, DIRECTORY_SEPARATOR);  
              
            // 获取类名中的顶级命名空间  
            $vendorNameSpace = substr($className0$pos);  
  
            // 根据顶级命名空间找到基础目录  
            $baseDir self::$vendorMap[$vendorNameSpace] ?? __DIR__ . DIRECTORY_SEPARATOR;  
  
            // 拼接完整文件路径  
            $classNameWithoutVendorSpace = str_replace('\\', DIRECTORY_SEPARATOR, substr($className$pos));  
            $filePath = realpath($baseDir $classNameWithoutVendorSpace '.php');  
  
            return $filePath;  
        }  
    }  
  
?>  


index.php 入口文件

<?php  
    include('./lib/autoloadClass.php');     // 引入自动加载类  
    spl_autoload_register(['autoloadClass''autoload'], truetrue);       // 只需注册一个自动加载方法即可  
  
    use app\os\Linux;       // 在use一个命名空间的类时,还没有触发自动加载方法。 use一个命名空间是为了待会调用autoload()的时候所传入的$classname带上命名空间。 
    use app\person\User;      
  
    $user new User("zbp"24'root');    // 在使用这个类的时候,才会触发自动加载方法,真正的做到了懒加载  
    $linux new Linux($user);  
    $linux->start();  
?>  


主要逻辑有2点:

1、预定义所有允许自动加载的顶级命名空间与其基本目录的映射;

2、使用 sql_autoload_register()注册autoload方法。当外界使用某个类的时候,autoload方法根据该类(连带命名空间)的顶级命名空间找到对应的基本目录,根据该类的子命名空间找到类的路径。


特别注意:

开发者A在TP5项目中安装了一个composer的类M(例如是/vendor/phpmailer这个目录),开发者B在自己的本地TP5项目中直接拷贝类M到自己的相应目录中,结果运行失败,没有加载到类M。

运行失败是因为,B没有在/vendor/composer/autoload_static.php 这个自动加载类的顶级命名空间容器中添加 phpmailer这个目录路径与其顶级命名空间的映射。此时我们可以手动加上或者不用手动复制phpmailer目录,而是使用composer命令安装,他会自动在autoload_static.php做修改加上这个映射。




更多内容请关注微信公众号
zbpblog微信公众号

如果您需要转载,可以点击下方按钮可以进行复制粘贴;本站博客文章为原创,请转载时注明以下信息

张柏沛IT技术博客 > PHP设计模式篇(二) 类自动加载

热门推荐
推荐新闻