介绍PSR-0之前,先来说说命名空间(NameSpace)和Autoloading吧。
NameSpace(命名空间)
namespace是PHP5.3版本加入的新特性,用来解决在编写类库或应用程序时创建可重用的代码如类或函数时碰到的两类问题:
1.用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突。
2.为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性。
PHP 命名空间中的元素使用了类似文件系统的原理。例如,类名可以通过三种方式引用:
1.非限定名称,或不包含前缀的类名称,例如 $a=new foo(); 或 foo::staticmethod();。如果当前命名空间是 currentnamespace,foo 将被解析为 currentnamespace\foo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,则 foo 会被解析为foo。 警告:如果命名空间中的函数或常量未定义,则该非限定的函数名称或常量名称会被解析为全局函数名称或常量名称。详情参见 使用命名空间:后备全局函数名称/常量名称。
2.限定名称,或包含前缀的名称,例如 $a = new subnamespace\foo(); 或 subnamespace\foo::staticmethod();。如果当前的命名空间是 currentnamespace,则 foo 会被解析为 currentnamespace\subnamespace\foo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,foo 会被解析为subnamespace\foo。
3.完全限定名称,或包含了全局前缀操作符的名称,例如, $a = new \currentnamespace\foo(); 或 \currentnamespace\foo::staticmethod();。在这种情况下,foo 总是被解析为代码中的文字名(literal name)currentnamespace\foo。
另外注意访问任意全局类、函数或常量,都可以使用完全限定名称,例如 \strlen() 或 \Exception 或 \INI_ALL。
- <?php
- use My\Full\Classname as Another, My\Full\NSname;
- $obj = new Another; // 实例化一个 My\Full\Classname 对象
- $obj = new \Another; // 实例化一个Another对象
- $obj = new Another\thing; // 实例化一个My\Full\Classname\thing对象
- $obj = new \Another\thing; // 实例化一个Another\thing对象
- $a = \strlen('hi'); // 调用全局函数strlen
- $b = \INI_ALL; // 访问全局常量 INI_ALL
- $c = new \Exception('error'); // 实例化全局类 Exception
- ?>
以上是namespace的简要介绍,对此不了解的同学还是把细读文档吧。在众多新的PHP项目中,namespace已经被普遍使用了。尤其是伴随Composer的流行,它已经是很有必要去了解的特性了。
Autoload(自动加载)
很多开发者写面向对象的应用程序时对每个类的定义建立一个 PHP 源文件。一个很大的烦恼是不得不在每个脚本开头写一个长长的包含(require, include)文件列表(每个类一个文件)。
而Autoload,就是解决这个问题的,通过定义的一个或一系列autoload函数,它会在试图使用尚未被定义的类时自动调用。通过调用autoload函数,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。这个autoload函数可以是默认的__autoload(),如下:
- <?php
- function __autoload($class_name) {
- require_once $class_name . '.php';
- }
- $obj = new MyClass();
- <?php
- function my_autoload($class_name) {
- require_once $class_name . '.php';
- }
- spl_autoload_register("my_autoload");
- $obj = new MyClass();
在这里我们展示了autoload函数最简单的例子,当然通过一些规则的设置,也可以胜任实际环境中许多复杂的情况。
但是如果我们的项目依赖某些其他的项目,本来大家在各自独立的加载规则下能顺利运行,但融合在一起可能就不妙了。那么能否有一种通用的加载规则来解决这个问题?
PSR-0
PSR是由PHP Framework Interoperability Group(PHP通用性框架小组)发布的一系列标准/规范,目前包括了PSR-0~PSR-4共4个,而PSR-0就是其中的自动加载标准(其后的PSR-4称为改进的自动加载的标准,是PSR-0的补充。PSR-0使用更广泛)。https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
下面描述了具体互操作性的自动加载所必须的条件:
强制要求
- 一个完全合格的namespace和class必须符合这样的结构 \<Vendor Name>
<Namespace> *<Class Name> - 每个namespace必须有一个顶层的namespace("Vendor Name"提供者名字)
- 每个namespace可以有多个子namespace
- 当从文件系统中加载时,每个namespace的分隔符要转换成 DIRECTORY_SEPARATOR(操作系统路径分隔符)
- 在CLASS NAME(类名)中,每个下划线(_)符号要转换成DIRECTORY_SEPARATOR。在namespace中,下划线(_)符号是没有(特殊)意义的。
- 当从文件系统中载入时,合格的namespace和class一定是以 .php 结尾的
- verdor name,namespaces,class名可以由大小写字母组合而成(大小写敏感的)
例子
- \Doctrine\Common\IsolatedClassLoader => /path/to/project/lib/vendor/Doctrine/Common/IsolatedClassLoader.php
- \Symfony\Core\Request => /path/to/project/lib/vendor/Symfony/Core/Request.php
- \Zend\Acl => /path/to/project/lib/vendor/Zend/Acl.php
- \Zend\Mail\Message => /path/to/project/lib/vendor/Zend/Mail/Message.php
NameSpace和Class Name中的下划线
- \namespace\package\Class_Name => /path/to/project/lib/vendor/namespace/package/Class/Name.php
- \namespace\package_name\Class_Name => /path/to/project/lib/vendor/namespace/package_name/Class/Name.php
实现的范例
下面是一个按照如上标准进行自动加载的简单范例:
- <?php
- function autoload($className)
- {
- //这里的$className一般是用namespace的方式来引用的,文章开头已有介绍
- //去除$className左边的'\' 这是PHP5.3的一个bug,详见https://bugs.php.net/50731
- $className = ltrim($className, '\\');
- $fileName = '';
- $namespace = '';
- //找到最后一个namespace分隔符的位置
- if ($lastNsPos = strrpos($className, '\\')) {
- $namespace = substr($className, 0, $lastNsPos);
- $className = substr($className, $lastNsPos + 1);
- $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
- }
- $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
- require $fileName;
- }
-
SplClassLoader 的实现
看到这里你可能还有疑问,比如psr-0中给出的例子
\Symfony\Core\Request => /path/to/project/lib/vendor/Symfony/Core/Request.php
在=>号之前是我们要去加载的类,而后面的部分是这个文件在文件系统中的路径(我们实际加载的是这个)。在加载Symfony\Core\Request时,并没有同时给出实际路径来,那么是如何得到这个映射的呢?在PSR-0标准中并没有对/path/to/project/lib路径的强制要求,而是可以有自己的实现。一般有两种方式,一种就是使用php配置中的include_path(上面给出的例子有使用这个方式来做),第二种就是自己去注册这种映射,可以选用其中一种,或者两种都兼容。下面就来看看在Symfony中是怎么做的?
- <?php
- require_once '/path/to/src/Symfony/Component/ClassLoader/ClassLoader.php';
- use Symfony\Component\ClassLoader\ClassLoader;
- $loader = new ClassLoader();
- // to enable searching the include path (eg. for PEAR packages)
- $loader->setUseIncludePath(true);
- // ... register namespaces and prefixes here - see below
- $loader->register();
- // register a single namespaces
- $loader->addPrefix('Symfony', __DIR__.'/vendor/symfony/symfony/src');
完。