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соответственно.
Контейнер, в котором склеены четыре независимых сущности:
- состояние — флаги
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().
$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 — они резервируются свойствами.
$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": {} }
}$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();Хранилище вложенных данных, где к любому уровню можно обратиться через путь, разделённый
точкой: $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, '/'); // кастомный разделительРазделитель задаётся в конструкторе и применяется во всех операциях.
$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', ...] — развернуть в плоский$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Конвертация объекта в массив/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не удался.
Трейт «массив ⇄ объект»: заполняет объект из массива и хранит данные во внутреннем репозитории, обращение — через магические методы. Динамических свойств не создаёт (безопасен для 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.
- Требуется 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и трейтов