Property Attributes - Grazulex/laravel-arc GitHub Wiki
🔧 Property Attributes
The Property
attribute is the heart of Laravel Arc, providing a unified, clean syntax for defining all types of DTO properties. This guide covers everything you need to know about using Property attributes effectively.
🎯 Overview
The Property
attribute replaces the old separate attribute classes (DateProperty, EnumProperty, NestedProperty) with a single, consistent interface:
use Grazulex\Arc\Attributes\Property;
#[Property(type: 'string', required: true, validation: 'max:255')]
public string $name;
📋 Attribute Parameters
Core Parameters
Parameter | Type | Default | Description |
---|---|---|---|
type |
string |
Required | Property type (string , int , float , bool , array , enum , date , nested , collection ) |
required |
bool |
true |
Whether the property is required during DTO creation |
default |
mixed |
null |
Default value if not provided |
validation |
string |
null |
Laravel validation rules |
class |
string |
null |
Target class for complex types (enum , nested , collection ) |
Advanced Parameters
Parameter | Type | Default | Description |
---|---|---|---|
transform |
array |
[] |
Array of transformer classes to apply before casting |
format |
string |
null |
Date format for date properties |
timezone |
string |
null |
Timezone for date properties |
immutable |
bool |
false |
Use CarbonImmutable for date properties |
📝 Basic Types
String Properties
#[Property(type: 'string', required: true, validation: 'max:255')]
public string $name;
#[Property(type: 'string', required: false, default: 'guest')]
public string $role;
#[Property(type: 'string', required: true, validation: 'email')]
public string $email;
#[Property(type: 'string', required: false, validation: 'url')]
public ?string $website;
Numeric Properties
#[Property(type: 'int', required: true, validation: 'min:0|max:150')]
public int $age;
#[Property(type: 'float', required: true, validation: 'min:0')]
public float $price;
#[Property(type: 'int', required: false, default: 0)]
public int $count;
Boolean Properties
#[Property(type: 'bool', required: false, default: false)]
public bool $is_active;
#[Property(type: 'bool', required: true)]
public bool $terms_accepted;
Array Properties
#[Property(type: 'array', required: false, default: [])]
public array $tags;
#[Property(type: 'array', required: false)]
public ?array $metadata;
#[Property(type: 'array', required: true, validation: 'min:1')]
public array $categories;
🎨 Complex Types
Enum Properties
enum UserStatus: string
{
case ACTIVE = 'active';
case INACTIVE = 'inactive';
case PENDING = 'pending';
}
enum UserRole
{
case ADMIN;
case USER;
case MODERATOR;
}
class UserDTO extends LaravelArcDTO
{
// BackedEnum (with values)
#[Property(type: 'enum', class: UserStatus::class, required: true)]
public UserStatus $status;
// UnitEnum (without values)
#[Property(type: 'enum', class: UserRole::class, default: UserRole::USER)]
public UserRole $role;
}
// Usage
$user = new UserDTO([
'status' => 'active', // Converted to UserStatus::ACTIVE
'role' => 'ADMIN' // Converted to UserRole::ADMIN
]);
Date Properties
use Carbon\Carbon;
use Carbon\CarbonImmutable;
class EventDTO extends LaravelArcDTO
{
// Basic date property
#[Property(type: 'date', required: true)]
public Carbon $created_at;
// Date with custom format
#[Property(type: 'date', required: false, format: 'Y-m-d')]
public ?Carbon $birth_date;
// Date with timezone
#[Property(type: 'date', required: false, timezone: 'Europe/Brussels')]
public ?Carbon $event_date;
// Immutable date
#[Property(type: 'date', required: false, immutable: true)]
public ?CarbonImmutable $deadline;
}
// Usage
$event = new EventDTO([
'created_at' => '2024-01-15 10:30:00',
'birth_date' => '1990-05-15',
'event_date' => 1642248600, // Unix timestamp
'deadline' => now()
]);
Nested DTOs
class AddressDTO extends LaravelArcDTO
{
#[Property(type: 'string', required: true)]
public string $street;
#[Property(type: 'string', required: true)]
public string $city;
#[Property(type: 'string', required: true, validation: 'size:2')]
public string $country_code;
}
class UserDTO extends LaravelArcDTO
{
#[Property(type: 'string', required: true)]
public string $name;
// Optional nested DTO
#[Property(type: 'nested', class: AddressDTO::class, required: false)]
public ?AddressDTO $address;
// Required nested DTO
#[Property(type: 'nested', class: AddressDTO::class, required: true)]
public AddressDTO $billing_address;
}
// Usage
$user = new UserDTO([
'name' => 'John Doe',
'address' => [
'street' => '123 Main St',
'city' => 'Brussels',
'country_code' => 'BE'
],
'billing_address' => [
'street' => '456 Oak Ave',
'city' => 'Antwerp',
'country_code' => 'BE'
]
]);
// Access nested properties
echo $user->address->city; // "Brussels"
Collection Properties
class OrderItemDTO extends LaravelArcDTO
{
#[Property(type: 'string', required: true)]
public string $name;
#[Property(type: 'int', required: true, validation: 'min:1')]
public int $quantity;
#[Property(type: 'float', required: true, validation: 'min:0')]
public float $price;
}
class OrderDTO extends LaravelArcDTO
{
#[Property(type: 'string', required: true)]
public string $order_number;
// Collection of DTOs
#[Property(type: 'collection', class: OrderItemDTO::class, required: false, default: [])]
public array $items;
// Collection with validation
#[Property(type: 'collection', class: OrderItemDTO::class, required: true, validation: 'min:1')]
public array $required_items;
}
// Usage
$order = new OrderDTO([
'order_number' => 'ORD-001',
'items' => [
['name' => 'Widget A', 'quantity' => 2, 'price' => 19.99],
['name' => 'Widget B', 'quantity' => 1, 'price' => 29.99]
]
]);
// Access collection items
foreach ($order->items as $item) {
echo $item->name; // Each item is an OrderItemDTO instance
}
🔄 Transformation Pipeline
Apply transformations to data before casting:
use Grazulex\Arc\Transformers\TrimTransformer;
use Grazulex\Arc\Transformers\LowercaseTransformer;
use Grazulex\Arc\Transformers\UppercaseTransformer;
use Grazulex\Arc\Transformers\HashTransformer;
class UserDTO extends LaravelArcDTO
{
// Email: trim whitespace and convert to lowercase
#[Property(
type: 'string',
required: true,
validation: 'email',
transform: [TrimTransformer::class, LowercaseTransformer::class]
)]
public string $email;
// Country code: trim and convert to uppercase
#[Property(
type: 'string',
required: false,
validation: 'size:2',
transform: [TrimTransformer::class, UppercaseTransformer::class]
)]
public ?string $country_code;
// Password: hash the value
#[Property(
type: 'string',
required: true,
transform: [HashTransformer::class]
)]
public string $password_hash;
}
// Usage
$user = new UserDTO([
'email' => ' [email protected] ', // Becomes: '[email protected]'
'country_code' => ' us ', // Becomes: 'US'
'password_hash' => 'mypassword123' // Becomes: hashed value
]);
Available Transformers
- TrimTransformer - Removes leading/trailing whitespace
- LowercaseTransformer - Converts to lowercase
- UppercaseTransformer - Converts to uppercase
- HashTransformer - Hashes values (configurable algorithm)
Custom Transformers
Create custom transformers by implementing TransformerInterface
:
use Grazulex\Arc\Contracts\TransformerInterface;
class SlugTransformer implements TransformerInterface
{
public function transform(mixed $value): mixed
{
if (!is_string($value)) {
return $value;
}
return Str::slug($value);
}
}
// Use in DTO
#[Property(
type: 'string',
required: true,
transform: [TrimTransformer::class, SlugTransformer::class]
)]
public string $slug;
✅ Validation Rules
Basic Validation
#[Property(type: 'string', required: true, validation: 'max:255')]
public string $name;
#[Property(type: 'string', required: true, validation: 'email|unique:users,email')]
public string $email;
#[Property(type: 'int', required: true, validation: 'min:18|max:65')]
public int $age;
Complex Validation
#[Property(
type: 'string',
required: true,
validation: 'required|string|min:8|regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/'
)]
public string $password;
#[Property(
type: 'array',
required: false,
validation: 'array|min:1|max:10'
)]
public ?array $tags;
Generated Rules
Laravel Arc automatically generates validation rules:
$rules = UserDTO::rules();
// Returns array of Laravel validation rules based on Property attributes
🏠 Default Values
Static Defaults
#[Property(type: 'string', required: false, default: 'guest')]
public string $role;
#[Property(type: 'bool', required: false, default: false)]
public bool $is_active;
#[Property(type: 'array', required: false, default: [])]
public array $tags;
Enum Defaults
#[Property(type: 'enum', class: UserStatus::class, default: UserStatus::PENDING)]
public UserStatus $status;
Date Defaults
#[Property(type: 'date', required: false, default: 'now')]
public ?Carbon $created_at;
📚 Best Practices
1. Use Type Hints
Always use PHP type hints that match your Property type:
// ✅ Good
#[Property(type: 'string', required: true)]
public string $name;
// ❌ Bad
#[Property(type: 'string', required: true)]
public $name;
2. Nullable Properties
Use nullable types for non-required properties:
// ✅ Good
#[Property(type: 'string', required: false)]
public ?string $description;
// ❌ Bad
#[Property(type: 'string', required: false)]
public string $description;
3. Meaningful Validation
Use validation rules that make sense for your business logic:
#[Property(type: 'string', required: true, validation: 'email|max:254')]
public string $email;
#[Property(type: 'int', required: true, validation: 'min:0|max:150')]
public int $age;
4. Consistent Naming
Use consistent naming conventions:
// ✅ Good - snake_case for DTO properties
class UserDTO extends LaravelArcDTO
{
public string $first_name;
public string $last_name;
public string $email_address;
}
5. Group Related Properties
Organize properties logically:
class UserDTO extends LaravelArcDTO
{
// Identity
#[Property(type: 'string', required: true, validation: 'max:255')]
public string $name;
#[Property(type: 'string', required: true, validation: 'email')]
public string $email;
// Profile
#[Property(type: 'int', required: false, validation: 'min:0|max:150')]
public ?int $age;
#[Property(type: 'enum', class: UserStatus::class)]
public UserStatus $status;
// Relationships
#[Property(type: 'nested', class: AddressDTO::class, required: false)]
public ?AddressDTO $address;
}
🔗 Related Pages
- Transformation Pipeline - Detailed guide on data transformations
- Advanced Features - Explore all advanced capabilities
- Examples Gallery - Real-world usage examples
- Best Practices - Recommended patterns and approaches
⬅️ Back to: Quick Start | ➡️ Next: Advanced Features