PHP code example of holiq / action-data

1. Go to this page and download the library: Download holiq/action-data library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

holiq / action-data example snippets


return [
    'action_path' => 'app/Actions',
    'data_path' => 'app/DataTransferObjects',
];

   readonly class CreateUserData extends DataTransferObject
   {
       public function __construct(
           #[Required, Length(min: 2, max: 50)]
           public string $name,

           #[Required, Email]
           public string $email,
       ) {}
   }
   

   readonly class CreateUserAction extends Action
   {
       public function execute(CreateUserData $data): User
       {
           return User::create($data->toArray());
       }
   }
   

   $userData = CreateUserData::resolve($request->validated())
       ->validateAttributes();

   $user = CreateUserAction::resolve()->execute($userData);
   

// config/action-data.php
return [
    'action_path' => 'app/Actions',
    'data_path' => 'app/DataTransferObjects',
];



namespace App\Actions;

use Holiq\ActionData\Foundation\Action;

readonly class StoreUserAction extends Action
{
    public function execute(): mixed
    {
        // Your business logic here
    }
}



namespace App\Actions;

use App\DataTransferObjects\StoreUserData;
use Holiq\ActionData\Foundation\Action;

readonly class StoreUserAction extends Action
{
    public function execute(StoreUserData $data): User
    {
        // Type-safe business logic with validated DTO
        return User::create($data->toArray());
    }
}



namespace App\DataTransferObjects;

use Holiq\ActionData\Foundation\DataTransferObject;

readonly class CreateUserData extends DataTransferObject
{
    final public function __construct(
        // Define your properties with validation attributes
        // #[Required, Length(min: 1, max: 255)] public string $name,
        // #[Required, Email] public string $email,
    ) {}
}

// From array
$userData = CreateUserData::resolve([
    'first_name' => 'John',
    'last_name' => 'Doe',
    'email' => '[email protected]'
]);

// From Form Request
$userData = CreateUserData::resolveFrom($request);

// From Eloquent Model
$userData = CreateUserData::resolveFrom($user);

readonly class CreateUserData extends DataTransferObject
{
    public function __construct(
        public string $firstName,
        public string $lastName,
        public string $email,
    ) {}
}

$data = new CreateUserData('John', 'Doe', '[email protected]');

// Convert to snake_case (default)
$array = $data->toArray();
// Result: ['first_name' => 'John', 'last_name' => 'Doe', 'email' => '[email protected]']

// Convert to camelCase
$camelCase = $data->toCamelCase();
// Result: ['firstName' => 'John', 'lastName' => 'Doe', 'email' => '[email protected]']

// Convert to JSON
$json = $data->toJson();

// Convert with context-specific exclusions
$createArray = $data->toArrayForCreate();
$updateArray = $data->toArrayForUpdate();

readonly class CreateUserData extends DataTransferObject
{
    public function __construct(
        public string $firstName,
        public string $lastName,
        public string $email,
        public ?string $password = null,
    ) {}

    protected function toExcludedPropertiesOnCreate(): array
    {
        return []; // Include all properties for create
    }

    protected function toExcludedPropertiesOnUpdate(): array
    {
        return ['password']; // Exclude password from updates
    }
}

use Holiq\ActionData\Attributes\Validation\{Required, Email, Length, Range, Pattern};
use Holiq\ActionData\Foundation\DataTransferObject;

readonly class CreateUserData extends DataTransferObject
{
    public function __construct(
        #[Required, Length(min: 2, max: 50)]
        public string $name,

        #[Required, Email]
        public string $email,

        #[Required, Range(min: 18, max: 120)]
        public int $age,

        #[Pattern(regex: '/^\+?[1-9]\d{1,14}$/')]
        public ?string $phone = null,
    ) {}
}

// Validate using attributes
$user = CreateUserData::resolve($data)->validateAttributes();

// If invalid, validateAttributes() throws Illuminate\Validation\ValidationException
// with Laravel-standard per-field errors.

$user = new CreateUserData('John Doe', '[email protected]', 25);

// Single validation
$user->validate(
    fn (CreateUserData $data) => str_contains($data->email, '@'),
    'Email must contain @ symbol'
);

// Chain multiple validations
$user
    ->validate(fn ($data) => !empty($data->name), 'Name is 

use Attribute;
use Holiq\ActionData\Contracts\Validator;

#[Attribute(Attribute::TARGET_PROPERTY)]
class Positive implements Validator
{
    public function validate(mixed $value, string $property): bool
    {
        return is_numeric($value) && $value > 0;
    }

    public function getErrorMessage(string $property): string
    {
        return "'{$property}' must be a positive number.";
    }
}

readonly class AddressData extends DataTransferObject
{
    public function __construct(
        public string $street,
        public string $city,
        public string $country,
    ) {}
}

readonly class UserData extends DataTransferObject
{
    public function __construct(
        public string $name,
        public string $email,
        public AddressData $address, // Nested DTO
    ) {}
}

// Option 1: Using resolver (auto maps nested array → DTO)
$user = UserData::resolve([
    'name' => 'John Doe',
    'email' => '[email protected]',
    'address' => [
        'street' => '123 Main St',
        'city' => 'Anytown',
        'country' => 'USA',
    ],
]);

// Option 2: Manual instantiation (must pass AddressData object)
$user = new UserData(
    name: 'John Doe',
    email: '[email protected]',
    address: new AddressData(
        street: '123 Main St',
        city: 'Anytown',
        country: 'USA',
    ),
);

// Access nested data
echo $user->address->street; // "123 Main St"

readonly class UserData extends DataTransferObject
{
    public function __construct(
        public string $name,
        public AddressData $currentAddress,
        /** @var AddressData[] */
        public array $previousAddresses = [], // Array of DTOs
    ) {}
}

$user = UserData::resolve([
    'name' => 'Jane Smith',
    'currentAddress' => [
        'street' => '456 Oak Ave',
        'city' => 'Springfield',
        'country' => 'USA',
    ],
    'previousAddresses' => [
        [
            'street' => '789 Pine St',
            'city' => 'Oldtown',
            'country' => 'USA',
        ],
        [
            'street' => '321 Elm Dr',
            'city' => 'Hometown',
            'country' => 'USA',
        ],
    ],
]);

// Access array of DTOs
foreach ($user->previousAddresses as $address) {
    echo $address->street; // Each item is an AddressData instance
}

readonly class UserProfileData extends DataTransferObject
{
    public function __construct(
        public string $name,
        public string $email,
        public ?string $bio = null,
        public int $age = 0,
    ) {}

    /**
     * Define transformations applied during resolve()
     */
    protected static function transforms(): array
    {
        return [
            'name' => fn ($value) => trim(strtoupper($value)),
            'email' => fn ($value) => trim(strtolower($value)),
            'bio' => fn ($value) => $value ? trim($value) : null,
            'age' => fn ($value) => max(0, (int) $value), // Ensure non-negative
        ];
    }
}

$profile = UserProfileData::resolve([
    'name' => '  john doe  ',           // Becomes "JOHN DOE"
    'email' => '  [email protected]  ',  // Becomes "[email protected]"
    'bio' => '  Software developer  ',   // Becomes "Software developer"
    'age' => '-5',                       // Becomes 0
]);

readonly class ProductData extends DataTransferObject
{
    public function __construct(
        public string $name,
        public float $price,
        /** @var string[] */
        public array $tags,
    ) {}

    protected static function transforms(): array
    {
        return [
            'name' => fn ($value) => ucwords(trim($value)),
            'price' => fn ($value) => round((float) $value, 2),
            'tags' => fn ($value) => is_array($value)
                ? array_values(array_map('strtolower', array_filter($value)))
                : [],
        ];
    }
}

$product = ProductData::resolve([
    'name' => '  awesome widget  ',    // Becomes "Awesome Widget"
    'price' => '19.999',               // Becomes 20.0
    'tags' => ['Electronics', '', 'GADGET', null, 'Popular'], // Becomes ["electronics", "gadget", "popular"]
]);

// Data Transfer Object with Validation
namespace App\DataTransferObjects;

use Holiq\ActionData\Attributes\Validation\Email;
use Holiq\ActionData\Attributes\Validation\Length;
use Holiq\ActionData\Attributes\Validation\Pattern;
use Holiq\ActionData\Attributes\Validation\Required;
use Holiq\ActionData\Foundation\DataTransferObject;

readonly class CreateUserData extends DataTransferObject
{
    public function __construct(
        #[Required]
        #[Length(min: 2, max: 50)]
        public string $firstName,

        #[Required]
        #[Length(min: 2, max: 50)]
        public string $lastName,

        #[Required]
        #[Email]
        public string $email,

        #[Required]
        #[Length(min: 8)]
        public string $password,

        #[Pattern(regex: '/^\+?[1-9]\d{1,14}$/')]
        public ?string $phone = null,
    ) {}

    /**
     * Apply data transformations
     *
     * Note: Transform keys support both camelCase and snake_case.
     * Use camelCase (firstName) or snake_case (first_name) - both work!
     */
    protected static function transforms(): array
    {
        return [
            'firstName' => fn ($value) => ucfirst(trim($value)),
            'lastName' => fn ($value) => ucfirst(trim($value)),
            'email' => fn ($value) => strtolower(trim($value)),
            'phone' => fn ($value) => $value ? preg_replace('/\D/', '', $value) : null,
        ];
    }

    protected function toExcludedPropertiesOnUpdate(): array
    {
        return ['password']; // Don't include password in updates
    }
}

// Action Class
namespace App\Actions\User;

use App\DataTransferObjects\CreateUserData;
use App\Models\User;
use Holiq\ActionData\Foundation\Action;
use Illuminate\Support\Facades\Hash;

readonly class CreateUserAction extends Action
{
    public function execute(CreateUserData $data): User
    {
        // Data is already validated and transformed
        return User::create([
            'first_name' => $data->firstName,
            'last_name' => $data->lastName,
            'email' => $data->email,
            'password' => Hash::make($data->password),
            'phone' => $data->phone,
        ]);
    }
}

// Form Request
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class CreateUserRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'first_name' => ['

// Address DTO
readonly class AddressData extends DataTransferObject
{
    public function __construct(
        #[Required] public string $street,
        #[Required] public string $city,
        #[Required] public string $state,
        #[Required] public string $zipCode,
        #[Required] public string $country,
    ) {}
}

// Order Item DTO
readonly class OrderItemData extends DataTransferObject
{
    public function __construct(
        #[Required] public string $productId,
        #[Required] public int $quantity,
        #[Required] public float $price,
    ) {}

    protected static function transforms(): array
    {
        return [
            'quantity' => fn ($value) => max(1, (int) $value),
            'price' => fn ($value) => round((float) $value, 2),
        ];
    }
}

// Main Order DTO
readonly class CreateOrderData extends DataTransferObject
{
    public function __construct(
        #[Required] public string $customerId,
        #[Required] public AddressData $shippingAddress,
        #[Required] public AddressData $billingAddress,
        /** @var OrderItemData[] */
        #[Required] public array $items,
        public ?string $notes = null,
    ) {}
}

// Usage
$orderData = CreateOrderData::resolve([
    'customer_id' => '12345',
    'shipping_address' => [
        'street' => '123 Main St',
        'city' => 'Anytown',
        'state' => 'CA',
        'zip_code' => '12345',
        'country' => 'USA',
    ],
    'billing_address' => [
        'street' => '456 Oak Ave',
        'city' => 'Somewhere',
        'state' => 'NY',
        'zip_code' => '67890',
        'country' => 'USA',
    ],
    'items' => [
        [
            'product_id' => 'prod-1',
            'quantity' => 2,
            'price' => 29.99,
        ],
        [
            'product_id' => 'prod-2',
            'quantity' => 1,
            'price' => 15.50,
        ],
    ],
    'notes' => 'Please handle with care',
]);

// Action with Dependencies
namespace App\Actions\User;

use App\DataTransferObjects\CreateUserData;
use App\Models\User;
use App\Services\EmailService;
use App\Services\UserService;
use Holiq\ActionData\Foundation\Action;

readonly class CreateUserWithNotificationAction extends Action
{
    public function __construct(
        private UserService $userService,
        private EmailService $emailService,
    ) {}

    public function execute(CreateUserData $data): User
    {
        $user = $this->userService->create($data);

        $this->emailService->sendWelcomeEmail($user);

        return $user;
    }
}

// Usage in Controller
$user = CreateUserWithNotificationAction::resolve()->execute($userData);
bash
php artisan vendor:publish --provider="Holiq\ActionData\ActionDataServiceProvider" --tag="config"
bash
# Basic DTO
php artisan make:dto CreateUserData

# DTO in subdirectory
php artisan make:dto User/CreateUserData

# Force overwrite existing files
php artisan make:dto CreateUserData --force
bash
composer analyse