karelwintersky / arris.entity
Entity types for Arris µFramework
Requires
- php: ^8.2
- ext-json: *
Requires (Dev)
- phpunit/phpunit: ^10.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Entity types for Arris µFramework. Version 2.x, requires PHP ^8.2.
composer require karelwintersky/arris.entity
Состав пакета
| Класс | Назначение | Когда нужен |
|---|---|---|
Arris\Entity\Result |
Контейнер результата операции (успех/ошибка, сообщения, код, данные) | Почти всегда — основной класс пакета |
Arris\Entity\Helper\Dot |
Хранилище данных с доступом через точку | Внутри Result, но пригоден и сам по себе |
Arris\Entity\Traits\ToArray |
Трейт toArray()/toJSON() |
Для своих entity/DTO-классов |
Arris\Entity\Traits\ArrayToEntityTrait |
Трейт «массив ⇄ объект» | Для своих entity/DTO-классов |
Удалено в 2.0:
Arris\Entity\ValueиArris\Entity\Contextбольше не входят в пакет — они не использовались ни в одной из экосистемных репозиториев, их место заняли нативные касты PHP иHelper\Dotсоответственно.
Entity\Result
Что это
Контейнер, в котором склеены четыре независимых сущности:
- состояние — флаги
is_success/is_error; - сообщения — одиночное
messageи списокmessages; - код —
code(строка или int, часто код ошибки); - данные — репозиторий
dataс dot-доступом (реализован наHelper\Dot).
Благодаря этому один объект уходит из слоя бизнес-логики наружу (контроллер, CLI, API) и несёт весь ответ: «что случилось, почему, с каким кодом и какие данные».
Реализует ArrayAccess и JsonSerializable, поэтому работает и как массив ($r['key']),
и как объект ($r->key), и сериализуется в JSON.
Публичные свойства
$r->is_success // bool — успех $r->is_error // bool — ошибка (инверсия is_success) $r->message // string — одиночное сообщение $r->messages // string[] — список сообщений $r->code // string|int — код $r->data // Dot — репозиторий данных // «быстрые» поля-заглушки, чтобы не заводить отдельные DTO: $r->raw // mixed $r->raw_int // int $r->raw_string // string $r->raw_array // array $r->raw_bool // bool $r->raw_object // stdClass
Поля raw_* — это просто типизированные полочки на случай, когда нужно быстро положить
значение в результат без возни с data/set().
API
Создание
$r = new Result(); // успех по умолчанию $r = new Result(false); // сразу ошибка $r = new Result(false, 'failure'); // ошибка + сообщение
Состояние
$r->setState(true|false); // аналог конструктора, но у существующего экземпляра $r->getState(); // bool $r->success('ok'); // флаги в true, опционально сообщение $r->error('boom'); // флаги в false, опционально сообщение
success()/error() возвращают $this — можно строить цепочки.
Если сообщение передано пустым — старое сообщение не затирается (осознанное поведение).
Сообщения
$r->setMessage('value is [%s]', ['x']); // vsprintf-подстановка аргументов $r->getMessage(); // string $r->addMessage('%s => %s', [1, 2]); // добавить к списку $r->getMessages(); // ['1 => 2', ...] $r->getMessages(true); // '[1 => 2,4 => 5]' — склейка + скобки $r->getMessages(true, ' ; ', []); // '1 => 2 ; 4 => 5' — без скобок
У setMessage и addMessage аргументы для sprintf передаются массивом
(вторым параметром). У success()/error() — вариадиком: $r->success('x %s', 'y').
Код
$r->setCode(500); // int или string $r->setCode('E_NOT_FOUND'); $r->getCode(); // string|int
Одиночные ключи
$r->set('a', 'b'); // динамическое свойство $r->a $r->get('a'); // сначала ищет свойство, потом data $r->has('a'); // bool $r->a; // прямое чтение тоже работает
Порядок поиска в get()/has()/__get(): сначала объявленные/динамические свойства,
потом ключ в data. NB: не используйте ключи code и data — они резервируются свойствами.
Данные (dot-доступ)
$r->setData('time.total', 42); // вложенность через точку $r->getData('time.total'); // 42 $r->getData('time'); // ['total' => 42] $r->getData(); // весь массив данных $r->setData(['a' => 1, 'b' => 2]); // пачкой $r->addData('thumbnails', [['id' => 1]]); // ДОБАВИТЬ элемент в массив по ключу $r->addData('thumbnails', [['id' => 2]]); // ключ теперь [[id 1],[id 2]]
Ключевое различие: setData — установить значение по ключу (перезапишет),
addData — дописать значение в массив, лежащий по ключу (внутри это Dot::merge).
Сериализация
$json = $r->serialize(); // JSON-строка (алиас: asJSON()) $json = $r->asJSON(); $copy = Result::fromJSON($json); // обратно в Result $array = $r->jsonSerialize(); // для json_encode($r)
serialize() бросает JsonException, если данные не сериализуются в JSON
(в 1.x могла молча вернуть false).
json_encode($r) использует jsonSerialize(), который включает блок raw:
{
"is_success": true,
"is_error": false,
"message": "...",
"code": "",
"messages": [],
"data": { ... },
"raw": { "_": null, "bool": false, "int": 0, "string": "", "array": [], "object": {} }
}
ArrayAccess
$r['key'] = 'value'; // эквивалент set() isset($r['key']); // эквивалент has() $val = $r['key']; // эквивалент get() unset($r['key']);
Пример полного цикла
$result = (new Result()) ->setData('user.id', 42) ->addMessage('loaded in %d ms', [12]) ->setCode(200); $result->success('user loaded'); if ($result->getState()) { $id = $result->getData('user.id'); } header('Content-Type: application/json'); echo $result->serialize();
Entity\Helper\Dot
Что это
Хранилище вложенных данных, где к любому уровню можно обратиться через путь, разделённый
точкой: $dot->get('user.profile.name'). Именно на нём держится Result::data.
Внутри — массив + разбор пути. Поддерживает ArrayAccess, Countable,
IteratorAggregate, JsonSerializable.
Создание
$d = new Dot(); // пустое хранилище $d = new Dot(['a' => 1]); // из массива (плоского) $d = new Dot(['user.name' => 'John'], true); // parse=true — развернуть dot-ключи $d = new Dot(['a' => 1], false, '/'); // кастомный разделитель
Разделитель задаётся в конструкторе и применяется во всех операциях.
API — чтение
$d->get(); // весь массив $d->get('a'); // значение по ключу $d->get('user.name'); // вложенность через точку $d->get('missing', 0); // с дефолтом (нет ключа — вернётся дефолт) $d->all(); // весь массив (алиас get() без аргументов) $d->has('user.name'); // bool — есть ли ключ (в т.ч. глубокий) $d->isEmpty(); // пусто ли всё хранилище $d->isEmpty('a'); // пусто ли значение по ключу $d->count(); // int — количество элементов count($d); // то же (Countable) $d->flatten(); // ['user.name' => 'John', ...] — развернуть в плоский
API — запись
$d->set('a', 1); // установить значение $d->set('user.name', 'John'); // создаст вложенные уровни $d->set(['a' => 1, 'b' => 2]); // пачкой $d->add('a', 1); // установить ТОЛЬКО если ключа ещё нет $d->merge('list', [1]); // слить массив: list = [старое..., новое] $d->mergeRecursive('a', [...]); // рекурсивное слияние (дубли → массивы) $d->mergeRecursiveDistinct(...); // рекурсивно, но новое значение перетирает старое $d->push('list', 3); // добавить элемент в конец массива по ключу $d->push(4); // без ключа — просто в конец хранилища $d->replace('a', [...]); // array_replace по ключу $d->delete('a'); // удалить ключ $d->delete(['a', 'b']); // пачкой $d->clear(); // очистить всё $d->clear('a'); // очистить значение по ключу $d->pull('a'); // взять значение И удалить ключ
Работа со всем массивом
$d->setArray(['x' => 1]); // заменить всё содержимое $d->setReference($array); // работать с внешним массивом ПО ССЫЛКЕ $d->toJson(); // json всего хранилища $d->toJson('user'); // json значения по ключу
setReference — удобно, когда массив живёт снаружи (например, в объекте-холдере),
а Dot нужен только ради dot-доступа к нему без копирования.
Пример
$d = new Dot(); $d->set('user.name', 'John'); $d->set('user.roles', ['admin']); $d->has('user.roles'); // true $d->get('user'); // ['name' => 'John', 'roles' => ['admin']] $d->push('user.roles', 'editor'); // ['admin', 'editor'] foreach ($d as $key => $value) { … } // Iterate через ArrayIterator
Трейты
Arris\Entity\Traits\ToArray
Конвертация объекта в массив/JSON с фильтрацией свойств. Подключается в свой entity-класс:
use Arris\Entity\Traits\ToArray; class User { use ToArray; public int $id = 1; public string $name = 'foo'; protected string $password_hash = '…'; public array $roles = ['admin']; } $user = new User(); $user->toArray(); // все свойства $user->toArray(['id', 'name']); // только указанные $user->toArray(excluded: ['password_hash']); // все, кроме исключённых $user->toJSON(); // JSON тех же данных $user->toJSON(true); // pretty-print
Правила:
$excludedимеет приоритет над$included;get_object_vars($this)вызывается в контексте класса — поэтому в результат попадают иpublic, иprotected/privateсвойства этого класса (следите, чтобы секреты вродеpassword_hashпопали в$excluded, либо объявляйте их вне класса);- значения-объекты, у которых есть метод
toArray(), рекурсивно конвертируются; вложенные массивы обрабатываются рекурсивно; toJSON()бросаетRuntimeException, еслиjson_encodeне удался.
Arris\Entity\Traits\ArrayToEntityTrait
Трейт «массив ⇄ объект»: заполняет объект из массива и хранит данные во внутреннем репозитории, обращение — через магические методы. Динамических свойств не создаёт (безопасен для PHP 8.2, никаких deprecation):
use Arris\Entity\Traits\ArrayToEntityTrait; class Post { use ArrayToEntityTrait; } $post = Post::fromArray([ 'id' => 1, 'title' => 'Hello', ]); $post->title; // 'Hello' (через __get) isset($post->title); // true (через __isset) $post->new_field = 'x';// запись через __set $post->toArray(); // ['id' => 1, 'title' => 'Hello', 'new_field' => 'x']
Особенности:
- ассоциативный массив → каждый ключ доступен как свойство (
$post->title); - список (list) → элементы складываются во внутренний массив
_(обращаться к ним как к свойствам нельзя); toArray()возвращает ровно те данные, что были переданы вfromArray()(плюс всё, что было записано через__set);- в отличие от
Result::set(), здесь ключи не создают динамических свойств — всё живёт во внутреннем репозитории, поэтомуget_object_vars()не «протечёт» наружу.
Тесты
composer install make test # или: php ./vendor/bin/phpunit
Покрыты: Result, Dot, оба трейта. Сейчас 46 тестов / 116 assertion.
Изменения в 2.0
- Требуется PHP ^8.2, код для PHP 7.4 удалён
Result: убрана реализация устаревшего\Serializable; JSON-сериализация черезserialize()/fromJSON()/jsonSerialize();serialize()теперь бросаетJsonExceptionвместо молчаливогоfalse; типизированы свойства и сигнатуры (static,mixed, union-типы); добавленdeclare(strict_types=1)- Удалены
Arris\Entity\ValueиArris\Entity\Context— не использовались нигде в экосистеме; вместо них нативные касты PHP иHelper\Dot ArrayToEntityTraitпереехал из корня репозитория в пакет (Arris\Entity\Traits\ArrayToEntityTrait), динамические свойства заменены на__set- PHPUnit обновлён до ^10, тесты переведены на namespace + строгие сигнатуры,
добавлено покрытие
Dotи трейтов