DEV Community

david duymelinck
david duymelinck

Posted on

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.

Top comments (0)