PHP allows specifying doc comments on declarations in code, which can be retrieved using the Reflection API. However, doc comments are just strings that provide unstructured metadata information. There are PHP libraries that allow to specify structured metadata information on declarations via annotations, which implemented using doc comments.
<?php
class User
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
private int $id;
/**
* @Column(type="string", length=20)
*/
private string $username;
}
Since PHP 8.0, we can use attributes, which allows specifying structured metadata information on declarations in code.
<?php
class User
{
#[Id]
#[GeneratedValue]
#[Column('integer')]
private int $id;
#[Column('string', length: 20)]
private string $username;
}
Attribute syntax
Attribute declaration starts with #[
and ends with ]
.
<?php
#[Entity]
class User {}
Inside #[
]
square brackets, we can specify one or many attributes, separated by comma.
<?php
#[Entity, Table]
class User {}
Also, each attribute can be enclosed in its own #[
]
square brackets.
<?php
#[Entity]
#[Table]
class User {}
Attributes can have zero or more arguments, which are enclosed in (
)
parenthesis.
<?php
#[Table('user', 'test_db')]
class User {}
Instead of positional arguments, we can use named arguments.
<?php
#[Table(name: 'user', scheme: 'test_db')]
class User {}
Arguments can be scalar types, arrays, constants or mathematical expressions.
<?php
define('MESSAGE', 'Hello');
#[ScalarTypeAttribute('string', 2, 2.5, true)]
#[ArrayAttribute(['first', 'second'])]
#[ExpressionAttribute(1 + 2)]
#[ConstantAttribute(MESSAGE)]
class User {}
Attributes can be specified before and after doc comments.
<?php
#[Entity]
/**
* @Class
*/
class User {}
<?php
/**
* @Class
*/
#[Entity]
class User {}
Apply attributes
Attributes can be applied on various declarations in the code:
- Classes
- Class constants
- Class properties
- Class methods
- Method parameters
<?php
#[ClassAttribute]
class User
{
#[ConstantAttribute]
public const STATUS_ACTIVE = 'A';
#[PropertyAttribute]
private string $username;
#[MethodAttribute]
public function setUsername(#[MethodParameterAttribute] string $username): void {}
}
- Interfaces
<?php
#[InterfaceAttribute]
interface UserInterface {}
- Traits
<?php
#[TraitAttribute]
trait UserTrait {}
- Functions
- Function parameters
- Closures
- Short closures
- Anonymous classes
<?php
#[FunctionAttribute]
function saySomething(#[FunctionParameterAttribute] string $message) {}
$fn1 = #[ClosureAttribute] function() {
return 'Hello';
};
$fn2 = #[ShortClosureAttribute] fn() => 'Hello';
$object = new #[AnonymousClassAttribute] class {};
Reading attributes
Attributes can be retrieved using the Reflection API. Reflection classes have the getAttributes
method that returns an array of ReflectionAttribute
instances.
<?php
#[Entity]
#[Table('user', 'test_db')]
class User {}
<?php
require_once 'User.php';
$class = new ReflectionClass(User::class);
foreach ($class->getAttributes() as $attribute) {
$name = $attribute->getName();
$args = $attribute->getArguments();
echo $name.': '.implode(', ', $args).PHP_EOL;
}
Example will output:
Entity:
Table: user, test_db
Attribute classes
Not required, but recommended creating a class that defines attribute. In simple case, we only need to create an empty class which declared with #[Attribute]
attribute.
<?php
#[Attribute]
class Table {}
Then we can apply this attribute on declaration:
<?php
#[Table]
class User {}
When an attribute is declared with an actual class, we can retrieve an instance of the attribute using the Reflection API.
<?php
require_once 'User.php';
require_once 'Table.php';
$class = new ReflectionClass(User::class);
$attribute = $class->getAttributes()[0];
$instance = $attribute->newInstance();
print_r($instance); // Table Object ()
Attribute classes can use namespace.
<?php
namespace App\Orm\Attributes;
use Attribute;
#[Attribute]
class Table {}
<?php
namespace App\Entity;
use App\Orm\Attributes\Table;
#[Table]
class User {}
Attribute class constructor
Attribute class can have constructor.
<?php
#[Attribute]
class Table
{
public string $name;
public string $scheme;
public function __construct(string $name, string $sheme)
{
$this->name = $name;
$this->scheme = $sheme;
}
}
The arguments defined in the attribute declaration are passed to its constructor.
<?php
#[Table('user', 'test_db')]
class User {}
We can use the Reflection API to retrieve an instance of the attribute and its properties:
<?php
require_once 'User.php';
require_once 'Table.php';
$class = new ReflectionClass(User::class);
$attribute = $class->getAttributes()[0];
$instance = $attribute->newInstance();
echo $instance->name; // user
echo $instance->scheme; // test_db
Targets of the attribute
By default, the attribute can be applied to any declaration that accepts attributes. We can define targets of the attribute. This means that attribute can be applied to specific type of declarations.
The following attribute can be applied only on class declarations:
<?php
#[Attribute(Attribute::TARGET_CLASS)]
class Table {}
We can use bitmask to specify more than one targets of the attribute.
<?php
#[Attribute(Attribute::TARGET_FUNCTION|Attribute::TARGET_METHOD)]
class Invocable {}
The following targets can be used:
Attribute::TARGET_CLASS
Attribute::TARGET_FUNCTION
Attribute::TARGET_METHOD
Attribute::TARGET_PROPERTY
Attribute::TARGET_CLASS_CONSTANT
Attribute::TARGET_PARAMETER
Attribute::TARGET_ALL
Note that targets are validated only when calling the newInstance
method using the Reflection API.
<?php
class User
{
#[Table]
private string $username;
}
<?php
require_once 'User.php';
require_once 'Table.php';
$property = new ReflectionProperty(User::class, 'username');
$attribute = $property->getAttributes()[0];
$instance = $attribute->newInstance();
Fatal error: Uncaught Error: Attribute "Table" cannot target property (allowed targets: class) in main.php:9
Repeatable attribute
By default, the attribute can be applied only one time on the same declaration. We can use Attribute::IS_REPEATABLE
flag that allows to apply attribute on declaration multiple times.
<?php
#[Attribute(Attribute::TARGET_CLASS|Attribute::IS_REPEATABLE)]
class UniqueConstraint
{
public array $columns;
public function __construct(array $columns)
{
$this->columns = $columns;
}
}
<?php
#[UniqueConstraint(['company_id', 'username'])]
#[UniqueConstraint(['company_id', 'email'])]
class User {}
Note that this flag is validated only when calling the newInstance
method.
<?php
require_once 'User.php';
require_once 'UniqueConstraint.php';
$class = new ReflectionClass(User::class);
foreach ($class->getAttributes() as $attribute) {
$instance = $attribute->newInstance();
$name = $attribute->getName();
echo $name.': '.implode(', ', $instance->columns).PHP_EOL;
}
Example will output:
UniqueConstraint: company_id, username
UniqueConstraint: company_id, email
Leave a Comment
Cancel reply