alvarez/concrete-dto

Installs: 1

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/alvarez/concrete-dto

v1.0.0 2026-01-13 07:21 UTC

This package is not auto-updated.

Last update: 2026-01-14 06:36:13 UTC


README

ConcreteDTO Logo

ConcreteDTO

A small, explicit PHP Data Transfer Object library that keeps data predictable and readable.

Learn it in minutes, keep it in your toolbox for years.

License: MIT PHP Version

DocumentationInstallationQuick StartAPI Reference

Why ConcreteDTO?

  • Tiny API focused on constructor-first DTOs
  • Clear import/export helpers for arrays, JSON, and custom converters
  • Immutability helpers to prevent sneaky mutations
  • Framework-agnostic and dependency-light

Installation

composer require alvarez/concrete-dto

ConcreteDTO requires PHP 8.1+ and works with any framework or plain PHP codebase.

Quick Start

Define a DTO

<?php

use Alvarez\ConcreteDto\AbstractDTO;

final class UserDTO extends AbstractDTO
{
    public function __construct(
        public readonly string $name,
        public readonly string $email,
    ) {}
}

Import data

// From array
$user = UserDTO::fromArray([
    'name' => 'Daniel Alvarez',
    'email' => 'alvarez@alvarez.com',
]);

// From JSON
$user = UserDTO::fromJSON('{"name":"Daniel Alvarez","email":"alvarez@alvarez.com"}');

// From custom object
use Alvarez\ConcreteDto\Contracts\DTOFrom;
use Alvarez\ConcreteDto\Contracts\IsDTO;

final class UserRequestToDTO implements DTOFrom
{
    public static function handle(mixed $dataToConvert): IsDTO
    {
        return new UserDTO(
            name: $dataToConvert->fullName,
            email: $dataToConvert->contact,
        );
    }
}

$request = new UserRequest('Daniel Alvarez', 'alvarez@alvarez.com');
$user = UserDTO::from(UserRequestToDTO::class, $request);

Export data

// To array
$data = $user->toArray();
// ['name' => 'Daniel Alvarez', 'email' => 'alvarez@alvarez.com']

// To JSON
$json = $user->toJson();
// {"name":"Daniel Alvarez","email":"alvarez@alvarez.com"}

// To custom object
use Alvarez\ConcreteDto\Contracts\DTOTo;

final class UserDTOToModel implements DTOTo
{
    public static function handle(IsDTO $dto): mixed
    {
        return new UserModel(name: $dto->name, email: $dto->email);
    }
}

$model = $user->to(UserDTOToModel::class);

// Filter fields
$publicData = $user->except(['email']);
// ['name' => 'Daniel Alvarez']

Immutability

// Clone with updates
$updated = $user->cloneWith(['email' => 'new@example.com']);
// Original $user remains unchanged

Validation

Override the static validate() method to add custom validation logic. Validation runs automatically when using fromArray(), fromJSON(), or cloneWith().

For validation on direct instantiation with new, call self::validate($this->toArray()) in your constructor.

final class UserDTO extends AbstractDTO
{
    public function __construct(
        public readonly string $name,
        public readonly string $email,
    ) {
        self::validate($this->toArray()); // Enable validation on direct instantiation
    }

    public static function validate(array $data): void
    {
        if (!filter_var($data['email'] ?? '', FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Invalid email format');
        }
        
        if (strlen($data['name'] ?? '') < 2) {
            throw new InvalidArgumentException('Name too short');
        }
    }
}

// Validation runs automatically
$user = UserDTO::fromArray(['name' => 'Daniel', 'email' => 'invalid']);
// Throws InvalidArgumentException

$user = new UserDTO('A', 'invalid@email');
// Also throws InvalidArgumentException because validate is called in constructor

API Reference

Importing Methods

Method Purpose
fromArray(array $data): static Create DTO from associative array
fromJSON(string $json): static Create DTO from JSON string
from(DTOFrom|string $conversor, mixed $data): static Create DTO using custom converter

Exporting Methods

Method Purpose
toArray(): array Export DTO as associative array
toJson(): string Export DTO as JSON string
to(DTOTo|string $conversor): mixed Export DTO using custom converter

Immutability Methods

Method Purpose
cloneWith(array $fields): static Clone with field overrides
except(array $keys): array Get array excluding specified fields

Validation

| Method | Purpose | |--------|---------|---| | validate(array $data): void | Override to add custom validation logic before DTO creation |

Real-World Examples

HTTP API Response

Route::get('/user/{id}', function (Request $request) {
    $user = User::find($request->id);
    return response()->json($user->toArray());
});

Queue Job

dispatch(new SendEmailJob($user->toJson()));

class SendEmailJob
{
    public function handle()
    {
        $user = UserDTO::fromJSON($this->userData);
        Mail::to($user->email)->send(new WelcomeEmail($user));
    }
}

Domain Model Conversion

// Map DTO to Eloquent model for persistence
$userModel = $userDTO->to(UserDTOToModel::class);
$userModel->save();

Filtering Sensitive Data

// Remove email before caching user data
$cacheData = $user->except(['email']);
Cache::put("user.{$user->name}", $cacheData);

Documentation

For in-depth guides, visit the full documentation.

Topics covered:

  • Getting Started - Installation and first DTO
  • Importing Data - Arrays, JSON, and custom converters
  • Exporting Data - Output formats and domain mapping
  • Immutability - Safe updates and field filtering

Testing

composer test

Runs tests with Pest PHP.

License

This library is open-sourced software licensed under the MIT license.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

⬆ back to top