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 Validated Dto Laravel Package

wendelladriel/laravel-validated-dto

Validated DTOs for Laravel: extend ValidatedDTO, declare typed properties and validation rules, and get validated, cast, type-safe data objects from request payloads. Includes built-in casts and config publishing for cleaner controllers.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its config so transformers and stubs are customizable:

composer require wendelladriel/laravel-validated-dto
php artisan vendor:publish --tag="validated-dto"

First place to look is the package docs site (laravel-validated-dto.wendelladriel.com), specifically the "Getting Started" and "Casting" pages — the README only covers the happy path. Then run php artisan list | grep dto to confirm the generator command (make:dto) is registered; scaffolding a DTO from the stub is faster than hand-writing one and gives you the rules(), defaults(), and casts() skeletons already wired.

The most valuable first use case is replacing a FormRequest + manual array plumbing in a controller with a single DTO. Declare typed properties, define rules, and inject the DTO into the controller action — the container resolves it from the current request, validates it, and hands you a typed object:

final class StoreUserController
{
    public function __invoke(UserDTO $dto): JsonResponse
    {
        $user = User::create($dto->toArray());

        return response()->json($user, 201);
    }
}

Start with one endpoint, confirm ValidationException still renders as a 422 with the expected error keys (it does, since it reuses Laravel's validator), then expand to the rest of the app. If you want the shape without validation (e.g., outbound API payloads) extend SimpleDTO instead of ValidatedDTO.

Implementation Patterns

Typed properties plus casts() is the whole value proposition. Rules validate the raw input; casts convert it into the typed property. Use StringCast, IntegerCast, FloatCast, BooleanCast, ArrayCast, CarbonCast, EnumCast, CollectionCast, and DTOCast rather than sprinkling (int) and Carbon::parse() in services. The #[Cast] attribute is a per-property alternative to the casts() method when you only need one or two casts.

Nested DTOs and collections. Use DTOCast(AddressDTO::class) for a single nested object and DTOCast(AddressDTO::class, collection: true) (or CollectionCast) for arrays of children. This is the pattern that pays off most in real applications: a CreateOrderDTO containing OrderLineDTO[] and ShippingAddressDTO keeps validation rules co-located with the data they describe, and toArray() recurses correctly as long as you're on a recent 3.x/4.x release (nested serialization was fixed in 3.1.x).

Defaults over nullable. If a field is sometimes, give it an entry in defaults() rather than making the typed property nullable. It removes null checks from every consumer and keeps toArray() output stable for the frontend.

Two directions of data flow. Use fromRequest() / fromArray() / fromJson() for inbound, and fromModel() for outbound. For API responses, a DTO built from a model gives you an explicit, versioned contract — you decide what leaves the application instead of leaking $hidden/$appends behaviour. toModel() covers the write path when you don't want to pass toArray() straight into create().

Lazy validation for queues and commands. fromCommand() and job payloads often arrive in pieces. The lazy validation feature (3.10.0) lets you build the DTO and defer validate() until you actually have all the data, rather than being forced to validate a half-populated payload in the constructor.

Cross-field rules go in after(). Laravel's after validation hook is exposed on the DTO. Use it for "end date must be after start date" style rules instead of dropping into a custom Rule object for one-off cases.

Data transformers for naming conventions. The published config lets you swap the global transformer, or override it per DTO, so camelCase PHP properties can serialize to snake_case JSON (or vice versa) without touching every property. This is the extension point to reach for when integrating with a third-party API.

Custom stubs. Publish and edit the stubs once so every make:dto in the team emits the same base class, namespace, and boilerplate (e.g., a shared BaseDTO with a toResponse() helper).

Gotchas and Tips

Uninitialized typed properties are the number-one footgun. Validation runs, then casts populate properties — but if a key is absent, has no default, and isn't sometimes, you'll get Typed property must not be accessed before initialization when you read it. Fix the rule or add a default; don't make everything nullable.

Casts run after validation, so rules see raw input. A rule of boolean on "0"/"1" strings will pass and then BooleanCast normalizes it — that's intended. But a rule of integer on an empty string historically produced surprising cast results (fixed in 3.5.2), so pin a recent patch version and test your edge-case payloads (empty string, "0", null) explicitly.

Don't rely on $this->data inside overridden methods. The data property is cleared after validation (3.6.0), and its lifecycle has changed more than once (3.9.0 fix). Use validatedData() / all() / toArray() instead of reaching into internals.

toArray() is not the same as "all properties". Properties marked #[SkipOnTransform] (4.4.0), plus hidden/forbidden ones, are excluded from serialization. If a field vanishes from a response, check the attribute before you check the cast.

Carbon and enum serialization. Carbon timezone offsets are preserved (3.7.1), but toArray() and toJson() can diverge — enums serialize to their values, Carbon to strings. If your tests assert on toArray() but production returns JSON, add a toJson() assertion too.

Static analysis will complain. PHPStan/Psalm see typed properties assigned outside the constructor. Add @property docblocks to the class or exclude DTOs from the strictest rules; don't "fix" it by switching to public untyped properties, which defeats the point.

File uploads. Uploaded files validated through fromRequest() need the file validation fixes from 3.4.0+. Type the property as UploadedFile and use a dedicated cast or handle the move in the service layer — don't let the DTO write to disk.

Debugging a failing DTO. dd($dto) shows constructor arguments, not resolved properties — use dd($dto->all()) or $dto->toArray(). When validation fails silently in a job, remember ValidationException doesn't render in console context; catch and log $e->errors().

Keep the package current. A large fraction of the changelog between 3.0 and 4.x is serialization fixes (nested DTOs, collections, models, enums, JsonSerializable). Version drift between environments here manifests as subtly different JSON, not hard errors — pin the version in composer.json rather than relying on ^.

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.
dakiquang/laravel-oauth-refresh-lock
woda/laravel-worktrees
dalefranks/ramasite
faghy/laracashbook
roundly-consulting/google-places-for-laravel
roundly-consulting/kubernetes-api-for-laravel
roundly-consulting/plausible-for-laravel
gvp/gvp-laravel-template
metasyncsite/laravel-metasync-client
roundly-consulting/onboarding-for-laravel
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
adachsoft/changelog-linter