Working with attributes
Another significant addition to PHP 8 is the addition of a brand-new class and language construct known as attributes. Simply put, attributes are replacements for traditional PHP comment blocks that follow a prescribed syntax. When the PHP code is compiled, these attributes are converted internally into Attribute
class instances.
This new feature is not going to have an immediate impact on your code today. It will start to become more and more influential, however, as the various PHP open source vendors start to incorporate attributes into their code.
The Attribute
class addresses a potentially significant performance issue we discuss in this section, pertaining to an abuse of the traditional PHP comment block to provide meta-instructions. Before we dive into that issue and how Attribute
class instances address the problem, we first must review PHP comments.
Overview of PHP comments
The need for this form of language construct arose with the increasing use (and abuse!) of the plain workhorse PHP comment. As you are aware, comments come in many forms, including all of the following:
# 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" */
The last item, the famous PHP DocBlock
, is now so widely used it's become a de facto standard. The use of DocBlocks is not a bad thing. On the contrary—it's often the only way a developer is able to communicate information about properties, classes, and methods. The problem only arises in how it is treated by the PHP interpretation process.
PHP DocBlock considerations
The original intent of the PHP DocBlock has been stretched by a number of extremely important PHP open-source projects. One striking example is the Doctrine Object-Relational Mapper (ORM) project. Although not mandatory, many developers choose to define ORM properties using annotations nested inside PHP DocBlocks.
Have a look at this partial code example, which defines a class interacting with a database table called 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
If you were to use this class as part of a Doctrine ORM implementation, Doctrine would open the file and parse the DocBlocks, searching for @ORM
annotations. Despite some concerns over the time and resources needed to parse DocBlocks, this is an extremely convenient way to define the relationship between object properties and database table columns, and is popular with developers who use Doctrine.
Tip
Doctrine offers a number of alternatives to this form of ORM, including Extensible Markup Language (XML) and native PHP arrays. For more information, see https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html#annotations-reference.
Hidden dangers associated with the misuse of DocBlocks
There is yet another danger associated with this abuse of the original purpose of a DocBlock. In the php.ini
file, there is a setting named opcache.save_comments
. If disabled, this would cause the OpCode cache engine (OPcache) to ignore all comments, including DocBlocks. If this setting is in effect, a Doctrine-based application using @ORM
annotations in DocBlocks would malfunction.
Another problem has to do with how comments are parsed—or, more to the point, how comments are not parsed. In order to use the contents of a comment, the PHP application needs to open the file and parse it line by line. This is an expensive process in terms of time and resource utilization.
The Attribute class
In order to address hidden dangers, in PHP 8 a new Attribute
class is provided. Instead of using DocBlocks with annotations, developers can define the equivalent in the form of attributes. An advantage of using attributes rather than DocBlocks is that they are a formal part of the language and are thus tokenized and compiled along with the rest of your code.
Important note
In this chapter, and also in the PHP documentation, reference to attributes refers to instances of the Attribute
class.
Actual performance metrics are not yet available that compare the loading of PHP code containing DocBlocks with the loading of code that contains attributes.
Although the benefits of this approach are not yet seen, as the various open source project vendors start to incorporate attributes into their offerings you will start to see an improvement in speed and performance.
Here is the Attribute
class definition:
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) {} }
As you can see from the class definition, the main contribution from this class, used internally by PHP 8, is a set of class constants. The constants represent bit flags that can be combined using bitwise operators.
Attribute syntax
Attributes are enclosed using a special syntax borrowed from the Rust programming language. What goes inside the square brackets is pretty much left to the developer. An example can be seen in the following snippet:
#[attribute("some text")] // class, property, method or function (or whatever!)
Returning to our example of the SingleChar
class, here's how it might appear using traditional 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 */ }
Now, have a look at the same thing using attributes:
// /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 */ }
As you can see, in addition to providing a more robust compilation and avoiding the hidden dangers mentioned, it's also more efficient in terms of space usage.
Tip
What goes inside the square brackets does have some restrictions; for example, although #[returns("int")]
is allowed, this is not: #[return("int")
. The reason for this is because return
is a keyword.
Another example has to do with union types (explained in the Exploring new data types section). You can use #[param("int|array test")]
in an attribute, but this is not allowed: #[int|array("test")]
. Another peculiarity is that class-level attributes must be placed immediately before the class
keyword and after any use
statements.
Viewing attributes using Reflection
If you need to get attribute information from a PHP 8 class, the Reflection
extension has been updated to include attribute support. A new getAttributes()
method that returns an array of ReflectionAttribute
instances has been added.
In the following block of code, all the attributes from the Php8\Image\SingleChar::colorAlloc()
method are revealed:
<?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()); }
Here is the output from the code shown in the preceding snippet:
<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
The preceding output shows that attributes can be detected using the Reflection
extension classes. Finally, the actual method is shown in this code example:
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); } }
Now that you have an idea of how attributes can be used, let's continue our coverage of new features by discussing match
expressions, followed by named arguments.
Tip
For more information on this new feature, have a look at the following web page:
https://wiki.php.net/rfc/attributes_v2
Also, see this update:
https://wiki.php.net/rfc/shorter_attribute_syntax_change
Information on PHP DocBlocks can be found here:
https://phpdoc.org/
For more information about Doctrine ORM, have a look here:
https://www.doctrine-project.org/projects/orm.html
Documentation on php.ini
file settings can be found here:
https://www.php.net/manual/en/ini.list.php
Read about PHP Reflection here:
https://www.php.net/manual/en/language.attributes.reflection.php
Information about the Rust programming language can be found in this book: https://www.packtpub.com/product/mastering-rust-second-edition/9781789346572