命名空间/生成器/错误与异常

命名空间

命名空间是5.3引入的功能。它是一种自动地为程序员定义的符号添加前缀(即“命名空间”)从而避免命名冲突的机制。

定义命名空间

命名空间机制只对类名(含抽象类、trait)、接口名、函数名和常量名起效果。在一个PHP文件的最顶端(除了declare语句)使用语句namespace 前缀名;,可自动地为文件中所有上述符号添加前缀。

命名空间与文件系统中的PHP代码文件无关。多个PHP代码文件可以共同组成一个命名空间,一个PHP代码文件里也可以定义多个命名空间(但通常不建议这么做),方法包括:

  • 使用多条namespace语句。每条namespace语句将把它以下的符号加上指定前缀
  • 使用大括号语法: ``` <?php namespace MyProject { /* 各种定义 */ } namespace AnotherProject {

    /* 各种定义 */ }

注意:
 
+ 命名空间是大小写敏感的
+  可以以形如MyProject\Sub\Level的形式命名命名空间,从而体现代码各部分的层次关系
+ 任何没放在某个命名空间的符号,都自动地被视为位于全局命名空间(前缀为`\`)。但如果一个PHP文件内已有namespace语句,想把同一文件内的一部分符号放到全局命名空间,则需要手动指定:
```php
<?php
namespace MyProject {
    /* 各种定义,符号位于\MyProject空间中 */
 
}
 
namespace {
    /* 各种定义,符号位于全局命名空间\中 */
 
}

使用/动态地使用命名空间中的符号

视代码位置的不同,有三种使用命名空间中的符号的方式:

  • 以“非限定名称”(或称“无前缀名称”)的形式使用:即使用符号时无须添加任何前缀。适用于“在一个命名空间内使用位于同一个命名空间内的符号”的情况。如果命名空间中的函数或常量未定义,则该非限定的函数名称或常量名称会被解析为全局命名空间内的名称,而类名和接口名并无此机制,此时为避免找不到符号而触发致命错误,必须使用“完全限定名称”
  • 以“限定名称”(或称“包含前缀的名称”)的形式使用:例如,如果在位于命名空间 currentnamespace 的代码中使用符号subnamespace\foo,则会被解析为 currentnamespace\subnamespace\foo
  • 以“完全限定名称”的形式使用:使用符号时总是指明完整前缀(以\开头)。该方式适用于任何情况。

和变量名、函数名、类名一样,PHP还允许动态地使用命名空间名:

$a = '\namespacename\classname';
$obj = new $a; // 相当于new  \namespacename\classname();

通过使用魔术常量__NAMESPACE__可以更方便地构造命名空间字符串。该魔术常量是个表示其所处的命名空间的名字的字符串。关键字namespace也能用于构造完全限定名称,例如,假设当前命名空间为MyProject,则new namespace\sub\ClassA()相当于new \MyProject\sub\ClassA()

为符号创建别名

通过use关键字,可以在编译期为某个以“完全限定名称”形式(但不必也不推荐加上最开头的\)使用的符号起一个简短的别名,从而方便使用:

<?php
namespace foo;
use My\Full\Classname as Another; // 随后能使用符号Another,而不必使用 My\Full\Classname
use My\Full\NSname;  // 等同于 use My\Full\NSname as NSname,该操作一般称为“导入”。随后能使用符号NSname\a表示 My\Full\NSname\a
use A\Namaspace\Name as nn; // 假设   A\Namaspace\Name 是个命名空间名,那么随后能使用 nn\a 来表示  A\Namaspace\Name\a

PHP5.6之前只支持为完全限定名称形式的类名、接口名以及命名空间名创建别名,之后支持函数名和常量名:

<?php
use function My\Full\functionName;  // importing a function (PHP 5.6+)
 
use function My\Full\functionName as func;  // aliasing a function (PHP 5.6+)
 
use const My\Full\CONSTANT;  // importing a constant (PHP 5.6+)
 

由于创建别名是一个编译期过程,use语句必须出现在代码顶层。

注意:使用use关键字并不能触发任何代码的加载,它只起到为符号起别名的作用。PHP文件中唯一加载代码的方式只有使用requireinclude语句。

PSR-4 自动加载 规范

PSR-4自动加载规范约定了命名空间与代码文件在文件系统中的位置的对应关系。这一约定十分简单,操作性很强。中文版见此处

如果使用和编写遵循这一规范的代码,则可以使用现成的PSR-4自动加载器来自动加载代码(见《PHP中的OOP系统》一文中“类的自动加载”一节)。

异常与错误

PHP区分了“错误”和“异常”两个概念。

  • 错误可以是编译期出现,也可以是运行时出现;而异常必定在运行时出现
  • 错误可以是解释器触发的,也可以由程序员使用代码手动触发;异常绝大部分情况下由程序员用代码手动抛出(至少在PHP5中如此)
  • 错误具有级别,任何非致命级别的错误都不会导致程序中止;异常没有级别,任何未经捕获的异常将触发一个致命级别的错误

PHP5中,解释器与PHP内置函数通常使用错误机制,而在PHP7中,大部分错误以Error类的异常实例的形式被抛出,就像大部分编程语言那样。

错误处理

在php.ini文件中通过error_reporting选项,可以控制解释器报告哪些级别(使用常量来表示)的错误,也可以通过在运行时调用函数error_reporting来控制。但注意,有些错误在运行时之前就触发了。

当错误被触发,根据php.ini中的相关配置选项(尤其是displayerrors、logerrors、error_log 这几项),可以进行输出、记录到文件等操作。如果是通过 php-fpm 来调用 PHP 程序,应注意 php-fpm 也有一些错误处理的选项,且会覆盖 php.ini 中的设置。Web Server、XDebug 等程序的配置都会影响到错误发生时的情况。

函数seterrorhandler允许程序员在运行时添加一些回调函数,以用来处理一部分运行时错误(包括那些在错误报告设置中被排除的错误)。通常可以在回调函数中做这些事:

  • 将一些可以处理的错误转换成异常(抛出ErrorException)
  • 调用exit函数立刻中止本次程序的执行(而不是放任程序在已经出错的情况下继续执行)

其它参考信息:

异常机制

PHP5中的异常机制类似于其它常见语言,例如异常类、异常对象、抛出异常、捕获异常等等。但绝大部分内置的PHP函数或操作符从不抛出异常,而是以其它方式(例如触发错误、返回表示错误的值等)报告执行过程已经失败。

异常类

和大部分常见语言一样,异常信息以对象的形式被包装。在PHP5中存在着以下预定义的异常类可供使用:

  • Exception:最基本的异常类型。可以自行继承该类,创建新类型的异常。此页介绍了一些自定义异常类的技巧
  • ErrorException:适合在 seterrorhandler中抛出一个此类异常,以实现“将错误转为异常”的效果
  • SPL Exceptions:标准库中提供了一些异常类型的实现

PHP7中引入的异常类型见此表

throw

使用 throw 异常对象;语句抛出一个异常。

try...catch...finally语句

形式如下:

<?php
try {
   /* 语句 */
 
} catch(异常类型1 变量1) {
    /* 语句 */
 
} catch(异常类型2 变量2) {
    /* 语句 */
 
} finally {
       /* 语句 */
 
}
  • 一个try块可以跟多个catch块以捕获不同类型的异常。从实践上说,捕获Excetption类型的catch块应放在最后
  • finally块出现在catch块之后,或用于取代catch块。无论何种情况,其中的代码总会被执行
  • 任何未经捕获的异常将触发一个致命级别的错误。利用函数setexceptionhandler可以注册异常处理函数作为最后一道防线

生成器函数

生成器函数的概念和作用同JS/Python等常见语言的生成器基本一样。以下说明其在PHP5中的基本用法,其余详见文档

yield关键字

只要在函数(包括lamda函数)体内使用了yield关键字,该函数就是一个生成器函数。

  • 同JS一样该关键字可出现在任何表达式之前,例如$data = yield ($value)。额外地,yield还允许其后的$key => $value形式的表达式
  • yield后可以不跟任何表达式,这将yield一个NULL值
  • 关键字yield from在PHP7中引入,和JS中的yield*类似

返回值

PHP5中生成器函数不能使用return返回一个值,否则会得到编译期错误。PHP7中可以。

生成器函数被调用后,将返回一个Generator类型的实例。该类实现了iterator接口以及其它一些方法。最重要的方法是send, 这一功能使得PHP支持协程(见《第八章:子程序和控制抽象》)。