PHP allows to specify doc comments on declarations in code which can be retrieved using the Reflection API. However, doc comments are just strings that provides unstructured metadata information. There are PHP libraries that allows 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 to specify 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, seperated 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 has getAttributes
method that returns an array of ReflectionAttribute
instances.
User.php
<?php
#[Entity]
#[Table('user', 'test_db')]
class User {}
main.php
<?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 to create a class that defines attribute. In simple case we only need to create an empty class which declared with #[Attribute]
attribute.
Table.php
<?php
#[Attribute]
class Table {}
Then we can apply this attribute on declaration:
User.php
<?php
#[Table]
class User {}
When attribute is declared with an actual class we can retrieve an instance of the attribute using the Reflection API.
main.php
<?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.
Table.php
<?php
namespace App\Orm\Attributes;
use Attribute;
#[Attribute]
class Table {}
User.php
<?php
namespace App\Entity;
use App\Orm\Attributes\Table;
#[Table]
class User {}
Attribute class constructor
Attribute class can have constructor.
Table.php
<?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 attribute declaration are passed to its constructor.
User.php
<?php
#[Table('user', 'test_db')]
class User {}
We can use the Reflection API to retrieve an instance of the attribute and its properties:
main.php
<?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, attribute can be applied on any declaration that accepts attributes. We can define targets of the attribute. This means that attribute can be applied on 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 newInstance
method using the Reflection API.
User.php
<?php
class User
{
#[Table]
private string $username;
}
main.php
<?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, attribute can be applied only one time on same declaration. We can use Attribute::IS_REPEATABLE
flag that allows to apply attribute on declaration multiple times.
UniqueConstraint.php
<?php
#[Attribute(Attribute::TARGET_CLASS|Attribute::IS_REPEATABLE)]
class UniqueConstraint
{
public array $columns;
public function __construct(array $columns)
{
$this->columns = $columns;
}
}
User.php
<?php
#[UniqueConstraint(['company_id', 'username'])]
#[UniqueConstraint(['company_id', 'email'])]
class User {}
Note that this flag is validated only when calling newInstance
method.
main.php
<?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