一.命名空间自动加载
(PHP 5 >= 5.3.0, PHP 7)
什么是命名空间?从广义上来说,命名空间是一种封装事物的方法。在很多地方都可以见到这种抽象概念。例如,在操作系统中目录用来将相关文件分组,对于目录中的文件来说,它就扮演了命名空间的角色。具体举个例子,文件 foo.txt 可以同时在目录/home/greg 和 /home/other 中存在,但在同一个目录中不能存在两个 foo.txt 文件。另外,在目录 /home/greg 外访问 foo.txt 文件时,我们必须将目录名以及目录分隔符放在文件名之前得到 /home/greg/foo.txt。这个原理应用到程序设计领域就是命名空间的概念。
在PHP中,命名空间用来解决在编写类库或应用程序时创建可重用的代码如类或函数时碰到的两类问题:
用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突。
为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性。
1.Namespace,限定名称,非限定名称。
PHP 命名空间中的元素使用同样的原理。例如,类名可以通过三种方式引用:
类型 | 实例 | 引用结果 |
---|---|---|
非限定名称,或不包含前缀的类名称, | $a=new foo(); foo::staticmethod(); | 如果当前命名空间是 currentnamespace ,foo 将被解析为 currentnamespace\foo 。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,则 foo 会被解析为foo。 警告:如果命名空间中的函数或常量未定义,则该非限定的函数名称或常量名称会被解析为全局函数名称或常量名称。 |
限定名称,或包含前缀的名称 | $a = new subnamespace\foo(); subnamespace\foo::staticmethod(); | 如果当前的命名空间是 currentnamespace,则foo会被解析为currentnamespace\subnamespace\foo 。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,foo 会被解析为 subnamespace\foo |
完全限定名称,或包含了全局前缀操作符的名称 | $a = new \currentnamespace\foo(); \currentnamespace\foo::staticmethod(); | 在这种情况下,foo 总是被解析为代码中的文字名(literal name)currentnamespace\foo |
(1)限定名称和非限定名称
假如有代码如下:
<?php
namespace AnotherNamespace;
const VAR_STRING = "hello namespace";
class Foo
{
public $var = 1;
public function test()
{
echo $this->var;
}
}
如果使用命名空间:
使用方法 | 结果 |
---|---|
echo AnotherNamespace\VAR_STRING; | PHP Fatal error: Undefined constant 'AnotherNamespace\AnotherNamespace\VAR_STRING' |
echo \AnotherNamespace\VAR_STRING; | “hello namespace” |
可见如果不使用完全限定名称,会自动加载的命名空间是:\AnotherNamespace\AnotherNamespace\Foo
,所以自然是没有找到。
使用了完全限定名称,\AnotherNamespace\Foo
,就会加载这个目录。
(2)备注
Number | 注意事项 |
---|---|
1 | 如果在其他的php文件中声明了命名空间,然后使用use myNameSpace\SubNameSpace\Util; ,那么一定要包含这个文件。否则就会报一个fatel error。 |
2 | 只有类、接口、函数和常量的代码受命名空间的影响,它们是: |
3 | 它必须在其它所有代码之前声明命名空间,除了一个以外:declare关键字。 |
2.sql_autoload_register()
说明
bool spl_autoload_register ([ callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]] )
将函数注册到SPL __autoload函数队列中。如果该队列中的函数尚未激活,则激活它们。
如果在你的程序中已经实现了__autoload()
函数,它必须显式注册到__autoload()
队列中。因为 spl_autoload_register()
函数会将Zend Engine中的__autoload()
函数取代为spl_autoload()
或spl_autoload_call()
。
如果需要多条 autoload 函数,spl_autoload_register() 满足了此类需求。 它实际上创建了 autoload 函数的队列,按定义时的顺序逐个执行。相比之下, __autoload() 只可以定义一次。
参数
参数 | 说明 |
---|---|
autoload_function | 欲注册的自动装载函数。如果没有提供任何参数,则自动注册 autoload 的默认实现函数spl_autoload()。 |
throw | 此参数设置了 autoload_function 无法成功注册时, spl_autoload_register()是否抛出异常。 |
prepend | 如果是 true,spl_autoload_register() 会添加函数到队列之首,而不是队列尾部。 |
返回值 | 成功时返回 TRUE, 或者在失败时返回 FALSE。 |
这函数就是注册一个自动加载函数,当使用未加载的类、常量、函数的时候会先调用该函数。
3.Thinkphp源码的自动加载函数
正如前面所说,如果使用了别的文件里的命名空间却没有引用它,就会导致php报一个fatel的错误。因为它在本文件中根本找不到所使用的命名空间。而如果每一次引用就要写上include,或者require 会使得代码显得很是累赘,所以TP就完美的使用了php 的__autoload函数。
(1)首先看一下源码
/**
* 类库自动加载
* @param string $class 对象类名
* @return void
*/
public static function autoload($class)
{
// 检查是否存在映射
if (isset(self::$_map[$class])) {
include self::$_map[$class];
} elseif (false !== strpos($class, '\\')) {
$name = strstr($class, '\\', true);
if (in_array($name, array('Think', 'Org', 'Behavior', 'Com', 'Vendor')) || is_dir(LIB_PATH . $name)) {
// Library目录下面的命名空间自动定位
$path = LIB_PATH;
} else {
// 检测自定义命名空间 否则就以模块为命名空间
$namespace = C('AUTOLOAD_NAMESPACE');
$path = isset($namespace[$name]) ? dirname($namespace[$name]) . '/' : APP_PATH;
}
$filename = $path . str_replace('\\', '/', $class) . EXT;
if (is_file($filename)) {
// Win环境下面严格区分大小写
if (IS_WIN && false === strpos(str_replace('/', '\\', realpath($filename)), $class . EXT)) {
return;
}
include $filename;
}
} elseif (!C('APP_USE_NAMESPACE')) {
// 自动加载的类库层
foreach (explode(',', C('APP_AUTOLOAD_LAYER')) as $layer) {
if (substr($class, -strlen($layer)) == $layer) {
if (require_cache(MODULE_PATH . $layer . '/' . $class . EXT)) {
return;
}
}
}
// 根据自动加载路径设置进行尝试搜索
foreach (explode(',', C('APP_AUTOLOAD_PATH')) as $path) {
if (import($path . '.' . $class))
// 如果加载类成功则返回
{
return;
}
}
}
1)我们一步一步分析。
首先第一个判断
// 检查是否存在映射
if (isset(self::$_map[$class])) {
include self::$_map[$class];
}
正如代码注释所说,Think::$_map[]
存放的是已经自动加载过的类,并且在数组中存放了类对应的文件路径名。所以如果检测到加载的类已经存在就直接读取数组,类似于缓存的一种方法,可以减少自动加载的耗时。
2)判断有无限定名。
elseif (false !== strpos($class, '\\')) {
2.1)自动加载文件名类名分析。
每一次自动加载的时候是有两大种情况。
编号 | 类型 | 实例 | 分析 |
---|---|---|---|
1 | 没有限定名称,就是纯粹的使用类 | $obj = new classA(); | 处于方便的角度,我们会去加载一下某一个自动加载的目录。 |
2 | 有限定名 | $obj = new \Think\Controller(); | 我们就会去解析命名空间到路径名,然后去加载其类文件。 |
对于第二种情况,这里有一个问题,就是我们的命名空间是类似
namespace MainNameSpace\SubNameSpace;
class Foo{
}
之类的声明,但是这里并不涉及到任何有关这个类文件名的信息,如果这两者之间没有任何关系的话,神仙也不能加载这个类文件,所以TP强制规定了类的文件命名必须是和类名一致,然后加上某一个固定后缀。
如:
也就是说我们遵循了这个命名规则之后,在自动加载的时候就可以直接从命名空间路径解析出来文件的路径。
2.2)现在我们接着看 代码:
$name = strstr($class, '\\', true);
if (in_array($name, array('Think', 'Org', 'Behavior', 'Com', 'Vendor')) || is_dir(LIB_PATH . $name)) {
// Library目录下面的命名空间自动定位
$path = LIB_PATH;
} else {
// 检测自定义命名空间 否则就以模块为命名空间
$namespace = C('AUTOLOAD_NAMESPACE');
$path = isset($namespace[$name]) ? dirname($namespace[$name]) . '/' : APP_PATH;
}
首先我们做的是获取命名空间的路径,使用strstr($class,'\\',true)
可以直接获取’\’前的字符串。
其次我们判断这个顶级命名空间是否是TP自身类库的,如果是的话,那么路径就是TP的Library路径。
如果不是的话,检测是否配置了自动加载的命名空间路径
$namespace = C('AUTOLOAD_NAMESPACE');
$path = isset($namespace[$name]) ? dirname($namespace[$name]). '/' : APP_PATH;
如果配置文件里配置了自动加载的命名空间,如:
'AUTOLOAD_NAMESPACE' => array(
'MY_AUTOLOAD_NAMESPACE' => './NameSpace'
);
如果没有配置自动加载命名空间配置,那么就是使用模块为路径名。./Application
2.3)判断完顶级命名空间之后,就使用顶级命名空间所对应的文件路径名,加上次级路径名,加上后缀。
$filename = $path . str_replace('\\', '/', $class) . EXT;