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

nutgram/hydrator

nutgram/hydrator is a lightweight PHP library for turning arrays into typed objects, with support for nested DTOs, type casting, and attribute-driven mapping. It integrates smoothly with Nutgram and Laravel-style workflows, reducing boilerplate when working with API payloads and Telegram updates.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer. It has no framework dependencies, so it works in plain PHP, Laravel, Symfony, or inside a Nutgram bot:

composer require nutgram/hydrator

The entry point is a single Hydrator instance. You give it a target class and an associative array (already json_decoded), and it reflects over the class to build the object:

use SergiX44\Hydrator\Hydrator;

$hydrator = new Hydrator();

$payload = json_decode($json, true);

$user = $hydrator->hydrate(User::class, $payload);

Your DTO is a normal PHP class. Properties can be public, typed, readonly, or promoted through the constructor — the hydrator walks the class definition and assigns from matching array keys:

final class User
{
    public string $name;
    public int $age;
    public ?string $bio = null;
}

Where to look first:

  1. README.md for the attribute namespace and the hydrate() signature.
  2. src/Annotation/ (or src/Attributes/) — this is where MapFrom, ArrayType, HydrateWith, etc. live. These attributes are the entire public API surface you'll use day-to-day.
  3. The package tests, which are the most reliable documentation for nested-object and array edge cases.

First real use case: replacing hand-written fromArray() / fromJson() factory methods on API response DTOs. Instead of dozens of $this->foo = $data['foo'] ?? null; lines, the DTO declares its shape and the hydrator does the wiring. This is exactly how Nutgram uses it internally to turn Telegram update JSON into typed update objects, so hydrating webhook payloads is the canonical scenario.

Implementation Patterns

Map non-matching JSON keys with attributes. Snake_case APIs and camelCase DTOs are the common mismatch:

use SergiX44\Hydrator\Annotation\MapFrom;

final class User
{
    #[MapFrom('first_name')]
    public string $firstName;

    #[MapFrom('last_name')]
    public string $lastName;
}

Nested DTOs hydrate automatically. Just type the property as another class; the hydrator recurses when the value is an array:

final class Order
{
    public int $id;
    public User $customer;
    public Address $shippingAddress;
}

Typed arrays need an explicit attribute. PHP can't express User[], so the hydrator can't infer the element type. Use the array-type attribute on list properties:

use SergiX44\Hydrator\Annotation\ArrayType;

final class Order
{
    /** @var OrderItem[] */
    #[ArrayType(OrderItem::class)]
    public array $items;

    #[ArrayType('string')]
    public array $tags = [];
}

Enums and scalars are handled by type declaration. Backed enums can be declared directly on the property and are converted from their backing value. Casts to int, float, bool, and string follow the declared type.

Custom casting via #[HydrateWith]. For value objects (Money, UUID, Carbon dates) implement the package's hydrator contract and reference the class on the property. Keep casters stateless and side-effect free so they're safe to reuse across the whole app:

#[HydrateWith(MoneyHydrator::class)]
public Money $total;

Integration with HTTP clients and Laravel. Wrap hydration in a small helper so controllers and jobs don't repeat json_decode:

function hydrate(string $class, array|string $data, ?Hydrator $hydrator = null): object
{
    $hydrator ??= app(Hydrator::class); // or a static singleton
    return $hydrator->hydrate(
        $class,
        is_string($data) ? json_decode($data, true, flags: JSON_THROW_ON_ERROR) : $data
    );
}

Register the Hydrator as a singleton in the container — reflection metadata is cached per class, and one instance keeps that cache warm across requests in long-running workers (Octane, Swoole, queue workers).

Nutgram bots. Inside a bot handler, incoming updates arrive already hydrated as Nutgram\Telegram\Types\Update objects. Use nutgram/hydrator explicitly when you hydrate your own domain DTOs from callback_data, inline query payloads, or third-party API responses inside a handler — the API is identical, and it keeps your DTO layer consistent with the framework's internals.

Validate after hydrating, not during. The hydrator is a mapper, not a validator. Hydrate into the DTO, then run Laravel's Validator or a value-object constructor check. This separation means a malformed payload fails with a validator error you control, not a TypeError deep in reflection.

Gotchas and Tips

  • Pass arrays, not objects. Decode with json_decode($json, true). Hydrating from stdClass or from a JSON string directly won't work if the package expects arrays.
  • Missing required properties throw. A non-nullable typed property with no corresponding key and no default is an initialization error. Give optional fields an explicit = null default or a nullable type — the hydrator won't invent one.
  • Type coercion is not a validator. A JSON "42" may hydrate into an int property, and 1 into a bool. Don't rely on hydration to reject loosely-typed API data; validate if it matters.
  • Nested arrays vs. objects are different code paths. #[ArrayType(Foo::class)] handles a list of objects; a single nested Foo handles an object. Forgetting ArrayType gives you arrays of raw arrays (or a type error), not Foo[]. This is the number-one source of confusion.
  • Keys are matched literally before attributes apply. #[MapFrom] overrides the property name, but if both user_name and userName exist in the payload, be explicit about which one wins.
  • Nullable and union types are ambiguous. For ?Foo, the hydrator can't know whether a null key should skip hydration or produce null; test both the "key present with null" and "key absent" cases.
  • Reflection cache and long-running processes. Cache is per-class per-hydrator instance. If you instantiate a fresh Hydrator per request you lose the benefit; bind it as a singleton.
  • Debugging hydration failures. The exception message usually names the class and property. Bisect by hydrating sub-DTOs individually, and temporarily set JSON_THROW_ON_ERROR on decode so JSON syntax errors aren't mistaken for hydration errors.
  • Extension points are the attributes, not inheritance. Prefer a custom #[HydrateWith] caster over subclassing the hydrator. Custom casters compose and are registered per-property, so they're easy to test in isolation.
  • Pin the version. With a small package and a low star count, minor releases can rename or relocate attributes. Lock it in composer.json and read the changelog before upgrading; the attribute namespace is the part most likely to move.
  • Not a replacement for a schema library. If you need defaults, aliases, discriminator-based polymorphism, and validation in one pass, you'll be layering more and more attributes. For simple, well-shaped payloads the hydrator is lean and fast; for complex polymorphic APIs, consider Symfony Serializer and keep this package for the straightforward DTOs.
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.
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
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource