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

romanzipp/laravel-dto

A lightweight base class for building Data Transfer Objects in Laravel. Define typed properties, cast nested DTOs and collections, and hydrate from arrays or requests, giving your app predictable, self-validating data objects instead of loose arrays.

View on GitHub
Deep Wiki
Context7

Getting Started

  • composer require romanzipp/laravel-dto. The package is small and Laravel auto-discovery means there is typically nothing to publish or configure. Before committing, check the composer.json constraints (php, illuminate/*) against your app’s versions — a low-traffic package can lag behind current Laravel releases.
  • Look first at two things: the README’s DTO definition example, and the base DTO class in vendor/romanzipp/laravel-dto/src. The package is a thin reflection-based hydrator; the source is the real documentation and you can read it in ten minutes. That is where you confirm the exact factory names (fromArray, fromRequest, or similar), casting rules, and missing-key behavior.
  • First use case: stop passing untyped $request->validated() arrays from controllers into actions/services. Define an intent-specific DTO and hand that around instead.
namespace App\Dtos;

use RomanZipp\LaravelDto\Dto;

class CreateUserDto extends Dto
{
    public string $name;
    public string $email;
    public ?int $age = null;
}
public function store(StoreUserRequest $request, CreateUserAction $action)
{
    return $action->execute(
        CreateUserDto::fromArray($request->validated())
    );
}
  • Keep DTOs in app/Dtos and make them intent-specific (CreateUserDto, UpdateUserDto, UserFilterDto) rather than one-per-model. An UpdateUserDto with all-nullable properties is what makes PATCH semantics explicit.

Implementation Patterns

Controller → DTO → Action. The highest-value pattern: controller validates and hydrates, the action’s execute(CreateUserDto $dto) signature is self-documenting, and the DTO becomes the seam you can test without HTTP.

Validation stays in FormRequests. Let the DTO own shape and types only. StoreUserRequest::validated() produces a clean array; the DTO turns that into typed properties. Resist the urge to also put rules in the DTO — two validation layers drift.

Nested DTOs. Type a property as another DTO class (public AddressDto $address;) and check whether the hydrator recurses. If it does, you get free composition; if it doesn’t, write an explicit static factory (CreateUserDto::fromPayload()) that maps child arrays manually. Don’t assume recursion works — test it on day one.

DTO → Eloquent. User::create($dto->toArray()) works well for creates but requires $fillable/$guarded to line up. For updates, filter nulls first so a partially-populated DTO doesn’t blank columns:

$user->update(array_filter($dto->toArray(), fn ($v) => $v !== null));

DTO → response. Return $dto->toArray() from an API Resource or response()->json($dto) if the base class is JsonSerializable. This gives you a stable response shape decoupled from the model.

Jobs and events. Passing a DTO as a job constructor argument is convenient and plain PHP objects serialize into the queue payload fine. Verify a round-trip with dispatchSync in a test before relying on it; if the base class keeps data in non-public properties, the serialized form may not survive unserialize the way you expect.

Testing. Because DTOs are plain objects, unit-test actions by constructing CreateUserDto::fromArray([...]) directly — no request, no HTTP kernel, no database.

Own base class. Create App\Dtos\Dto extends RomanZipp\LaravelDto\Dto and put shared helpers (only(), except(), toModel()) there. When the hydrator’s behavior doesn’t fit, you change one file — or swap packages — instead of touching fifty DTOs.

Collections. collect($rows)->map(fn ($r) => UserDto::fromArray($r)) for list endpoints; keep it in a small UserDto::collect() helper so the mapping logic isn’t duplicated.

Gotchas and Tips

  • No validation layer. Typed properties coerce scalars; they do not reject "not-an-email". Always validate upstream.
  • Coercion surprises. PHP casts are permissive: (int) "12abc" is 12, (bool) "false" is true, (int) "" is 0. If the hydrator casts naively, normalise in prepareForValidation() on the FormRequest.
  • Missing keys. An uninitialised typed property throws Typed property ... must not be accessed before initialization on read. Give every optional field a default (= null) so “absent” and “explicitly null” are at least distinguishable from a crash.
  • Unknown keys. Determine whether the hydrator throws or silently ignores extra input. If it throws, never feed it $request->all() — always validated().
  • toArray() semantics. Confirm how it handles nested DTOs, private/protected properties, and nulls. This is the single behavior most likely to differ from your assumptions.
  • Queue serialization pins class names. Renaming or moving a DTO class breaks jobs already in the queue. Keep DTOs in a stable namespace and treat renames as a deploy concern.
  • Package maturity. 11 stars, zero dependents, a single maintainer. MIT means you can fork, but for anything load-bearing pin the exact version and consider vendoring the source. If you need validation, MapFrom-style name mapping, lazy properties, or rich casting, spatie/laravel-data covers the same ground with far more community support — evaluate it before standardising on this one.
  • Performance. Reflection hydration has a cost per instantiation. Fine once per request; measure before using it inside a loop over thousands of rows — hydrate the collection once, or map to arrays and build DTOs lazily.
  • Extension point is the base class. Override the constructor or static factory in your own App\Dtos\Dto rather than patching vendor code; that keeps upgrades (or a fork) trivial.
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