Search by

karelwintersky / arris.entity

KarelWintersky

Entity types for Arris µFramework

Package info

github.com/ArrisFramework/Arris.Entity

pkg:composer/karelwintersky/arris.entity

Statistics

Installs: 836

Dependents: 5

Suggesters: 2

Stars: 0

Open Issues: 0

2.0.0 2026-08-07 10:15 UTC

This package is auto-updated.

Last update: 2026-09-07 10:27:38 UTC


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 и трейтов