浅析PHP类的自动加载和命名空间

php是使用require(require_once)include(include_once)关键字加载类文件。可是在实际的开发工程中咱们基本上不会去使用这些关键字去加载类。 由于这样作会使得代码的维护至关的困难。实际的开发中咱们会在文件的开始位置用use关键字使用类,而后直接new这个类就能够了. 至于类是怎么加载的,通常都是框架或者composer去实现的。php

<?php

use Illuminate\Container\Container;

$container = new Container();

自动加载

咱们能够经过一段伪代码来模拟一下在类的实例化工程中类是如何工做的laravel

function instance($class)
{
    // 若是类已加载则返回其实例
    if (class_exists($class, false)) {
        return new $class();
    }
    // 查看 autoload 函数是否被用户定义
    if (function_exists('__autoload')) {
        __autoload($class); // 最后一次加载类的机会
    }
    // 再次检查类是否存在
    if (class_exists($class, false)) {
        return new $class();
    } else { // 系统:我实在没辙了
        throw new Exception('Class Not Found');
    }
}

php在语言层面提供了__autoload 魔术方法给用户来实现本身的自动加载逻辑。当用户去new一个类的时候,若是该类没有被加载,php会在抛出错误前调用__autoload方法去加载类。下面的例子中的__autoload方法只是简单的输出要加载类的名称, 并无去实际的加载对应的类, 因此会抛出错误。数组

<?php

use Illuminate\Container\Container;

$container = new Container();

function __autoload($class)
{
    /* 具体处理逻辑 */
    echo $class;// 简单的输出要加载类的名称
}

/**
 *
运行结果
Illuminate\Container\Container
Fatal error: Uncaught Error: Class 'Illuminate\Container\Container' not found in D:\project\php\laravel_for_ci_cd\test\ClassLoader.php:5
Stack trace:
#0 {main}
  thrown in D:\project\php\laravel_for_ci_cd\test\ClassLoader.php on line 5
 */

明白了 __autoload 函数的工做原理以后,咱们来用它去实现一个最简单自动加载。咱们会有index.phpPerson.php两个文件在同一个目录下。微信

//index.php
<?php
function __autoload($class)
{
    // 根据类名肯定文件名
    $file = './'.$class . '.php';
    if (file_exists($file)) {
        include $file; // 引入PHP文件
    }
}
new Person();

/*---------------------分割线-------------------------------------*/

//Person.php
class Person
{
    // 对象实例化时输出当前类名
    function __construct()
    {
        echo '<h1>' . __CLASS__ . '</h1>';
    }
}

/**运行结果
 * 输出 <h1>Person</h1>
 */

命名空间

命名空间并非什么新鲜的事务,不少语言都早就支持了这个特性(只是叫法不相同),它主要解决的一个问题就是命名冲突! 就好像平常生活中不少人都会重名,咱们必需要经过一些标识来区分他们的不一样。好比说如今咱们要用php介绍一个叫张三的人 ,他在财务部门工做。咱们能够这样描述。app

namespace 财务部门;
 
class 张三
{
    function __construct()
    {
        echo '财务部门的张三';
    }
}

这就是张三的基本资料 , namespace是他的部门标识,class是他的名称. 这样你们就能够知道他是财务部门张三而不是工程部门张三composer

非限定名称,限定名称和彻底限定名称

1.非限定名称,或不包含前缀的类名称,例如 $comment = new Comment(); 若是当前命名空间是BlogArticleComment将被解析为、BlogArticleComment。若是使用Comment的代码不包含在任何命名空间中的代码(全局空间中),则Comment会被解析为Comment框架

注意: 若是文件的开头有使用use关键字 use onetwoComment;Comment会被解析为 *onetwoComment*。函数

2.限定名称,包含前缀的名称,例如 $comment = new ArticleComment(); 若是当前的命名空间是Blog,则Comment会被解析为BlogArticleComment。若是使用Comment的代码不包含在任何命名空间中的代码(全局空间中),则Comment会被解析为ArticleCommentui

3.彻底限定名称,或包含了全局前缀操做符的名称,例如 $comment = new ArticleComment(); 在这种状况下,Comment老是被解析为ArticleCommentspa

spl_autoload

接下来让咱们要在含有命名空间的状况下去实现类的自动加载。咱们使用 spl_autoload_register() 函数来实现,这须要你的 PHP 版本号大于 5.12。spl_autoload_register函数的功能就是把传入的函数(参数能够为回调函数函数名称形式)注册到 SPL __autoload 函数队列中,并移除系统默认的 __autoload() 函数。一旦调用 spl_autoload_register() 函数,当调用未定义类时,系统就会按顺序调用注册到 spl_autoload_register() 函数的全部函数,而不是自动调用 __autoload() 函数。

如今, 咱们来建立一个 Linux 类,它使用 os 做为它的命名空间(建议文件名与类名保持一致):

<?php
namespace os; // 命名空间
 
class Linux // 类名
{
    function __construct()
    {
        echo '<h1>' . __CLASS__ . '</h1>';
    }
}

接着,在同一个目录下新建一个 index.php文件,使用 spl_autoload_register 以函数回调的方式实现自动加载:

<?php

spl_autoload_register(function ($class) { // class = os\Linux
 
    /* 限定类名路径映射 */
    $class_map = array(
        // 限定类名 => 文件路径
        'os\\Linux' => './Linux.php',
    );
    /* 根据类名肯定文件路径 */
    $file = $class_map[$class];
    /* 引入相关文件 */
    if (file_exists($file)) {
        include $file;
    }
});
 
new \os\Linux();

这里咱们使用了一个数组去保存类名文件路径的关系,这样当类名传入时,自动加载器就知道该引入哪一个文件去加载这个类了。可是一旦文件多起来的话,映射数组会变得很长,这样的话维护起来会至关麻烦。若是命名能遵照统一的约定,就可让自动加载器自动解析判断类文件所在的路径。接下来要介绍的PSR-4 就是一种被普遍采用的约定方式

PSR-4规范

PSR-4 是关于由文件路径自动载入对应类的相关规范,规范规定了一个彻底限定类名须要具备如下结构:

<顶级命名空间>(<子命名空间>)*<类名>

PSR-4 规范中必需要有一个顶级命名空间,它的意义在于表示某一个特殊的目录文件基目录)。子命名空间表明的是类文件相对于文件基目录的这一段路径(相对路径),类名则与文件名保持一致(注意大小写的区别)。

举个例子:在全限定类名 appviewnewsIndex 中,若是 app 表明 C:Baidu,那么这个类的路径则是 C:BaiduviewnewsIndex.php.咱们就以解析 appviewnewsIndex 为例,编写一个简单的 Demo:

<?php

$class = 'app\view\news\Index';
 
/* 顶级命名空间路径映射 */
$vendor_map = array(
    'app' => 'C:\Baidu',
);
 
/* 解析类名为文件路径 */
$vendor = substr($class, 0, strpos($class, '\\')); // 取出顶级命名空间[app]
$vendor_dir = $vendor_map[$vendor]; // 文件基目录[C:\Baidu]
$rel_path = dirname(substr($class, strlen($vendor))); // 相对路径[/view/news]
$file_name = basename($class) . '.php'; // 文件名[Index.php]
 
/* 输出文件所在路径 */
echo $vendor_dir . $rel_path . DIRECTORY_SEPARATOR . $file_name;

php修炼之路-微信公众号

相关文章
相关标签/搜索