Php features: Attributes

RMAG news

I’m not sure how big of a series I want to make this. But here we go.
The target of the series is to show php features using as few libraries as possible.

Theory

Attributes are the php build-in way to add metadata to classes and methods.

class PostsController
{
#[Route(“/api/posts/{id}”, methods: [“GET”])]
public function get($id) { /* … */ }
}

Before php 8.0 we were using docstrings to do the same thing.

class PostsController
{
/**
* @Route(“/api/posts/{id}”, methods={“GET”})
*/

public function get($id) { /* … */ }
}

The problems with the docstrings where plenty:

To make the docstrings work you needed a library
The IDEs couldn’t autocomplete and make suggestions for the docstring as easy as attributes, because it is parsed as text and not as code.

As you can see from the example frameworks can use it to add routes. But php also has build-in attributes, like the php 8.3 Override attribute.

Code

I add the composer.json, and index.php file so you can recreate the tutorial.

{
“name”: “attributes/tutorial”,
“type”: “project”,
“autoload”: {
“psr-4”: {
“Attributes\”: “src/”
}
},
“authors”: [],
“require”: {}
}
<?php

// index.php

use AttributesDomainFinance;

include ‘vendor/autoload.php’;

Finance::add();

Now that the bootstrap is done, lets go to the interesting part.

<?php

// srcDomainFinance.php

namespace AttributesDomain;

use AttributesAttributesDeprecated;
use AttributesUtilsLogger;

class Finance
{
#[Deprecated(‘use adds method’)]
public static function add($amount)
{
Logger::add(__CLASS__,__FUNCTION__);
}
}

I’m a positive person, so I only need an add method for finances.

The two things that are attribute specific are the attribute, Deprecated, and the Logger::add method.

<?php

// srcAttributesAttributes.php

namespace AttributesAttributes;

use Attribute;

#[Attribute]
class Deprecated
{
public function __construct(public readonly string $message)
{
}
}

The attribute class can be empty as it is metadata. The use of the attribute is handled by one or more other classes.
You have to add the Attribute class to identify your class as an attribute.

In this example a string is added to make the class more usable.

<?php

// src/Utils/Logger.php

namespace AttributesUtils;

use AttributesAttributesDeprecated;
use ReflectionClass;

class Logger
{
public static function add(string $class, string $method) {
$reflectionClass = new ReflectionClass($class);
$reflectionFunction = $reflectionClass->getMethod($method);
$deprecated = $reflectionFunction->getAttributes(Deprecated::class);

foreach ($deprecated as $depre) {
$instance = $depre->newInstance();
error_log($instance->message . n, 3, __DIR__ . ‘../../../deprications.log’);
}
}
}

This class is that gives the attribute value. If a class like this doesn’t exist you are better of writing a comment.
Most of the time classes like this one are added to an event in the lifecycle of the framework or when there is a cache warmup.

The line that gets the Deprecated attribute is $deprecated = $reflectionFunction->getAttributes(Deprecated::class);. This is possible because the reflection getMethod method returns an instance of the ReflectionMethod class. Other classes ReflectionClass, ReflectionProperty, ReflectionFunctionAbstract (which is the parent of ReflectionMethod), ReflectionParameter and ReflectionClassConstant also have this method, so you can get attributes from most of parts of a class.

In the foreach loop you see the line $instance = $depre->newInstance();. This allows you to use the attribute class with the parameters you added in the code.

Conclusion

Attributes on their own don’t add much to your code. But combined with classes that process the attributes they become a powerful part of your code. they allow you to have fewer tightly coupled dependencies and make the documentation of your code more functional.

Like all features of a language, use it don’t misuse it.

Leave a Reply

Your email address will not be published. Required fields are marked *