PHP-FIG,它的网站是:www.php-fig.org。就是这个联盟组织发明和创造了PSR规范,其中自动加载涉及其中两个规范,一个是PSR0,一个是PSR4, PSR0规范已通过时了,官方有提示,如今主要是用PSR4规范定义自动加载标准。php
这个 PSR 描述的是经过文件路径自动载入类的指南;它做为对 PSR-0 的补充;根据这个 指导如何规范存放文件来自动载入;
术语「类」是一个泛称;它包含类,接口,traits 以及其余相似的结构;web
彻底限定类名应该相似以下范例:数组
( app)*
彻底限定类名必须有一个顶级命名空间(Vendor Name);
彻底限定类名能够有多个子命名空间;
彻底限定类名应该有一个终止类名;
下划线在彻底限定类名中是没有特殊含义的;
字母在彻底限定类名中能够是任何大小写的组合;
全部类名必须以大小写敏感的方式引用;
当从彻底限定类名载入文件时:函数
在彻底限定类名中,连续的一个或几个子命名空间构成的命名空间前缀(不包括顶级命名空间的分隔符),至少对应着至少一个基础目录。
在「命名空间前缀」后的连续子命名空间名称对应一个「基础目录」下的子目录,其中的命名 空间分隔符表示目录分隔符。子目录名称必须和子命名空间名大小写匹配;
终止类名对应一个以 .php 结尾的文件。文件名必须和终止类名大小写匹配;
自动载入器的实现不可抛出任何异常,不可引起任何等级的错误;也不该返回值;优化
彻底限定类名 | 命名空间前缀 | 基础路径 | 彻底路径 |
---|---|---|---|
\Acme\Log\Writer\File_Writer | Acme\Log\Write | ./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 |
你们注意看第二列和第四列,命名空间前缀对应基础路径,命名空间前缀以后的子命名空间必须对应代码目录(类名必须是PHP文件)网站
上一节中封装自动加载的方法比较简单,没法自动加载带命名空间的类ui
spl_autoload_register(function ($class) { // 命名空间前缀 $prefix = 'Foo\\Bar\\'; // 命名空间前缀对应的基础目录 $base_dir = __DIR__ . '/src/'; // 检查new的类是否有命名空间前缀 $len = strlen($prefix); if (strncmp($prefix, $class, $len) !== 0) { return; } // 获取去掉命名空间前缀后的类名 $relative_class = substr($class, $len); // 将命名空间的中的分隔符替换为目录分隔符,再加上基础目录和.php后缀,最终拼接成 // 文件路径 $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php'; // 若是文件存在则require if (file_exists($file)) { require $file; } });
可是上面的方法只能适用固定的命名空间前缀,不能通用。this
<?php namespace Example; /** * 下面这个例子实现了一个命名空间前缀对应多个基础目录 * * 如今咱们的目录结构是下面这样: * * /demo/autoload/ * controller/ * DemoController.php # Foo\Bar\DemoController * Admin/ * AdminController.php # Foo\Bar\Admin\AdminController * model/ * DemoModel.php # Foo\Bar\DemoModel * Admin/ * AdminModel.php # Foo\Bar\Admin\AdminModel * * Foo\Bar分别对应基础路径 /demo/autoload/controller 和 /demo/autoload/model */ class Psr4AutoloaderClass { /** * 一个数组,key为命名空间前缀,值为基础路径 * * @var array */ protected $prefixes = array(); /** * 封装自动加载函数 * * @return void */ public function register() { spl_autoload_register(array($this, 'loadClass')); } /** * * 添加一个基础路径对应一个命名空间前缀 * * @param string $prefix 命名空间前缀. * @param string $base_dir 命名空间类文件的基础路径 * @param bool true为往数组头部添加元素,false为往数组尾部添加元素 * @return void */ public function addNamespace($prefix, $base_dir, $prepend = false) { // 去掉左边的\ $prefix = trim($prefix, '\\') . '\\'; // 规范基础路径 $base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/'; // 初始化数组 if (isset($this->prefixes[$prefix]) === false) { $this->prefixes[$prefix] = array(); } // 将命名空间前缀和基础路径存入数组 if ($prepend) { array_unshift($this->prefixes[$prefix], $base_dir); } else { array_push($this->prefixes[$prefix], $base_dir); } } /** * 真正包含文件方法,将给到类名文件包含进来 * * @param string $class 全限定类名(包含命名空间). * @return 成功将返回文件路径,失败则返回false */ public function loadClass($class) { $prefix = $class; //查找$prefix最后一个\的位置,看看最后一个\以前的字符串是否在$this->prefixes中 //若是不存在则继续查询上一个\的位置,获取上一个\以前的字符串是否在$this->prefixes中 //若是循环结束仍是没有找到则返回false while (false !== $pos = strrpos($prefix, '\\')) { $prefix = substr($class, 0, $pos + 1); $relative_class = substr($class, $pos + 1); $mapped_file = $this->loadMappedFile($prefix, $relative_class); if ($mapped_file) { return $mapped_file; } //去掉右边的\ $prefix = rtrim($prefix, '\\'); } return false; } /** * 若是参数中的$prefix在$this->prefixes中存在,那么将循环$this->prefixes[$prefix]里的value(基础路径) * 以后拼接文件路径,若是文件存在将文件包含进来 * * @param string $prefix 命名空间前缀. * @param string $relative_class 真正的类名(不包含命名空间路径的类名). * @return mixed 包含成功返回文件路径,不然返回false */ protected function loadMappedFile($prefix, $relative_class) { // 检查数组中是否有$prefix这个key if (isset($this->prefixes[$prefix]) === false) { return false; } // 将数组中全部的基础路径中的文件包含进来 foreach ($this->prefixes[$prefix] as $base_dir) { // 拼接文件绝对路径 $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php'; // 若是文件存在则包含进来 if ($this->requireFile($file)) { // 返回文件路径 return $file; } } // 没有找到文件 return false; } /** *若是文件存在则包含进来. * * @param string $file 文件路径. * @return bool */ protected function requireFile($file) { if (file_exists($file)) { require $file; return true; } return false; } }