DevCraft Документации
РазработкиDevCraft Dev ToolsGuides

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(): stringPretty JSON с Unicode и незаэкранированными слэшами; при ошибке — JsonException
static setLogger(LoggerInterface $logger): voidПереопределить логгер для сбоев валидации
static resetLogger(): voidСбросить кастомный логгер (тесты / teardown)

В маппинге участвуют только public, non-static свойства. Private/protected mapper игнорирует.

Правила гидратации

ReflectionMapper::hydrate($target, $data) обходит public-свойства:

  1. Отсутствующий ключ или явный null → null fallback.
  2. Значение конвертируется по типу свойства.
  3. #[ArrayOf] постобрабатывает list-свойства.
  4. Атрибуты валидации проверяют сконвертированное значение.
  5. Ошибки логируются и собираются; после прохода непустая карта ошибок → ValidationException.

Конверсия скаляров

Объявленный типПринимаемый ввод
stringтолько string
intint или numeric string через FILTER_VALIDATE_INT
floatfloat, int или numeric string через FILTER_VALIDATE_FLOAT
boolbool, 0/1 или boolean strings через FILTER_VALIDATE_BOOLEAN
arrayarray
objectobject
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_ERROR
  • JSON_UNESCAPED_UNICODE
  • JSON_PRETTY_PRINT
  • JSON_UNESCAPED_SLASHES
  • JSON_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.

На этой странице