了解命名空间的变化

命名空间 的概念是在 PHP 5.3 中引入的,用来隔离类的层次结构。遗憾的是,最初用于解析命名空间名称的算法有几个缺陷。除了过于复杂之外,内部标记命名空间和类名的方式也不一致,从而导致了意想不到的错误。

在介绍优点和潜在的向后兼容中断之前,我们先来看看命名空间标记化过程是如何变化的。

发现 tokenization 的差异

tokenization 过程是解释过程的重要组成部分,在执行 PHP 代码时进行。在生成字节码的过程中,PHP 程序代码会被 PHP 解析引擎分解成标记(tokens)。

下表总结了命名空间 tokenization 的变化:

Table 1. Table 9.5 – Namespace tokenization differences in PHP 8
命名空间+Class 老的 tokenization 新的 tokenization

JustClass

T_STRING

T_STRING

\AbsoluteClass

T_NS_SEPARATOR
+ T_STRING

T_NAME_FULLY_QUALIFIED

Some\Thing\Class

T_STRING
+ T_NS_SEPARATOR
+ T_STRING

T_NAME_QUALIFIED

<namespace>\Class

T_NAMESPACE
+ T_NS_SEPARATOR
+ T_STRING

T_NAME_RELATIVE

如您所见,PHP 8 命名空间标记化要简单得多,也精简得多。

有关解析器标记的更多信息,请参阅以下参考文档: https://www.php.net/manual/en/tokens

这一变化产生了极为积极的影响。首先,您现在可以使用保留的关键词作为命名空间的一部分。此外,今后当语言中引入新的关键字时,PHP 将不会强制您在应用程序中更改命名空间。新的标记化过程也为使用命名空间属性(Attributes)提供了便利。

首先,让我们来看看在命名空间中使用关键字的情况。

在命名空间中使用保留关键字

PHP 7 中的命名空间标记化过程产生了一系列字符串 (T_STRING) 和反斜杠 (T_NS_SEPARATOR) 标记。这种方法的问题在于,如果其中一个字符串恰好是 PHP 关键字,那么在解析过程中就会立即产生语法错误。而 PHP 8 只产生一个标记,如表 9.5 所示。最终,这意味着您可以在命名空间中放置几乎任何东西,而不必担心保留关键字冲突的问题。

下面的代码示例说明了 PHP 8 与早期版本在命名空间处理方面的不同。在这个例子中,一个 PHP 关键字被用作命名空间的一部分。在 PHP 7 中,由于标记化过程不准确,List 被视为一个关键字,而不是命名空间的一部分:

// /repo/ch09/php8_namespace_reserved.php
namespace List\Works\Only\In\PHP8;
class Test {
    public const TEST = 'TEST';
}
echo Test::TEST . "\n";

这是 PHP 7 的输出:

root@php8_tips_php7 [ /repo/ch09 ]#
php php8_namespace_reserved.php
PHP Parse error: syntax error, unexpected 'List' (T_LIST),
expecting '{' in /repo/ch09/php8_namespace_reserved.php on line
3

在 PHP 8 中,程序代码片段按预期工作,如下所示:

root@php8_tips_php8 [ /repo/ch09 ]#
php php8_namespace_reserved.php
TEST

现在,您已经了解了令牌化过程在 PHP 8 和早期 PHP 版本中的不同之处及其潜在优势,让我们来看看潜在的向后兼容代码中断。

暴露不良的名称空间命名实践

PHP 8 的标记化过程让您不必再担心关键字冲突的问题,但也可能最终暴露出命名空间命名的不良做法。任何含有空白的命名空间现在都被认为是无效的。不过,无论如何,在命名空间中包含空白都是一种不好的做法!

下面的简单代码示例说明了这一原则。在这个示例中,你会发现命名空间包含空白:

// /repo/ch09/php7_namespace_bad.php
namespace Doesnt \Work \In \PHP8;
class Test {
    public const TEST = 'TEST';
}
echo Test::TEST . "\n";

如果我们在 PHP 7 中运行此代码,它可以正常工作。这是 PHP 7 的输出:

root@php8_tips_php7 [ /repo/ch09 ]#
php php7_namespace_bad.php
TEST

然而,在 PHP 8 中,会抛出 ParseError,如下所示:

root@php8_tips_php8 [ /repo/ch09 ]#
php php7_namespace_bad.php
PHP Parse error: syntax error, unexpected fully qualified name
"\Work", expecting "{" in /repo/ch09/php7_namespace_bad.php on
line 3

空格作为分隔符,在标记化过程中被解析器使用。在本代码示例中,PHP 8 假定命名空间为 Doesnt。下一个标记是 \Work,它标志着一个完全合格的类名。然而,在这一点上它并不是预期的,这就是为什么会抛出错误的原因。

至此,我们对 PHP 8 中命名空间处理所做更改的讨论告一段落。现在,您可以更好地在 PHP 8 中创建命名空间名称,不仅能遵循最佳实践,还能利用其独立于关键字命名冲突的优势。

总结

正如你所学到的,PHP 8 对魔法方法的定义更加严格。在本章中,你了解了方法签名的变化,以及如何通过正确使用魔法方法来减少潜在的 bug。你还了解了 Reflection 和 PDO 扩展中方法签名的变化。利用本章所学的知识,您可以在迁移到 PHP 8 时避免潜在的问题。 此外,您还了解了静态方法调用方式的变化,以及新的静态返回类型。

然后,你还学到了如何充分利用私有方法,以及如何对匿名类进行更好的控制。你还学到了一些关于新语法的技巧,以及哪些方法由于语言的变化而过时了。

您还学会了如何正确使用接口和特质来提高代码的使用效率。您还了解了为使 DOM 扩展符合新的 DOM 生活标准而引入的新接口。此外,您还深入了解了如何使用 DateTime 扩展中引入的新方法。

最后,您还学会了如何清理命名空间的使用并编写更紧凑的代码。现在,您已经更好地理解了命名空间标记化过程是如何不准确的,以及在 PHP 8 中又是如何改进的。

下一章将介绍每个开发人员都在努力追求的东西:提高性能的技巧、窍门和技术。