DevCraft Документации
Руководства

Маппер по reflection v1.1.0

DTO: fromArray, toArray, toJson через AbstractReflection и ReflectionMapper.

Введение

AbstractReflection — базовый класс для DTO с публичными типизированными свойствами. Даёт fromArray(), toArray() и toJson(). Заполнение и выгрузку делает ReflectionMapper.

Зачем: принять массив с API или формы, проверить поля атрибутами валидации, отдать обратно массив или JSON — без ручного присваивания каждого поля.

Предварительные требования

Пример

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'],
    ],
    '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Красивый JSON с Unicode; при ошибке — JsonException
static setLogger(LoggerInterface $logger): voidСвой логгер для сбоев валидации
static resetLogger(): voidСбросить логгер (тесты)

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

Правила заполнения

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

  1. Нет ключа или явный null → запасной путь для null (см. ниже).
  2. Значение приводится к типу свойства.
  3. #[ArrayOf] обрабатывает списки.
  4. Атрибуты валидации проверяют уже приведённое значение.
  5. Ошибки собираются; если карта ошибок не пуста → ValidationException.

Скаляры

Объявленный типЧто принимают
stringтолько string
intint или числовая строка (FILTER_VALIDATE_INT)
floatfloat, int или числовая строка (FILTER_VALIDATE_FLOAT)
boolbool, 0/1 или булевы строки (FILTER_VALIDATE_BOOLEAN)
arrayarray
objectobject
mixed / без типачто угодно

Объединения и пересечения типов свойств не поддерживаются — падают при приведении.

Вложенные DTO

Если тип свойства — наследник AbstractReflection, а на входе массив, mapper создаёт вложенный класс и заполняет его. Путь ошибки — через точку (address.city, locations.0.city).

Если на входе уже экземпляр нужного типа (или интерфейса) — принимается как есть.

Запасной путь для null

Когда ключа нет или значение null:

  • свойства с default оставляют default;
  • nullable без пригодного значения становятся null;
  • обязательные non-nullable без default получают ошибку «is required».

При сбое приведения/валидации у nullable свойства mapper ставит null вместо добавления пути в выброшенную карту ошибок (сбой всё равно пишется в лог).

#[ArrayOf]

ArrayOf — и подсказка для преобразования, и правило валидации.

Значение должно быть списком (array_is_list). Каждый элемент приводится к объявленному типу (встроенный или вложенный 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

Логирование

По умолчанию — Analog\Logger. Для тестов или своего лога:

AbstractReflection::setLogger($psrLogger);

Каждый сбой пишется с уровнем error и контекстом: class, property (путь через точку), expected, actual_type.

Прямой вызов mapper

Можно заполнить уже созданный объект без fromArray():

use Devcraft\Mapper\ReflectionMapper;
use Psr\Log\NullLogger;

$mapper = new ReflectionMapper(new NullLogger());
$mapper->hydrate($profile, $payload);

Второй аргумент конструктора — необязательный свой PropertyValidator.

Связанные разделы

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