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

Hydrator Laravel Package

yiisoft/hydrator

Yii Hydrator populates typed objects from arrays and other input using attributes and property mapping. It handles type casting, nested hydration, and dependency injection, offering PHP and Laravel apps a fast, flexible alternative to manual DTO construction.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and confirm your PHP version is compatible (this is a PHP 8.1+ package):

composer require yiisoft/hydrator

Start with a small DTO and the two core methods: create() for new objects and hydrate() for existing ones.

use Yiisoft\Hydrator\Hydrator;

final class UserInput
{
    public string $name = '';
    public int $age = 0;
}

$hydrator = new Hydrator();

$input = $hydrator->create(UserInput::class, [
    'name' => 'Ada',
    'age' => '36', // cast to int by the hydrator
]);

// $input->age === 36

Where to look first:

  • README.md for the quick-start usage and attribute examples.
  • src/Hydrator.php for create() / hydrate() behavior.
  • src/Attribute/Parameter/ for built-in attributes such as Map and Cast.
  • src/TypeCaster/ for casting behavior and custom caster contracts.
  • tests/ for edge cases around missing data, nulls, nested objects, and DI.
  • HydratorFactory when integrating with a PSR-11 container.

First day-to-day use case: map an HTTP request payload into a typed request DTO.

use Yiisoft\Hydrator\Attribute\Parameter\Cast;
use Yiisoft\Hydrator\Attribute\Parameter\Map;

final class CreateUserRequest
{
    #[Map('first_name')]
    public string $firstName = '';

    #[Map('last_name')]
    public string $lastName = '';

    #[Cast('int')]
    public int $age = 0;
}

$request = $hydrator->create(CreateUserRequest::class, $payload);

If your application already has a container, use the package factory so constructor dependencies can be resolved:

use Yiisoft\Hydrator\HydratorFactory;

$hydrator = (new HydratorFactory($container))->create();

Implementation Patterns

Use DTOs at the boundary, not domain entities. The most reliable pattern is to hydrate dedicated request/command DTOs and then map those into domain objects explicitly. This gives you a natural allowlist and avoids mass-assignment surprises.

Hydrate partially for PATCH-style updates. hydrate() works well for updating an existing object from only the fields present in the payload:

$hydrator->hydrate($user, [
    'firstName' => 'Grace',
]);

Design DTO properties with defaults so omitted keys keep their current/default values rather than becoming null.

Map snake_case payloads to camelCase properties. Keep transport naming out of PHP classes:

final class UpdateProfileRequest
{
    #[Map('display_name')]
    public string $displayName = '';

    #[Map('postal_code')]
    public string $postalCode = '';
}

Use constructor promotion for required values. Constructor parameters are hydrated before properties, which is a good fit for invariants:

final class Money
{
    public function __construct(
        #[Cast('int')]
        public int $amount,
        public string $currency = 'USD',
    ) {}
}

Hydrate nested DTOs deliberately. For small, stable structures you can use nesting support:

use Yiisoft\Hydrator\Attribute\Parameter\Nested;

final class OrderInput
{
    #[Nested]
    public CustomerInput $customer;

    /** @var ItemInput[] */
    public array $items = [];
}

For arrays of objects, confirm the version’s nested-array support or hydrate elements explicitly in a small mapper. Do not assume docblock generics alone will always drive runtime hydration.

Write custom casters for dates, enums, and booleans. Built-in casts cover scalar cases; custom casters keep conversion logic testable:

use Yiisoft\Hydrator\Attribute\Parameter\Cast;

final class EventInput
{
    #[Cast(IsoDateCaster::class)]
    public DateTimeImmutable $startsAt;

    #[Cast(StatusCaster::class)]
    public Status $status;
}

Implement the package’s caster interface and check src/TypeCaster/ for the exact method signature in your installed version.

Let DI resolve services and data resolve scalars. A container-aware factory/object factory is useful for command and handler objects:

final class GenerateReport
{
    public function __construct(
        private ReportRepository $reports,       // from container
        #[Map('report_id')] private string $reportId, // from input
    ) {}
}

Keep payload keys and service type names clearly separated, and map payload parameters explicitly when there is any ambiguity.

Reuse the hydrator for bulk rows. Hydrating database rows into read models or test fixtures is straightforward:

$users = array_map(
    static fn(array $row) => $hydrator->create(User::class, $row),
    $rows,
);

Create the hydrator once per request/process, not once per row.

Integrate with validation, not instead of it. Hydration and validation are separate concerns. Cast first only when that is safe for your validation rules; otherwise validate raw input and hydrate after validation.

Gotchas and Tips

  • Hydration is not validation or sanitization. A successfully hydrated DTO can still contain invalid or malicious values. Combine it with yiisoft/validator or your existing validation layer.
  • Missing keys and null are different. A missing key usually leaves a default in place; an explicit null can still cause a TypeError for a non-nullable property. Use nullable types and defaults intentionally.
  • Boolean casting is a common trap. Values like '0', 'false', '', and 'off' do not always mean what you expect. Use a custom boolean caster for form/query data.
  • Constructor parameter names matter. Map on a promoted constructor parameter is often the cleanest fix when payload keys do not match PHP parameter names.
  • Watch attribute placement. Attributes on constructor parameters and attributes on properties are not interchangeable. For promoted properties, test the behavior in your PHP version.
  • Nested arrays of objects need care. #[Nested] handles straightforward object nesting, but arrays of DTOs may require an explicit loop or custom caster. Avoid relying on docblock generics unless your version documents support.
  • Do not hydrate readonly properties on existing objects. Readonly properties are intended to be initialized during construction; use create() with constructor promotion instead of hydrate() for those models.
  • DI errors surface as hydration errors. If a constructor service cannot be resolved, check container registrations and whether you are using the container-aware factory rather than new Hydrator().
  • Avoid hydrating directly into sensitive entities. A DTO with only expected fields is safer than hydrating a rich entity from user input. Treat hydration as a boundary operation.
  • Debug by comparing keys. On failure, log array_keys($data) alongside the target class constructor/property names. Most issues are naming mismatches, missing defaults, or an unexpected null.
  • Catch and translate hydration exceptions. Do not expose raw reflection or type-caster errors to users; map them to a 422/400 response with field-level context where possible.
  • Reuse the hydrator and caches in long-running workers. Reflection metadata is typically cached, but custom casters must remain stateless. Avoid creating a new hydrator for every row or request in RoadRunner/Swoole-style workers.
  • Extension points are the escape hatch. Custom type casters, data extractors, object factories, parameter resolvers, and custom attributes let you adapt the package without forking it. Confirm exact interface names and signatures in src/ for your installed version, since the package API has evolved.
  • Keep complex graphs out of the hydrator. For deeply nested, cyclic, or permission-dependent object graphs, use explicit mappers. The hydrator is strongest for flat DTOs, request models, command objects, and simple nested structures.
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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route