Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Dto Laravel Package

cerbero/laravel-dto

Cerbero Laravel DTO provides a simple way to define typed data transfer objects in Laravel, with attribute casting, validation, and conversion from arrays, requests, and Eloquent models. It helps keep controllers and services clean by moving data shaping and validation into reusable DTO classes.

View on GitHub
Deep Wiki
Context7

Getting Started

cerbero/laravel-dto gives you a lightweight base class (Cerbero\Dto\Dto) for turning loose arrays, JSON, request payloads, or Eloquent data into typed objects. It is small and framework-agnostic in spirit, with Laravel-friendly entry points (requests, collections). There is no service provider to publish and essentially no config — the package is just a base class plus behaviour.

Install:

composer require cerbero/laravel-dto

First place to look is the base class itself (vendor/cerbero/laravel-dto/src/Dto.php) and the README’s Usage section — the public surface is intentionally narrow: a static factory (Dto::make()), toArray() / toJson(), and a handful of manipulation methods.

Minimal first use case — replace raw $request->validated() arrays leaking through your app:

use Cerbero\Dto\Dto;

class CreateUserDto extends Dto
{
    protected $map = [
        'first_name' => 'firstName',
        'last_name'  => 'lastName',
    ];

    protected $casts = [
        'age'     => 'int',
        'isAdmin' => 'bool',
    ];
}
// StoreUserRequest has already validated the payload
public function store(StoreUserRequest $request)
{
    $dto = CreateUserDto::make($request->validated());
    // or, if the package supports request sources directly:
    // $dto = CreateUserDto::make($request);
}

Then pass $dto (not an array) into your action/service. Day-one win: one concrete class per payload boundary, so refactors show up as IDE errors instead of array_key_exists bugs.

Implementation Patterns

One DTO per boundary, not per table. Create CreateUserDto, UpdateUserDto, UserResponseDto rather than one UserDto doing everything. This gives you independent $map / $casts per contract and avoids conditional casting logic inside a single class.

Source-agnostic factory at the edges. The main value is that the same DTO can be built from many sources:

CreateUserDto::make($request->validated());          // array
CreateUserDto::make($request->all());                // array
CreateUserDto::make($jsonString);                    // JSON
CreateUserDto::make($userModel->toArray());          // Eloquent -> array
CreateUserDto::make($someCollection);                // collection/iterable

Normalise at the boundary (controller, job handler, listener) and let actions accept a DTO only.

Mapping for external snake_case / third-party APIs. Use $map to translate source keys to your property names without littering actions with Arr::get() aliases.

Cast DTO into nested DTOs. For nested payloads, cast a property to another DTO class so the whole graph is typed:

class OrderDto extends Dto
{
    protected $casts = [
        'address' => AddressDto::class,
        'lines'   => OrderLineDto::class . '[]', // verify exact syntax in the docs
    ];
}

Partial DTOs for PATCH and evolving APIs. For partial updates or external payloads that change shape, use the package’s flags (Dto::PARTIAL, Dto::IGNORE_UNKNOWN) rather than making every property nullable:

$dto = UpdateUserDto::make($request->validated(), Dto::PARTIAL);

Be explicit at the call site — a PATCH DTO and a POST DTO should not share the same construction call.

Manipulation instead of in-place mutation. Use merge(), set(), and mutate() to derive a new DTO without changing the original. This is useful in pipelines:

$dto = CreateUserDto::make($request->validated())
    ->mutate(fn ($dto) => $dto->set('email', strtolower($dto->email)));

Controller → action → DTO response flow. Controller builds the input DTO and returns UserResponseDto::make($user). Actions work with input DTOs and return Eloquent models; the response layer builds output DTOs. Avoid returning DTOs directly from controllers unless you explicitly want the JsonSerializable behaviour — a thin resource or ->toJson() call is clearer.

Testing. Assert against toArray() output, not property-by-property:

$dto = CreateUserDto::make(['first_name' => 'Ada', 'age' => '41']);
$this->assertSame(
    ['firstName' => 'Ada', 'age' => 41],
    $dto->toArray()
);

That single assertion covers mapping and casting without coupling tests to internal property order.

Events and jobs. DTOs are convenient in dispatch calls, but queues serialise payloads. Convert to array at the dispatch boundary (UserCreated::dispatch($dto->toArray())) or implement a dedicated SerializesDto trait; don’t rely on default serialisation.

Gotchas and Tips

Flag semantics matter — read the constants once. PARTIAL means “don’t require every property”; IGNORE_UNKNOWN means “drop keys that don’t map to a property.” These solve different problems. Using PARTIAL when you actually need unknown-key tolerance (or vice versa) is the most common source of confusing “missing property” errors. Check the flag list on Dto in your installed version — it is the canonical reference.

Typed properties and uninitialised values. If you use PHP typed properties, an uninitialised one will throw Error when accessed, not return null. Ensure the property is either present in the source, has a default, or the DTO is built with PARTIAL.

Casts run once, on construction. Changing a property after construction via set() will not re-run the caster. If your setter needs normalisation, do it explicitly in the mutate() callback.

Nested nullability. A cast to AddressDto::class will typically fail on null. Either guard the source before building, or verify how the package handles null for cast properties in your pinned version — don’t assume it short-circuits.

No validation, no guessing. The DTO does not validate. Keep FormRequest rules as the single source of truth; the DTO is a shape, not a validator. Adding ad-hoc validation inside DTOs creates two places to update.

Reflection cost on hot paths. Construction is reflection-based. Building thousands of DTOs in a loop will show up in profilers. For large result sets, batch a single DTO wrapping a collection rather than one DTO per row, or convert lazily at the point of serialisation.

Debugging. dd($dto) works (it’s JsonSerializable), but ->toArray() gives clearer output and avoids magic. When a value is wrong, dump $request->validated() and the DTO side-by-side to isolate whether the issue is mapping, casting, or source.

Wrap it rather than spread it. With a small dependency (low stars, no known dependents), pin the version in composer.json and introduce an App\Support\Dto base class extending Cerbero\Dto\Dto. If the package is abandoned or the API shifts, you have one class to update instead of every DTO. This also gives you a single place to standardise serialisation, casting defaults, and flags.

Extension points are the methods on the base class — $map, $casts, custom cast methods, and the manipulation API. Prefer these over overriding constructor or array access behaviour; the latter is where compatibility breaks surface first.

Config quirks are minimal by design. There is essentially nothing to publish or tune; per-call flags are the configuration mechanism. If you find yourself wanting global settings, you likely want a project base class instead.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource