Php features: Attributes

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) { /* ... */ }
}
Enter fullscreen mode
Exit fullscreen mode

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) { /* ... */ }
}
Enter fullscreen mode
Exit fullscreen mode

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": {}
}
Enter fullscreen mode
Exit fullscreen mode
<?php // index.php use Attributes\Domain\Finance; include 'vendor/autoload.php'; Finance::add(); 
Enter fullscreen mode
Exit fullscreen mode

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

<?php // src\Domain\Finance.php namespace Attributes\Domain; use Attributes\Attributes\Deprecated;
use Attributes\Utils\Logger; class Finance
{ #[Deprecated('use adds method')] public static function add($amount) { Logger::add(__CLASS__,__FUNCTION__); }
}
Enter fullscreen mode
Exit fullscreen mode

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 // src\Attributes\Attributes.php namespace Attributes\Attributes; use Attribute; #[Attribute]
class Deprecated
{ public function __construct(public readonly string $message) { }
}
Enter fullscreen mode
Exit fullscreen mode

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 Attributes\Utils; use Attributes\Attributes\Deprecated;
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'); } }
}
Enter fullscreen mode
Exit fullscreen mode

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.


Discover more from Coursity

Subscribe to get the latest posts sent to your email.

Leave a Comment

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

Discover more from Coursity

Subscribe now to keep reading and get access to the full archive.

Continue reading

Scroll to Top
If there are enough subscribers to the list, we will do our best to expedite the selected dc location plans.