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

Symfony Dto Bundle Laravel Package

hprotravel/symfony-dto-bundle

A Symfony bundle from HproTravel for working with data transfer objects (DTOs), helping structure request and response data, improve type safety, and keep application layers clean. It integrates DTO handling into Symfony projects.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a private Bitbucket package (hprotravel/*), so the first hurdle is access, not code. Wire it up before you read a line of its source.

1. Teach Composer where the repo lives in the root composer.json:

{
  "repositories": [
    {
      "type": "vcs",
      "url": "[email protected]:hprotravel/symfony-dto-bundle.git"
    }
  ]
}

Use SSH keys or a Bitbucket app password (with repository:read) configured in auth.json / COMPOSER_AUTH. CI runners need the same credentials as your laptop — this is the single most common first-day blocker.

2. Require and register it

composer require hprotravel/symfony-dto-bundle
// config/bundles.php
return [
    // ...
    Hprotravel\SymfonyDtoBundle\SymfonyDtoBundle::class => ['all' => true],
];

If the bundle ships a Flex recipe you'll get config and services automatically; if not, check for a DependencyInjection/Configuration.php and add config/packages/symfony_dto.yaml by hand.

3. Read these files first, in this order

  • README.md — the only authoritative source for the public API; treat the rest of this doc as pattern guidance, not API reference.
  • src/DependencyInjection/Configuration.php — the config tree tells you what's actually tunable (naming strategy, validation toggles, cache, etc.).
  • src/ for the DTO base class/interface and any PHP attributes (e.g. mapping/ignore attributes).
  • Any ArgumentResolver / Controller listener — this is where "magic" hydration from Request happens, and where most surprises come from.

4. First use case: kill $request->request->all() in a controller

Replace untyped array access with a validated, typed DTO:

#[Route('/orders', methods: ['POST'])]
public function create(CreateOrderDto $dto): JsonResponse
{
    // $dto is already hydrated from the request body and validated
    $order = $this->orderService->place($dto);
    return $this->json($order, 201);
}

If the bundle resolves controller arguments, this just works once the DTO is registered. If it doesn't, fall back to explicit construction via a factory/resolver service (see below). Start with one endpoint, confirm validation and error shape, then roll out.

Implementation Patterns

Controller argument resolution (preferred) Type-hint the DTO on the action. The bundle typically resolves it from the request body/query, runs denormalization, and validates. Keep controllers thin: DTO in, domain service call, response DTO or entity out.

Manual hydration in services and queue handlers Don't rely on the HTTP layer for non-HTTP entry points (messenger consumers, CLI, webhooks). Inject the bundle's factory/denormalizer service and build DTOs explicitly:

$dto = $this->dtoFactory->create(CreateOrderDto::class, $payloadArray);

Wrap this in your own small PayloadToDto helper if you find yourself repeating it — it gives you one place to enforce validation groups and error handling across jobs.

Constructor-promoted, readonly DTOs Prefer immutable DTOs with promoted constructor properties. They compose better with validation attributes, are easier to test, and avoid "half-populated object" bugs during hydration. Reserve setter-based/mutable DTOs for partial-update (PATCH) flows.

Nested DTOs and collections Model AddressDto, OrderLineDto[] as real DTO classes rather than associative arrays. This is where a DTO bundle earns its keep: nested denormalization plus #[Valid] on the parent property gives you recursive validation for free. For collections, type-hint the array docblock (@var OrderLineDto[]) so the serializer can pick the element type.

DTOs as command/query objects The same DTOs are useful one layer down. Pass CreateOrderDto into a command bus or service method instead of an array or a partially-built entity. This keeps validation rules co-located with the shape of the input and makes service signatures self-documenting.

Response DTOs and serialization Use dedicated response DTOs (not entities) with serialization groups so you control the API surface. Map entity → response DTO explicitly in a small assembler/mapper method; don't expose entities directly. This decouples your JSON contract from Doctrine mappings and lets you evolve the schema independently.

Explicit entity mapping Keep a hand-written toEntity() / assembler step between DTO and entity. Avoid generic "map all properties with matching names" helpers on write paths — see the mass-assignment note below.

Where to put the DTO Co-locate DTOs with the feature that owns them (src/Order/Dto/, alongside the controller/service), not in a global Dto/ dump. It keeps ownership obvious and prevents the namespace sprawl that makes large Symfony apps hard to navigate.

Gotchas and Tips

  • Private repo auth is the #1 onboarding failure. Lock down SSH/app-password access for every developer and every CI pipeline. Pin to a tag or commit if the maintainers don't publish semantic versions — private bundles often don't.
  • Confirm the actual API, don't assume. Names like dtoFactory, attribute names, and config keys in this document are illustrative. The bundle's README and Configuration.php are the source of truth; grep src/ for ArgumentResolver, Denormalizer, and attribute classes before writing patterns against it.
  • Constructor vs. setter hydration. If a DTO only has constructor-promoted readonly properties, the resolver/denormalizer must be able to pass named arguments or use reflection. Mismatched casing (request snake_case vs. property camelCase) silently yields nulls — configure the name converter and verify with one real request.
  • Validation groups. Argument-resolved DTOs usually validate with the Default group. If you need create vs. update rule sets, check how the bundle lets you select a group (attribute, route default, or manual validation) — don't assume it's automatic.
  • Nested validation needs #[Valid]. A nested DTO without #[Valid] on the parent property passes validation even when the child is invalid. This bites hardest on collections.
  • Type coercion and dates. String → \DateTimeImmutable, enums, and int/float casting depend on the denormalizer config. Test enum-backed DTOs and date fields explicitly; silent coercion failures surface as null properties, not exceptions.
  • Mass assignment risk. Never bind a request DTO directly onto a Doctrine entity by property name. An attacker can set fields you didn't intend (e.g. isAdmin, ownerId). Always map field-by-field in an assembler.
  • Metadata/attribute caching in dev. If the bundle caches DTO metadata, attribute or property changes may not take effect until bin/console cache:clear. If hydration "ignores" a new property, clear cache before you debug the code.
  • Debugging hydration. dump($dto) in the controller first; if it's empty, the resolver/denormalizer — not your DTO — is the problem. Check the Symfony profiler's request/validator panels and the validator channel in logs. Add a smoke test per DTO that feeds a realistic payload and asserts every property.
  • Error responses. The bundle will likely throw a validation exception before your controller body runs. Register an exception listener / kernel.exception subscriber that renders ValidationFailedException as a 422 with field-level errors, otherwise you'll get generic 500s.
  • Extension points. Typical hooks: custom normalizers/denormalizers (register with serializer.normalizer tags and priority), custom type mappers for value objects, and mapping callbacks/events. If the bundle doesn't cover a case, extend the serializer rather than patching the bundle.
  • Testing. Unit-test DTO hydration and validation independently of controllers — feed arrays and assert properties plus constraint violations. This catches naming-strategy and coercion regressions far faster than HTTP tests.
  • Upgrade risk. A private, zero-dependent bundle has no external pressure to keep its API stable. Pin versions, read the changelog/diff before bumping, and keep a thin adapter layer around its factory/resolver so a breaking change touches one class, not every controller.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core