Skip to content

Latest commit

 

History

22 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Arris\Entity

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

About

Result class for Arris µFramework

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages