使用属性
PHP 8 的另一个重要新增功能是增加了一个全新的类和语言结构,即属性(attributes)。简单地说,属性是传统 PHP 注释块的替代品,遵循规定的语法。在编译 PHP 代码时,这些属性会在内部转换为属性类实例。
这项新功能不会立即对您的代码产生影响。不过,随着各种 PHP 开源供应商开始将属性纳入他们的代码,它的影响会越来越大。
属性类解决了我们在本节中讨论的一个潜在的重要性能问题,该问题与滥用传统的 PHP 注释块来提供元指令有关。在深入探讨这个问题以及属性类实例如何解决这个问题之前,我们首先要回顾一下 PHP 注释。
PHP 注释概述
这种语言结构形式的需求是随着普通的 PHP 注释的使用(和滥用!)日益增多而产生的。众所周知,注释有多种形式,包括以下所有形式:
# This is a "bash" shell script style comment
// this can either be inline or on its own line
/* This is the traditional "C" language style */
/**
* This is a PHP "DocBlock"
*/
最后一项,即著名的 PHP DocBlock,现在已经被广泛使用,成为事实上的标准。使用 DocBlocks 并不是一件坏事。恰恰相反,它往往是开发人员交流属性、类和方法信息的唯一方式。问题只在于 PHP 解释过程是如何处理它的。
PHP DocBlock 注意事项
PHP DocBlock 的初衷已经被一些极其重要的 PHP 开源项目所延伸。一个突出的例子就是 Doctrine ObjectRelational Mapper(ORM)项目。尽管不是强制性的,但许多开发人员选择使用嵌套在 PHP DocBlocks 中的注解来定义 ORM 属性。
请看这个部分代码示例,它定义了一个与名为 events
的数据库表交互的类:
namespace Php7\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="events")
* @ORM\Entity("Application\Entity\Events")
*/
class Events {
/**
* @ORM\Column(name="id",type="integer",nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @ORM\Column(name="event_key", type="string",
length=16, nullable=true, options={"fixed"=true})
*/
private $eventKey;
// other code not shown
如果将该类用作 Doctrine ORM 实现的一部分,Doctrine 将打开文件并解析 DocBlocks,搜索 @ORM
注释。尽管解析 DocBlocks 所需的时间和资源令人担忧,但这是定义对象属性与数据库表列之间关系的一种极为方便的方法,深受使用 Doctrine 的开发人员的欢迎。
Doctrine 提供了许多替代这种 ORM 形式的方法,包括可扩展标记语言(XML)和本地 PHP 数组。更多信息,请参见 https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html#annotations-reference 。 |
DocBlocks 滥用带来的隐患
这种滥用 DocBlock 原始用途的做法还存在另一种危险。在 php.ini
文件中有一个名为 opcache.save_comments
的设置。如果禁用该设置,OpCode 缓存引擎(OPcache)就会忽略所有注释,包括 DocBlock。如果该设置生效,那么在 DocBlocks 中使用 @ORM
注释的基于 Doctrine 的应用程序就会失灵。
另一个问题与注释的解析方式有关,或者更确切地说,与不解析注释的方式有关。为了使用注释的内容,PHP 应用程序需要打开文件并逐行解析。就时间和资源利用率而言,这是一个昂贵的过程。
属性类
为了消除隐患,PHP 8 提供了一个新的 属性类(Attribute)。开发人员可以用属性的形式来定义等价内容,而不是使用带有注释的 DocBlocks。使用属性而不是 DocBlocks 的一个好处是,它们是语言的正式组成部分,因此会被标记化,并与其他代码一起编译。
在本章以及 PHP 文档中,属性(attributes)指的是属性(Attribute)类的实例。 目前还没有实际的性能指标来比较包含 DocBlocks 的 PHP 代码的加载与包含属性的代码的加载。 |
虽然这种方法的优势尚未显现,但随着各种开源项目供应商开始将属性纳入其产品,你将开始看到速度和性能的提升。
下面是属性类(Attribute)的定义:
class Attribute {
public const int TARGET_CLASS = 1;
public const int TARGET_FUNCTION = (1 << 1);
public const int TARGET_METHOD = (1 << 2);
public const int TARGET_PROPERTY = (1 << 3);
public const int TARGET_CLASS_CONSTANT = (1 << 4);
public const int TARGET_PARAMETER = (1 << 5);
public const int TARGET_ALL = ((1 << 6) - 1);
public function __construct(
int $flags = self::TARGET_ALL) {}
}
从该类的定义中可以看出,PHP 8 内部使用的该类的主要贡献是一组类常量。这些常量表示位标志,可以使用位运算符进行组合。
属性语法
属性使用借鉴自 Rust 编程语言的特殊语法括起来。方括号内的内容基本上由开发人员决定。下面的代码段就是一个例子:
#[attribute("some text")]
// class, property, method or function (or whatever!)
回到我们的 SingleChar
类示例,下面是使用传统 DocBlocks 时的显示方式:
// /repo/src/Php7/Image/SingleChar.php
namespace Php7\Image;
/**
* Creates a single image, by default black on white
*/
class SingleChar {
/**
* Allocates a color resource
*
* @param array|int $r,
* @param int $g
* @param int $b]
* @return int $color
*/
public function colorAlloc()
{ /* code not shown */ }
现在,我们来看看使用属性做同样的事情:
// /repo/src/Php8/Image/SingleChar.php
namespace Php8\Image;
#[description("Creates a single image")]
class SingleChar {
#[SingleChar\colorAlloc\description("Allocates color")]
#[SingleChar\colorAlloc\param("r","int|array")]
#[SingleChar\colorAlloc\param("g","int")]
#[SingleChar\colorAlloc\param("b","int")]
#[SingleChar\colorAlloc\returns("int")]
public function colorAlloc() { /* code not shown */ }
正如你所看到的,除了提供更强大的编译功能和避免上述隐患外,它还能更有效地利用空间。
方括号内的内容有一些限制;例如,虽然允许使用 另一个例子与联合(union)类型有关(在探索新数据类型部分有解释)。您可以在属性中使用 |
使用反射查看属性
如果您需要从 PHP 8 类中获取属性信息,Reflection
扩展已更新为支持属性。新增的 getAttributes()
方法可返回一个 ReflectionAttribute
实例数组。
在下面的代码块中,Php8\Image\SingleChar::colorAlloc()
方法中的所有属性都显示出来了:
<?php
// /repo/ch01/php8_attrib_reflect.php
define('FONT_FILE', __DIR__ . '/../fonts/FreeSansBold.ttf');
require_once __DIR__ . '/../src/Server/Autoload/Loader.php';
$loader = new \Server\Autoload\Loader();
use Php8\Image\SingleChar;
$char = new SingleChar('A', FONT_FILE);
$reflect = new ReflectionObject($char);
$attribs = $reflect->getAttributes();
echo "Class Attributes\n";
foreach ($attribs as $obj) {
echo "\n" . $obj->getName() . "\n";
echo implode("\t", $obj->getArguments());
}
echo "Method Attributes for colorAlloc()\n";
$reflect = new ReflectionMethod($char, 'colorAlloc');
$attribs = $reflect->getAttributes();
foreach ($attribs as $obj) {
echo "\n" . $obj->getName() . "\n";
echo implode("\t", $obj->getArguments());
}
下面是前面代码片段的输出结果:
<pre>Class Attributes
Php8\Image\SingleChar
Php8\Image\description
Creates a single image, by default black on whiteMethod
Attributes for colorAlloc()
Php8\Image\SingleChar\colorAlloc\description
Allocates a color resource
Php8\Image\SingleChar\colorAlloc\param
r int|array
Php8\Image\SingleChar\colorAlloc\param
g int
Php8\Image\SingleChar\colorAlloc\param
b int
Php8\Image\SingleChar\colorAlloc\returns
int
前面的输出显示,可以使用 Reflection
扩展类检测属性。最后,本代码示例显示了实际方法:
namespace Php8\Image;use Attribute;
use Php8\Image\Strategy\ {PlainText,PlainFill};
#[SingleChar]
#[description("Creates black on white image")]
class SingleChar {
// not all code is shown
#[SingleChar\colorAlloc\description("Allocates color")]
#[SingleChar\colorAlloc\param("r","int|array")]
#[SingleChar\colorAlloc\param("g","int")]
#[SingleChar\colorAlloc\param("b","int")]
#[SingleChar\colorAlloc\returns("int")]
public function colorAlloc(
int|array $r, int $g = 0, int $b = 0) {
if (is_array($r))
[$r, $g, $b] = $r;
return \imagecolorallocate(
$this->image, $r, $g, $b);
}
}
在了解了属性的使用方法后,让我们继续讨论匹配表达式和命名参数的新功能。
有关此新功能的更多信息,请查看以下网页: https://wiki.php.net/rfc/attributes_v2 有关 PHP DocBlocks 的信息可以在这里找到: https://phpdoc.org/ 有关 Doctrine ORM 的更多信息,请查看这里: https://www.doctrine-project.org/projects/orm.html 有关 php.ini 文件设置的文档可以在此处找到: https://www.php.net/manual/en/ini.list.php 在此处阅读有关 PHP Reflection 的信息: https://www.php.net/manual/en/language.attributes 。 Reflection.php 有关 Rust 编程语言的信息可以在本书中找到: https://www.packtpub.com/product/mastering-rustsecond-edition/9781789346572 |