Simple Slim DTO

Small typed DTO classes for Laravel payloads, nested data, iterable casting, and array or JSON serialization.

Problem

Large DTO libraries often solve far more than a small response or integration DTO needs. That is useful in bigger domains, but sometimes I only want a readable schema: public typed properties, predictable input mapping, nested DTO casting, and a clean array output.

I still like and recommend spatie/laravel-data, but in some projects I only use a small part of its feature set. Simple Slim DTO is the smaller tool I reach for when the DTO should stay close to the payload shape and avoid a broad abstraction layer.

Installation

composer require pepperfm/ssd-for-laravel

Requirements

  • PHP ^8.4

Basic Usage

Extend BaseDto and describe the payload with public typed properties.

use Pepperfm\Ssd\BaseDto;

class ResponseWrapperDto extends BaseDto
{
    public array $data;

    public LinksDto $links;

    public MetaDto $meta;
}

Create DTOs from arrays, objects, Arrayable, Traversable, or named arguments.

$dto = ResponseWrapperDto::make([
    'data' => $response['data'],
    'links' => $response['links'],
    'meta' => $response['meta'],
]);
$dto = new ResponseWrapperDto(
    data: $response['data'],
    links: $response['links'],
    meta: $response['meta'],
);

Nested DTO properties are cast automatically.

use Pepperfm\Ssd\BaseDto;

class LinksDto extends BaseDto
{
    public ?string $first = null;

    public ?string $last = null;

    public ?string $prev = null;

    public ?string $next = null;
}

class MetaDto extends BaseDto
{
    public int $currentPage;

    public int $total;
}

Iterable Casting

Use ToIterable when a property contains a list of items.

The first argument is the collection type. Use ToIterable::ARRAY for a native array. The second argument is optional and defines the item type.

use Pepperfm\Ssd\Attributes\ToIterable;
use Pepperfm\Ssd\BaseDto;

class ResponseDataDto extends BaseDto
{
    public string $id;

    public string $name;
}

class ResponseWrapperDto extends BaseDto
{
    #[ToIterable(ToIterable::ARRAY, ResponseDataDto::class)]
    public array $data = [];
}
/** @var array<array-key, ResponseDataDto> $items */
$items = $dto->data;

For Laravel collections, pass the collection class as the first argument.

use Illuminate\Support\Collection;
use Pepperfm\Ssd\Attributes\ToIterable;
use Pepperfm\Ssd\BaseDto;

class ResponseWrapperDto extends BaseDto
{
    #[ToIterable(Collection::class, ResponseDataDto::class)]
    public Collection $data;
}
/** @var Collection<array-key, ResponseDataDto> $items */
$items = $dto->data;

If the item type is omitted, iterable items are kept as-is.

use Pepperfm\Ssd\Attributes\ToIterable;
use Pepperfm\Ssd\BaseDto;

class TagsDto extends BaseDto
{
    #[ToIterable(ToIterable::ARRAY)]
    public array $tags = [];
}

The item type may be another BaseDto class or any class that can be instantiated from one value.

Current ToIterable signature

Use #[ToIterable(collectionType, itemType)]. The old one-argument DTO form is intentionally not accepted because the first argument now always describes the collection type.

Key Mapping

Input keys can be camelCase or snake_case. Output keys are camelCase by default. Use MapName when the external key must be explicit.

use Pepperfm\Ssd\Attributes\MapName;
use Pepperfm\Ssd\BaseDto;

class UserDto extends BaseDto
{
    #[MapName('external_name')]
    public string $displayName;
}
$dto = UserDto::make([
    'external_name' => 'Ada',
]);

$dto->displayName; // Ada
$dto->external_name; // Ada
$dto->toArray(); // ['external_name' => 'Ada']

Collections and Helpers

Convert many payloads into DTO instances with collect().

$items = ResponseDataDto::collect($response['data']);

Use only() and except() on serialized DTO output.

$dto->only('data', 'meta');
$dto->except('links');

Serialization

$dto->toArray();
$dto->jsonSerialize();
json_encode($dto);

Testing

composer test
composer lint