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

Dto Laravel Package

fresns/dto

Fresns DTO provides a simple, typed data transfer object layer for Laravel/PHP applications. Define structured payloads, transform and validate data, and serialize cleanly to arrays or JSON for APIs, requests, and service boundaries.

View on GitHub
Deep Wiki
Context7

Getting Started

  • composer require fresns/dto. Check composer.json first: the package targets modern PHP (8.1+ for enums/readonly/attribute support). On a legacy app, confirm the platform constraint before installing.
  • Where to look first, in order: src/Dto.php (the base class — hydration, all(), only(), except(), toArray()/toJson()), src/Attributes/ (the constraint vocabulary you'll actually annotate with), src/Collections/ (DtoCollection), then tests/ for canonical usage the docs may lag behind.
  • The mental model: a DTO is a typed, self-validating boundary object. You hydrate it from an array (request payload, job payload, API response, queue message), and you emit it back as an array. It is not a model and should not touch the database.
  • Minimal first use case — move the shape of a request out of the controller:
use Fresns\Dto\Dto;

class CreatePostDto extends Dto
{
    public string $title;
    public ?string $content = null;
    public array $tagIds = [];
}

// controller
$dto = CreatePostDto::from($request->all());
$post = app(CreatePost::class)->execute($dto);

The win on day one isn't validation (FormRequests already do that) — it's that the argument passed into your Action has a name, a type, and an IDE-completable shape instead of array $data.

Implementation Patterns

  • DTO at the edge, Action in the middle. Hydrate once in the controller/FormRequest, pass the DTO into an Action or Service, and never let a raw $request->all() past that line. This is the single highest-leverage pattern; it makes Actions testable without HTTP.
  • DTO → Model explicitly. Post::create($dto->toArray()) works when names align, but prefer an explicit mapping method ($dto->toModel() or a PostData::fromModel() inverse) as soon as column names diverge. Implicit toArray() → create() breaks silently when a property is renamed.
  • Output DTOs are a separate class from input DTOs. CreatePostDto and PostResourceDto have different rules and different lifetimes; sharing one class is where DTO layers rot.
  • Nesting. Declare nested DTO-typed properties for object graphs and the collection type for arrays-of-objects. A property typed array gets you an array of arrays, not an array of DTOs — decide deliberately which you want at each level.
  • Collections for lists. Use the package collection when mapping over a result set so you keep map/filter returning typed objects instead of degrading to arrays halfway through a pipeline.
  • Shaping before output. only()/except() on the way out is the clean way to hide internal fields from an API response; don't add unset() calls at the call site.
  • Testing. DTOs are cheap to instantiate and need no container — build them from array fixtures in unit tests and assert on toArray(). This is much faster than feature-testing through HTTP just to check a payload shape.
  • Directory convention. app/DTOs/Input and app/DTOs/Output, one class per boundary crossing. Reviewers should be able to tell from the namespace which direction data flows.
  • Laravel wiring. DTOs don't need service-provider registration; resolve them with ::from() or new. If you want injection, bind them in a controller method and let the container build from the request — but be aware you're then relying on container resolution rather than explicit hydration, which is harder to trace when it fails.
  • Version discipline. ^1.3 in composer.json, and read the compare diff (not just the release title) on every bump. The 1.3.3 changelog is a bare compare link with no written notes, so the diff is the only source of truth.

Gotchas and Tips

  • Uninitialized typed properties are the #1 runtime error. A public string $foo; with no default and no attribute marking it optional will throw the moment it's read after hydration from a payload that omitted it. Either give a default, mark it nullable, or ensure it's required at the boundary — but decide per property, not per class.
  • Scalar coercion is quiet. "12" landing in an int property, or "false" in a bool, will not always fail loudly. Use declare(strict_types=1) and validate the transport layer as well; don't treat DTO typing as input validation.
  • Don't double-validate with confusing results. If the DTO enforces constraints, hydrate from $request->all(); if the FormRequest enforces them, hydrate from $request->validated() and keep the DTO permissive. Mixing both produces two different error formats for the same bad input, and the one the user sees is whichever runs second.
  • Exception messages point at the property path. When a nested DTO fails, read the path in the message before reaching for the debugger — it usually names the exact property and depth.
  • Serialization of dates and enums. Check what toArray() actually emits for a DateTimeInterface or a backed enum before it reaches a JSON response. This is the most common source of "the API shape changed after a refactor" surprises; pin it with an assertion in a test.
  • Keep DTOs logic-free. No queries, no mail, no resolve() calls inside toArray(). The moment a DTO has side effects, it can't be constructed in a unit test and you've lost the main benefit.
  • Extension points: custom cast/attribute classes alongside the built-ins in src/Attributes, and overriding toArray() on your own base DTO for project-wide normalisation (e.g. snake_case keys, null-stripping). Prefer one project-level base class over per-DTO overrides.
  • Config quirks: there is little to configure; if a config/dto.php existst in your install after vendor:publish, treat it as advisory and confirm in src/ that the key is actually read before relying on it.
  • On v1.3.3 specifically: it's a patch release on the 1.3 line with an empty changelog body, which normally means dependency bumps and small fixes rather than behaviour changes. Keep ^1.3, bump deliberately, and let your DTO toArray() assertions catch any serialization drift — that test suite is what makes patch releases safe to take.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource