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

Easy Hydrator Laravel Package

symplify/easy-hydrator

Symplify Easy Hydrator turns arrays into typed PHP objects via native attributes and constructor promotion. Map nested objects, arrays of objects, and custom keys without boilerplate or reflection code. A lightweight alternative to heavy serializers for clean DTO mapping in Laravel, Symfony, and pla...

View on GitHub
Deep Wiki
Context7

Getting Started

Install and verify the baseline. This package is framework-agnostic and ships no Laravel service provider, so installation is just Composer:

composer require symplify/easy-hydrator

Before adopting it, confirm your PHP version (the package targets PHP 7.4+ typed properties) and note the repository location: it lives under deprecated-packages/easy-hydrator, so treat it as a utility you may later replace rather than a long-term core dependency.

Where to look first. Open EasyHydrator\EasyHydrator and read its hydrate(string $class, array $data) method. The whole package is essentially reflection over the target class constructor: parameter names become array keys, and parameter types drive recursion (a class-typed parameter is hydrated from a nested array). Once you understand that contract, everything else is predictable.

Minimal first use case — request input to a DTO. The highest-value daily use in Laravel is replacing manual new FooDto(...) boilerplate in controllers with a single hydration call against validated input:

use EasyHydrator\EasyHydrator;

final class CreateUserDto
{
    public function __construct(
        public string $name,
        public string $email,
        public int $age,
    ) {}
}

// In a controller:
public function store(StoreUserRequest $request): JsonResponse
{
    $dto = (new EasyHydrator())->hydrate(
        CreateUserDto::class,
        $request->validated()
    );

    $this->createUserAction->execute($dto);

    return response()->json(['ok' => true], 201);
}

The keys in validated() must match constructor parameter names exactly (name, email, age above). That one-to-one contract is the package's entire value proposition and its main source of friction.

Implementation Patterns

Register a singleton and inject it. Reflection is not free; reuse one instance rather than constructing it per request in hot paths.

// AppServiceProvider::register()
$this->app->singleton(\EasyHydrator\EasyHydrator::class);

Then inject EasyHydrator $hydrator into controllers, actions, or a base controller helper:

abstract class Controller extends BaseController
{
    protected function hydrate(string $class, array $data): object
    {
        return app(\EasyHydrator\EasyHydrator::class)->hydrate($class, $data);
    }
}

Nested DTOs hydrate recursively. A class-typed constructor parameter is hydrated from the corresponding nested array, which makes it a good fit for grouped payloads:

final class OrderDto
{
    public function __construct(
        public string $id,
        public AddressDto $billing,
        public AddressDto $shipping,
    ) {}
}

$order = $hydrator->hydrate(OrderDto::class, [
    'id' => 'ORD-1',
    'billing'  => ['street' => '...', 'city' => '...'],
    'shipping' => ['street' => '...', 'city' => '...'],
]);

Normalize keys before hydrating. Because there is no automatic camelCase mapping, snake_case API payloads (first_name) will not match $firstName. Handle this in one place — a small mapper, or a FormRequest that renames keys — instead of renaming constructor parameters to match the wire format:

$payload = collect($request->validated())
    ->mapWithKeys(fn ($v, $k) => [Str::camel($k) => $v])
    ->all();

$dto = $hydrator->hydrate(CreateUserDto::class, $payload);

FormRequest as the validation boundary, hydrator as the mapping layer. Validate first (types, required fields, nested arrays), then hydrate. This keeps the hydrator doing what it does best — mapping — and keeps untrusted input out of reflection-based coercion.

Use it for config/value objects too. Hydrating from config('services.foo') into a typed DTO in a service provider is a clean way to remove ad-hoc array access and typo risk:

$this->app->singleton(PaymentConfig::class, fn () => (new EasyHydrator())
    ->hydrate(PaymentConfig::class, config('services.payment')));

Keep DTOs final, typed, and immutable. Constructor-only injection pairs naturally with read-only DTOs. On PHP 8.1+, readonly promoted properties give you hydration plus immutability with no extra code.

Test the contract, not the reflection. A small factory-style test that hydrates the DTO from a representative array catches key renames immediately:

it('hydrates from validated payload', function () {
    $dto = (new EasyHydrator())->hydrate(CreateUserDto::class, [
        'name' => 'Ada', 'email' => '[email protected]', 'age' => 36,
    ]);
    expect($dto->name)->toBe('Ada');
});

Avoid it for queued job constructors. Laravel serializes job properties; passing a hydrated DTO into a job constructor forces serialization concerns on classes the package does not know how to rebuild. Prefer hydrating inside the job from a plain array payload, or pass an Eloquent identifier and resolve.

Gotchas and Tips

Deprecation is the headline risk. The repository has moved to deprecated-packages, with no active maintenance guarantees and no security response. Before wiring it into a long-lived codebase, evaluate replacements: spatie/laravel-data (Laravel-native, validation + casts + transformers), cuyz/valinor (powerful, framework-agnostic, better error reporting), or plain new Dto(...) for small DTOs where the boilerplate cost is low. The hydrator works well as a transitional tool, but plan an exit.

Parameter names must match array keys exactly. first_name will not populate $firstName; you'll get a missing-data error rather than silently wrong data. Centralize key mapping and test it — a rename in a DTO constructor is a runtime break, not a compile-time one.

No automatic casting for DateTime, Carbon, enums, or Eloquent models. The package predates enums and does not know about DateTimeInterface. Supply already-constructed values or accept strings in the DTO and convert in the domain layer. Similarly, convert Eloquent models and collections with ->toArray() before hydrating.

Nullable and defaulted parameters need intent. A null payload value is only acceptable where the parameter type is nullable (?string). Required parameters without a corresponding key will fail — verify how your installed version handles missing optional keys before relying on defaults, and cover it with a test.

Arrays of objects depend on docblock annotations. For AddressDto[] $addresses, the hydrator needs the element type from an @param AddressDto[] $addresses docblock. Malformed or missing docblocks produce confusing failures, so treat the annotation as part of the DTO's public contract.

Nested hydration expects arrays. Passing an already-constructed AddressDto where the hydrator expects a nested array is a common mismatch. Decide at the boundary whether values are "arrays in" or "objects in" and stay consistent.

Error messages are low-level, not field-aware. Failures surface as reflection/missing-data exceptions without the field-level context Laravel developers expect. Wrap hydration in a try/catch and translate to ValidationException (or a domain exception) so API clients get structured 422 responses instead of a 500:

try {
    $dto = $hydrator->hydrate(CreateUserDto::class, $data);
} catch (\Throwable $e) {
    throw ValidationException::withMessages(['payload' => $e->getMessage()]);
}

Do not hydrate unvalidated request data. Reflection-based coercion can accept values you did not intend. Always hydrate from $request->validated() or an equivalent whitelisted array.

No cycle detection. Self-referencing structures (parent/child trees) will recurse until failure. Hydrate leaf DTOs first and assemble the tree yourself.

No auto-discovery or config. There is nothing to publish, no config to cache-bust, and no facade. Register the instance in the container and keep a single injection point; if you later swap to spatie/laravel-data, that single seam is where the migration happens.

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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels