Languages: 🇬🇧 English | 🇷🇺 Русский
The package hydrates object properties from arrays and JSON with casting to declared PHP types. Individual values can also be cast without hydrating an object.
composer require sunrise/hydratorThe package supports PHP 7.4 and newer. Examples in this README target PHP 8+.
use Sunrise\Hydrator\Annotation\Filter;
use Sunrise\Hydrator\Annotation\ItemType;
use Sunrise\Hydrator\Hydrator;
final class CreateUserRequest
{
#[Filter('trim')]
public string $email;
public bool $isActive;
#[ItemType('string', limit: 10)]
public array $roles = [];
}
$request = (new Hydrator())->hydrate(CreateUserRequest::class, [
'email' => ' user@example.com ',
'isActive' => 'true',
'roles' => ['user', 'editor'],
]);The hydrator:
- creates an object without calling its constructor or fills an existing object;
- skips properties marked with
#[Ignore]; - ignores input keys that do not match any property;
- hydrates nested objects recursively;
- collects property and element hydration errors into one
InvalidDataException.
An input value is required when the corresponding property is not initialized and no default value is defined for it. A default value can be defined in the property declaration, in the constructor parameter with the same name, or through #[DefaultValue].
Default values are used only when the input key is missing. A passed null is treated as an input value and is validated against the property type.
$request = (new Hydrator())->hydrateWithJson(
CreateUserRequest::class,
'{"email":"user@example.com","isActive":true}'
);The root JSON value must be an object or an array.
use Sunrise\Hydrator\Dictionary\BuiltinType;
use Sunrise\Hydrator\Hydrator;
use Sunrise\Hydrator\Type;
$value = (new Hydrator())->castValue(
'42',
Type::fromName(BuiltinType::INT)
);| PHP type | Accepted input values |
|---|---|
mixed |
Any value |
bool |
bool or the strings 1, 0, true, false, yes, no, on, off |
int |
int or a string representation of an integer |
float |
float, int, or a string representation of a number |
string |
string or int |
array |
array or stdClass |
DateTimeImmutable and subclasses |
A string in the configured format; an integer or string for format U |
DateInterval |
A string accepted by the DateInterval constructor |
DateTimeZone |
A timezone identifier |
| A concrete backed enum | A value of the corresponding backing type; for int, a string representation of an integer is also accepted |
| An instantiable user-defined class | array or stdClass |
A class implementing ArrayAccess |
array or stdClass |
Also supported:
| Type | Package |
|---|---|
A subclass of MyCLabs\Enum\Enum |
myclabs/php-enum |
Ramsey\Uuid\UuidInterface |
ramsey/uuid |
Subclasses of Symfony\Component\Uid\AbstractUid |
symfony/uid |
null is accepted only for a type that allows null.
For booleans, numbers, dates, intervals, timezones, enums, UUIDs, and UIDs, an empty string after trimming is treated as a missing value: null is returned for a nullable type; otherwise an InvalidValueException is thrown.
Union and intersection types are not supported.
#[ItemType] defines the type of array or collection elements and optionally limits their count:
use Sunrise\Hydrator\Annotation\ItemType;
final class OrderRequest
{
#[ItemType(OrderItemRequest::class, limit: 100)]
public array $items;
}Each element will be cast to the declared type. The allowsNull parameter allows null values in elements:
#[ItemType('int', allowsNull: true, limit: 100)]
public array $ids;For an instantiable class implementing ArrayAccess, the element type can also be inferred from the type of the last constructor parameter declared with ...:
final class UserCollection extends ArrayObject
{
public function __construct(User ...$users)
{
parent::__construct($users);
}
}The constructor is not called during hydration: the hydrator only reads the type of its last variadic parameter and creates the object without the constructor. An explicit #[ItemType] takes precedence over the constructor parameter type.
If the collection throws OverflowException while adding an element, the hydrator treats it as an element limit overflow and returns an array overflow error to the user.
The hydrator can read the element type of an array property from @var:
final class OrderRequest
{
/** @var list<OrderItemRequest> */
public array $items;
}
$hydrator = new Hydrator(isDocBlockReaderEnabled: true);PHPDoc reading is disabled by default. #[ItemType] takes precedence over @var.
@var is not used for classes implementing ArrayAccess. Use #[ItemType] or the type of the variadic constructor parameter instead.
| Attribute | Purpose |
|---|---|
#[Alias('external-name')] |
Defines the input key for a property |
#[Context([...])] |
Defines or overrides context values for a property or parameter |
#[DefaultValue(...)] |
Defines a value used when the input key is missing |
#[Filter(...)] |
Transforms the input value before type casting |
#[Format('...')] |
Defines the date and time format |
#[Ignore] |
Excludes a property from hydration |
#[ItemType(...)] |
Defines the element type and maximum element count |
#[Filter] can be used more than once. Filters are applied in sequence:
use Sunrise\Hydrator\Annotation\Filter;
#[Filter('trim')]
#[Filter('strtolower')]
public string $email;#[Filter] is available only on PHP 8.0 and newer.
By default, DateTimeImmutable uses the DateTimeInterface::RFC3339_EXTENDED format.
Format and timezone can be configured for a hydrator instance:
use DateTimeInterface;
use Sunrise\Hydrator\Dictionary\ContextKey;
use Sunrise\Hydrator\Hydrator;
$hydrator = new Hydrator(context: [
ContextKey::TIMESTAMP_FORMAT => DateTimeInterface::RFC3339_EXTENDED,
ContextKey::TIMEZONE => 'Europe/Belgrade',
]);Operation-specific context is passed to hydrate() or castValue(). Use #[Context] and #[Format] for a property or parameter:
use Sunrise\Hydrator\Annotation\Context;
use Sunrise\Hydrator\Annotation\Format;
use Sunrise\Hydrator\Dictionary\ContextKey;
#[Format('Y-m-d')]
#[Context([ContextKey::TIMEZONE => 'UTC'])]
public DateTimeImmutable $date;Precedence order:
- attribute context;
- operation context;
- hydrator context.
hydrate() collects individual value errors into InvalidDataException:
use Sunrise\Hydrator\Exception\InvalidDataException;
try {
$request = $hydrator->hydrate(CreateUserRequest::class, $data);
} catch (InvalidDataException $e) {
foreach ($e->getExceptions() as $error) {
echo $error->getPropertyPath() . ': ' . $error->getMessage() . PHP_EOL;
}
}InvalidValueException provides:
getPropertyPath()— dot-separated value path;getErrorCode()— error code;getMessage()— resolved message;getMessageTemplate()— message template;getMessagePlaceholders()— template parameters;getInvalidValue()— original value;getTranslationDomain()— translation domain.
Method exceptions:
hydrate()throwsInvalidDataExceptionfor input value errors andInvalidObjectExceptionwhen the object cannot be created or a property has an unsupported type;hydrateWithJson()additionally usesInvalidDataExceptionfor JSON decoding errors and an invalid root value;castValue()throwsInvalidValueExceptionfor a single invalid value,InvalidDataExceptionfor nested object or array errors, andInvalidObjectExceptionfor an unsupported target type.
If symfony/validator is installed, InvalidValueException::getViolation() and InvalidDataException::getViolations() return violations in the Symfony Validator format.
A converter implements TypeConverterInterface. If the type is not supported, the method returns without a result. If it is supported, it yields the result or throws an exception.
use Generator;
use Sunrise\Hydrator\Exception\InvalidValueException;
use Sunrise\Hydrator\Type;
use Sunrise\Hydrator\TypeConverterInterface;
final class MoneyTypeConverter implements TypeConverterInterface
{
public function castValue(
$value,
Type $type,
array $path,
array $context
): Generator {
if ($type->getName() !== Money::class) {
return;
}
if (!is_string($value)) {
throw InvalidValueException::mustBeString($path, $value);
}
yield Money::fromString($value);
}
public function getWeight(): int
{
return 100;
}
}A converter can be passed to the constructor or added later:
$hydrator = new Hydrator(typeConverters: [
new MoneyTypeConverter(),
]);
$hydrator->addTypeConverter(new MoneyTypeConverter());Converters are called in descending weight order. Implement HydratorAwareInterface or AnnotationReaderAwareInterface if a converter needs access to the hydrator or the annotation reader.
On PHP 8.0 and newer, attributes are read automatically. On PHP 7.4, Doctrine Annotations can be used for Alias, Context, DefaultValue, Format, Ignore, and ItemType:
composer require doctrine/annotationsuse Sunrise\Hydrator\AnnotationReader\DoctrineAnnotationReader;
$hydrator->setAnnotationReader(DoctrineAnnotationReader::default());On PHP 7, only DateTimeImmutable itself is supported, not its subclasses. Built-in enums are supported starting with PHP 8.1.
Deprecated names #[Subtype] and #[Relationship] are kept for backward compatibility. Use #[ItemType] in new code.


