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


⬅️ Back to: Quick Start | ➡️ Next: Advanced Features