Php features: Attributes
Tags: php
Published: 2024-04-21 08:38:36

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](https://www.php.net/manual/en/language.attributes.overview.php) are the php build-in way to add metadata to classes and methods.

```php
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.

```php

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](https://www.php.net/manual/en/class.override.php) attribute.

## Code

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

```json
{
    "name": "attributes/tutorial",
    "type": "project",
    "autoload": {
        "psr-4": {
            "Attributes\\": "src/"
        }
    },
    "authors": [],
    "require": {}
}
```

```php
<?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
<?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
<?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
<?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.