Custom Attributes
Create custom PHP attributes for DTOs.
Introduction
Section titled “Introduction”Custom attributes extend DTO functionality:
- ✅ Metadata - Add metadata to properties
- ✅ Behavior - Modify DTO behavior
- ✅ Validation - Custom validation logic
- ✅ Transformation - Transform data
Creating Custom Attributes
Section titled “Creating Custom Attributes”Basic Attribute
Section titled “Basic Attribute”use Attribute;
#[Attribute(Attribute::TARGET_PROPERTY)]class Description{ public function __construct( public readonly string $text, ) {}}Using the Attribute
Section titled “Using the Attribute”class UserDTO extends SimpleDTO{ public function __construct( #[Description('User full name')] public readonly string $name,
#[Description('User email address')] public readonly string $email, ) {}}Advanced Examples
Section titled “Advanced Examples”Metadata Attribute
Section titled “Metadata Attribute”#[Attribute(Attribute::TARGET_PROPERTY)]class ApiField{ public function __construct( public readonly string $name, public readonly ?string $description = null, public readonly bool $required = false, public readonly ?string $example = null, ) {}}
// Usageclass UserDTO extends SimpleDTO{ public function __construct( #[ApiField('user_name', 'Full name of the user', required: true, example: 'John Doe')] public readonly string $name, ) {}}Transformation Attribute
Section titled “Transformation Attribute”#[Attribute(Attribute::TARGET_PROPERTY)]class Transform{ public function __construct( public readonly string $transformer, ) {}}
// Usageclass UserDTO extends SimpleDTO{ public function __construct( #[Transform('trim')] public readonly string $name,
#[Transform('strtolower')] public readonly string $email, ) {}}Conditional Attribute
Section titled “Conditional Attribute”#[Attribute(Attribute::TARGET_PROPERTY)]class ShowIf{ public function __construct( public readonly string $condition, ) {}}
// Usageclass UserDTO extends SimpleDTO{ public function __construct( public readonly string $name,
#[ShowIf('isAdmin')] public readonly ?string $adminNotes = null, ) {}}Real-World Examples
Section titled “Real-World Examples”Database Column Mapping
Section titled “Database Column Mapping”#[Attribute(Attribute::TARGET_PROPERTY)]class Column{ public function __construct( public readonly string $name, public readonly ?string $type = null, public readonly bool $nullable = false, ) {}}
// Usageclass UserDTO extends SimpleDTO{ public function __construct( #[Column('user_name', type: 'varchar', nullable: false)] public readonly string $name,
#[Column('user_email', type: 'varchar', nullable: false)] public readonly string $email, ) {}}API Documentation
Section titled “API Documentation”#[Attribute(Attribute::TARGET_PROPERTY)]class ApiProperty{ public function __construct( public readonly string $description, public readonly ?string $example = null, public readonly ?array $enum = null, public readonly ?string $format = null, ) {}}
// Usageclass ProductDTO extends SimpleDTO{ public function __construct( #[ApiProperty('Product name', example: 'iPhone 15')] public readonly string $name,
#[ApiProperty('Product price in cents', example: 99900, format: 'int32')] public readonly int $price,
#[ApiProperty('Product status', enum: ['active', 'inactive', 'draft'])] public readonly string $status, ) {}}Audit Trail
Section titled “Audit Trail”#[Attribute(Attribute::TARGET_PROPERTY)]class Auditable{ public function __construct( public readonly bool $logChanges = true, public readonly ?string $label = null, ) {}}
// Usageclass UserDTO extends SimpleDTO{ public function __construct( #[Auditable(label: 'User Name')] public readonly string $name,
#[Auditable(label: 'Email Address')] public readonly string $email,
#[Auditable(logChanges: false)] public readonly ?string $avatar = null, ) {}}Searchable Fields
Section titled “Searchable Fields”#[Attribute(Attribute::TARGET_PROPERTY)]class Searchable{ public function __construct( public readonly int $weight = 1, public readonly bool $exact = false, ) {}}
// Usageclass ProductDTO extends SimpleDTO{ public function __construct( #[Searchable(weight: 10, exact: false)] public readonly string $name,
#[Searchable(weight: 5)] public readonly string $description,
#[Searchable(weight: 3, exact: true)] public readonly string $sku, ) {}}Encryption
Section titled “Encryption”#[Attribute(Attribute::TARGET_PROPERTY)]class Encrypted{ public function __construct( public readonly string $algorithm = 'AES-256-CBC', ) {}}
// Usageclass UserDTO extends SimpleDTO{ public function __construct( public readonly string $name,
#[Encrypted] public readonly string $ssn,
#[Encrypted(algorithm: 'AES-128-CBC')] public readonly string $creditCard, ) {}}Reading Attributes
Section titled “Reading Attributes”Using Reflection
Section titled “Using Reflection”use ReflectionClass;use ReflectionProperty;
$reflection = new ReflectionClass(UserDTO::class);
foreach ($reflection->getProperties() as $property) { $attributes = $property->getAttributes(Description::class);
foreach ($attributes as $attribute) { $instance = $attribute->newInstance(); echo "{$property->getName()}: {$instance->text}\n"; }}Helper Method
Section titled “Helper Method”class AttributeReader{ public static function getPropertyAttributes( string $class, string $property, string $attributeClass ): array { $reflection = new ReflectionClass($class); $prop = $reflection->getProperty($property); $attributes = $prop->getAttributes($attributeClass);
return array_map( fn($attr) => $attr->newInstance(), $attributes ); }}
// Usage$descriptions = AttributeReader::getPropertyAttributes( UserDTO::class, 'name', Description::class);Combining Attributes
Section titled “Combining Attributes”Multiple Attributes
Section titled “Multiple Attributes”class UserDTO extends SimpleDTO{ public function __construct( #[Required] #[Min(3)] #[Max(50)] #[Description('User full name')] #[ApiField('user_name', required: true)] #[Searchable(weight: 10)] public readonly string $name, ) {}}Attribute Groups
Section titled “Attribute Groups”#[Attribute(Attribute::TARGET_PROPERTY)]class UserField{ public function __construct( public readonly string $description, public readonly bool $required = false, public readonly bool $searchable = false, public readonly int $searchWeight = 1, ) {}}
// Usageclass UserDTO extends SimpleDTO{ public function __construct( #[UserField('User full name', required: true, searchable: true, searchWeight: 10)] public readonly string $name, ) {}}Best Practices
Section titled “Best Practices”Clear Naming
Section titled “Clear Naming”// ✅ Good - clear name#[Attribute(Attribute::TARGET_PROPERTY)]class ApiField
// ❌ Bad - vague name#[Attribute(Attribute::TARGET_PROPERTY)]class FieldReadonly Properties
Section titled “Readonly Properties”// ✅ Good - readonlypublic function __construct( public readonly string $name,) {}
// ❌ Bad - mutablepublic function __construct( public string $name,) {}Type Hints
Section titled “Type Hints”// ✅ Good - type hintspublic function __construct( public readonly string $name, public readonly int $weight,) {}
// ❌ Bad - no type hintspublic function __construct( public readonly $name, public readonly $weight,) {}See Also
Section titled “See Also”- Custom Casts - Custom type casts
- Custom Validation - Custom validation rules
- Attributes Overview - Built-in attributes