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 Attributes\Domain\Finance;
include 'vendor/autoload.php';
Finance::add();
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__);
}
}
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)
{
}
}
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');
}
}
}
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.
Top comments (0)