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/ */
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());
}
}
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
]);
// 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);