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 Tools Laravel Package

honeystone/laravel-dto-tools

Honeystone's Laravel DTO Tools provides a lightweight base DTO with validation, casting, serialization, and Eloquent integration, making it easy to move typed, validated data between requests, services, and models in Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is installed via Composer and registered automatically through Laravel package discovery:

composer require honeystone/laravel-dto-tools
php artisan vendor:publish --tag=dto-tools-config

The fastest path to a working DTO is the generator, which scaffolds a class extending the package's base DTO with the MakeableDto trait wired in:

php artisan make:dto UserData

That produces something like:

class UserData extends AbstractDto
{
    use MakeableDto;

    public function __construct(
        public readonly string $name,
        public readonly string $email,
    ) {}
}

From there, the first use case to validate the package against your codebase is hydrating a DTO from an Eloquent model:

$dto = UserData::make($user);
// or explicitly
$dto = UserData::from($user);

Where to look first:

  1. config/dto-tools.php — namespace and path settings the generator will respect. Set this before generating anything, or you'll be moving files later.
  2. The base AbstractDto and the MakeableDto trait source in vendor/honeystone/laravel-dto-tools/src — the magic make/from resolution lives here and determines what "magic" means for every DTO you write.
  3. The Attributes directory — MapFrom, MapTo, and their variants are the crux of the package; skim this folder before writing your first nested DTO.

Implementation Patterns

Model → DTO with attribute mapping. The most valuable pattern is declaring how a DTO field is sourced from a model, which survives model refactors better than constructor plumbing:

class UserData extends AbstractDto
{
    use MakeableDto;

    public function __construct(
        public readonly int $id,
        #[MapFrom('profile.full_name')]
        public readonly string $name,
        #[MapFrom('email')]
        public readonly string $email,
    ) {}
}

$dto = UserData::make($user); // relation paths resolve automatically

Nested DTOs and collections. Children declared as DTO types or DTO collections are hydrated recursively, so a PostData can hold an AuthorData and an array of CommentData without manual loops. Prefer this over manually calling make() inside constructors—the recursive resolver handles collections, paginators, and lazy-loaded relations consistently.

DTO → API payload. Because properties are readonly and typed, json_encode($dto) or ->toArray() produces a predictable shape. This is where the package earns its keep: DTOs become the single source of truth for response contracts and double as a de-facto OpenAPI sketch. Pair with spatie/laravel-data style TypeScript generation scripts only if you already have them; the built-in attribute set won't cover every edge case.

DTO → Model persistence. The MapTo attribute (and corresponding write helpers) lets a DTO round-trip back into a model for updates. Use this for command/queued-job inputs where you want a validated, typed payload rather than a raw array, then hydrate a model from it at the boundary.

Validation coupling. A clean integration is to build DTOs out of FormRequest::validated() output. The MapFrom attribute can reference request keys, and you can make() a DTO directly from a request instance in the controller, keeping controllers thin and DTOs carrying the request→domain translation:

public function store(StoreUserRequest $request)
{
    $dto = UserData::make($request->validated());
    return UserResource::make($dto); // or a DTO-based resource
}

Service-layer boundary. Adopt a convention: controllers produce DTOs, services accept DTOs, repositories return DTOs. Because the DTOs are readonly and immutable, they're safe to pass through jobs and events without defensive copying—this is the biggest day-to-day ergonomic win.

Gotchas and Tips

  • Silent attribute failures. A misspelled MapFrom('relation.field') path doesn't error at attribute-declaration time; it fails during hydration, often with a confusing "missing" or null on a non-nullable property. Debug by calling ->toArray() on the DTO while the raw source is still in scope.
  • Constructor order matters for the generator. Re-run make:dto on an existing class only with --force; otherwise you'll hand-tune and re-tune. The generator is a starting point, not a live scaffold.
  • Readonly + defaults. Readonly promoted properties cannot have nullable defaults replaced post-construction. If a field is optional, be explicit with ?string $x = null; the hydrator will honor defaults only when it doesn't try to write a value.
  • Collection typing. Declare the element type via docblock (@var CommentData[]) or the resolver falls back to arrays of arrays. This is the single most common source of "why does my nested field return an array instead of a DTO."
  • Config namespace drift. If you set the config namespace after generating DTOs, older classes will miss the use imports the package expects. Regenerate or bulk-fix imports; the generator derives imports from config.
  • Model toArray() vs DTO toArray(). These differ subtly—hidden fields, appended accessors, and date serialization. If your API tests depend on model output, expect breakage when migrating to DTOs. Add a serialization test early.
  • N+1 in mapper. Nested DTOs that reach into relations will lazy-load each time. Eager-load in the query before calling make(), or use the mapper's support for already-loaded collections to avoid repeated queries.
  • MapTo write safety. Round-tripping DTO → model can overwrite fields you didn't intend to touch. Treat MapTo as a partial-update API, and be explicit about which fields are writable, or use a dedicated "update" DTO rather than reusing the read DTO.
  • Extension points. If the built-in attributes don't cover a type (e.g., a value object), the intended extension is a custom attribute plus a registered mapper—not a MapFrom closure. Hooking into the mapper registry is the supported escape hatch and survives package upgrades better than overriding AbstractDto.
  • Package maturity. At 51 stars and moderate score, expect gaps in docs and edge-case behavior around unions, enums, and Carbon casts. Pin a version, and budget time to read the resolver source once; it's small and will save you several debugging sessions later.
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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast