Reflection mapper v1.0.0
Гидратация и сериализация DTO через AbstractReflection и ReflectionMapper.
AbstractReflection — базовый класс для DTO с public typed свойствами. Даёт fromArray(), toArray() и toJson(). Гидратацию и сериализацию реализует ReflectionMapper.
Быстрый старт
use Devcraft\Abstracts\AbstractReflection;
use Devcraft\Attributes\ArrayOf;
use Devcraft\Attributes\Range;
final class Address extends AbstractReflection
{
public string $city;
}
final class Profile extends AbstractReflection
{
public string $name;
public Address $address;
#[ArrayOf(Address::class)]
public array $locations = [];
#[Range(min: 0)]
public int $score = 0;
}
$profile = Profile::fromArray([
'name' => 'Ada',
'address' => ['city' => 'Berlin'],
'locations' => [
['city' => 'Berlin'],
['city' => 'Paris'],
],
'score' => '10',
]);
$profile->toArray();
$profile->toJson();Канонический пример Proxy / Address:
use Devcraft\Abstracts\AbstractReflection;
use Devcraft\Attributes\ArrayOf;
use Devcraft\Attributes\Range;
use Devcraft\Attributes\Regex;
final class Address extends AbstractReflection
{
public string $city;
}
final class Proxy extends AbstractReflection
{
#[Regex('/^[0-9a-f-]{36}$/i')]
public string $id;
#[Range(min: 1, max: 65535)]
public int $port;
public Address $address;
#[ArrayOf(Address::class)]
public array $locations = [];
}
$proxy = Proxy::fromArray([
'id' => '550e8400-e29b-41d4-a716-446655440000',
'port' => '8080',
'address' => ['city' => 'Berlin'],
'locations' => [
['city' => 'Berlin'],
['city' => 'Paris'],
],
]);
$proxy->port; // int 8080 (string coerced)
$proxy->address->city; // 'Berlin'
$proxy->toArray(); // nested arrays
echo $proxy->toJson(); // pretty-printed JSONСм. также: AbstractReflection, ReflectionMapper, ArrayOf.
API AbstractReflection
| Метод | Описание |
|---|---|
static fromArray(array $data): static | Создать экземпляр и гидратировать public-свойства |
toArray(): array | Экспорт инициализированных public-свойств (вложенные DTO рекурсивно) |
toJson(): string | Pretty JSON с Unicode и незаэкранированными слэшами; при ошибке — JsonException |
static setLogger(LoggerInterface $logger): void | Переопределить логгер для сбоев валидации |
static resetLogger(): void | Сбросить кастомный логгер (тесты / teardown) |
В маппинге участвуют только public, non-static свойства. Private/protected mapper игнорирует.
Правила гидратации
ReflectionMapper::hydrate($target, $data) обходит public-свойства:
- Отсутствующий ключ или явный
null→ null fallback. - Значение конвертируется по типу свойства.
#[ArrayOf]постобрабатывает list-свойства.- Атрибуты валидации проверяют сконвертированное значение.
- Ошибки логируются и собираются; после прохода непустая карта ошибок →
ValidationException.
Конверсия скаляров
| Объявленный тип | Принимаемый ввод |
|---|---|
string | только string |
int | int или numeric string через FILTER_VALIDATE_INT |
float | float, int или numeric string через FILTER_VALIDATE_FLOAT |
bool | bool, 0/1 или boolean strings через FILTER_VALIDATE_BOOLEAN |
array | array |
object | object |
mixed / без типа | что угодно |
Union и intersection типы свойств не поддерживаются и падают при конверсии.
Вложенные DTO
Если тип свойства — подкласс AbstractReflection, а вход — array, mapper создаёт вложенный класс и гидратирует его с dotted path (address.city, locations.0.city).
Если вход уже экземпляр объявленного типа (или интерфейса) — принимается as-is.
Null fallback
Когда ключ отсутствует или значение null:
- свойства с default сохраняют default
- nullable без usable value становятся
null - обязательные non-nullable без default получают
is required
При сбое конверсии/валидации у nullable свойства mapper ставит null вместо добавления пути в thrown error map (сбой всё равно логируется).
#[ArrayOf]
ArrayOf — и подсказка конверсии для mapper, и ValidationRule.
При конверсии значение должно быть list (array_is_list). Каждый элемент конвертируется в объявленный тип (builtin или вложенный AbstractReflection).
#[ArrayOf('int')]
public array $ids = [];
#[ArrayOf(Address::class)]
public array $locations = [];Ассоциативные массивы падают с must be a list. Ошибки элементов — ids.1, locations.0.city и т.д.
Сериализация
toArray() включает только инициализированные public-свойства. Вложенные AbstractReflection раскрываются рекурсивно. Обычные массивы обходятся поэлементно.
toJson() кодирует toArray() с:
JSON_THROW_ON_ERRORJSON_UNESCAPED_UNICODEJSON_PRETTY_PRINTJSON_UNESCAPED_SLASHESJSON_INVALID_UTF8_SUBSTITUTE
Логирование
По умолчанию AbstractReflection использует Analog\Logger. Вызовите setLogger() для тестов или прикладного логирования:
AbstractReflection::setLogger($psrLogger);Каждый сбой логируется на error с ключами контекста class, property (dotted path), expected, actual_type.
Прямое использование mapper
Можно гидратировать существующий объект без fromArray():
use Devcraft\Mapper\ReflectionMapper;
use Psr\Log\NullLogger;
$mapper = new ReflectionMapper(new NullLogger());
$mapper->hydrate($profile, $payload);Второй аргумент конструктора — опциональный кастомный PropertyValidator.