Search by

illusiard / yii2-base-entity-system

Illusiard

Base Entity System for Yii2 (meta layer, bulk events, universal API)

Package info

github.com/Illusiard/yii2-base-entity-system

Type:yii2-extension

pkg:composer/illusiard/yii2-base-entity-system

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

v1.3.0 2026-09-19 15:39 UTC

This package is not auto-updated.

Last update: 2026-10-17 20:06:55 UTC


README

illusiard/yii2-base-entity-system (BES) - meta-driven CRUDL/search/schema слой для Yii2 с reference SPA и локальным dev host для проверки живого API.

Состав Репозитория

  • src/ - основной backend package BES: Yii2 module, bootstrap, registry, entity runtime, HTTP controllers, search, schema tooling, ACL integration, DTO и event facade.
  • tests/ - backend unit/integration tests для package contract.
  • dev/host/ - локальное Yii2-приложение на yii2-testkit, которое поднимает BES API на SQLite/FileCache для ручной проверки, live integration и browser E2E.
  • view/ - reference frontend SPA для BES. Поддерживает mock provider и live API provider.
  • protocol/ - protocol snapshots, mock payloads и meta-state.json для ревью meta/UI состояния.

Ключевые backend-слои:

  • Module - конфигурация BES в Yii2 application.
  • Bootstrap - подключает модуль, строит runtime и регистрирует routes.
  • BaseEntitySystem - центральный runtime object: DB/cache, registry, ACL, formatter, EventHub, urlPrefix.
  • BaseEntitySystemBuilder - сборка BES runtime из module config.
  • EntityRegistry - регистрация entity classes, meta providers и relation introspection.
  • EntityController - CRUDL HTTP surface.
  • EntityMetaController - entity index, meta и ACL/actions manifest.
  • SchemaController - schema introspection и controlled schema write tooling.
  • BaseActiveRecord и BaseActiveRecordQuery - model/query foundation, включая soft delete.
  • SearchEngine - list/search pipeline: filters, sort, view presets, pagination, deleted filter.
  • ACL - integration с yii2-entity-acl, compact operation masks и relation ACL.
  • DTO - typed meta/UI contract: EntityMeta, EntityUi, FieldMeta, RelationMeta, RelationViaMeta, ViewPreset и related DTO.

Backend API

Dev host по умолчанию использует prefix:

http://127.0.0.1:8081/api/v1

Entity Endpoints

GET  /entity
GET  /{entity}/meta
GET  /{entity}/actions
GET  /{entity}/actions?id={id}

GET  /{entity}/list
GET  /{entity}/view?id={id}
POST /{entity}/create
POST /{entity}/update?id={id}
POST /{entity}/delete?id={id}
POST /{entity}/restore?id={id}
POST /{entity}/permanentDelete?id={id}
POST /{entity}/permanent-delete?id={id}

id поддерживает одиночный integer или string primary key, включая UUID. BES не генерирует UUID сам: значение должно назначаться моделью host-приложения или базой данных. Primary key исключается из writable body, а типы внешних ключей должны совпадать с типом связанного primary key.

delete использует model-level semantics: для моделей с activity field is_active выполняется soft delete, для моделей без activity field остается hard delete. restore восстанавливает soft-deleted запись. permanentDelete и alias permanent-delete удаляют запись физически.

List/Search Query

GET /{entity}/list поддерживает:

  • page - номер страницы.
  • pageSize - размер страницы.
  • sort - поле сортировки, -field для descending.
  • view - имя ViewPreset.
  • filter[field]=value - field filters.
  • deleted=0 - только активные записи.
  • deleted=1 - только soft-deleted записи.
  • deleted=-1 - все записи.

По умолчанию soft-deleted записи скрыты, если у модели есть activity field. Для сущностей без activity field параметр deleted не навязывает искусственный фильтр.

Body И Headers

create и update принимают JSON body с полями модели:

{
  "title": "Runtime Notes",
  "author_id": 1,
  "is_active": true
}

Для JSON requests используйте Content-Type: application/json. Dev host не требует отдельного auth header и работает с permissive ACL; в production-приложении authentication/authorization остаются ответственностью host app и ACL policy.

Пагинация list response передается через headers:

X-Pagination-Total-Count
X-Pagination-Page-Count
X-Pagination-Current-Page
X-Pagination-Per-Page

Для browser clients dev host также выставляет CORS headers и expose headers для pagination.

Wire Format

Успешные ответы intentionally small:

{"items": []}
{"item": {}}
{"success": true}

actions возвращает compact ACL manifest:

{
  "entityAcl": 127,
  "recordAcl": 127,
  "relationsAcl": {
    "author": {
      "entityAcl": 127,
      "recordAcl": null
    }
  }
}

Formatted entity rows/items включают business fields, refs и acl. refs используются frontend runtime для relation labels:

{
  "author_id": {
    "entity": "author",
    "id": 1,
    "title": "Dana Writer"
  },
  "id": {
    "entity": "tag",
    "title": "Tags",
    "count": 2
  }
}

acl содержит compact bit mask операций:

Operation Bit
list 1
read 2
create 4
update 8
delete 16
restore 32
permanentDelete 64

Dev host permissive ACL обычно возвращает 127 для полной маски.

ACL Subject And Object Context

User IDs и record IDs в ACL integration принимают int|string, поэтому UUID передается в yii2-entity-acl без приведения к integer. Для передачи групп текущая Yii identity может реализовать AclSubjectInterface:

use illusiard\baseentity\interfaces\AclSubjectInterface;
use yii\web\IdentityInterface;

final class UserIdentity implements IdentityInterface, AclSubjectInterface
{
    public function getGroupIds(): array
    {
        return $this->groupIds;
    }
}

getGroupIds() возвращает список integer/string identifiers. BES передает его ACL adapter в context key groupIds. Группы используются только для текущей identity, чей getId() совпадает с проверяемым user ID; проверка через EntityEngine::forUser() для другого пользователя не получает группы текущего пользователя.

Record-level owner context задается только моделью, явно реализующей AclObjectInterface:

use illusiard\baseentity\components\base\BaseModel;
use illusiard\baseentity\interfaces\AclObjectInterface;

final class Document extends BaseModel implements AclObjectInterface
{
    public function getOwnerId(): int|string
    {
        return $this->owner_id;
    }
}

BES передает результат getOwnerId() в context key ownerId для загруженной записи, включая list formatting, record-level CRUD access checks, record actions manifest и relation read checks. Если модель не реализует интерфейс или запись не найдена, owner context отсутствует. BES не подставляет текущий user ID вместо owner ID.

Error Format

Ошибки нормализуются в envelope:

{
  "error": {
    "type": "not_found",
    "message": "Record not found",
    "details": {}
  }
}

Типичные типы:

  • bad_request - некорректный query/body.
  • forbidden - ACL запретил операцию.
  • not_found - сущность или запись не найдена.
  • conflict - generic HTTP 409 conflict.
  • validation - ошибки валидации модели, обычно HTTP 422.
  • integrity - DB/reference integrity conflict, обычно HTTP 409.
  • internal_error - непредвиденная ошибка, generic message для клиента.

Meta/UI Contract

BES использует typed DTO для meta и UI contract:

  • EntityMeta - fields, relations, operations, UI и views.
  • EntityUi - listFields, viewFields, formFields, filterFields, readOnlyFields, hiddenFields, hiddenRelations, sortFields, titleField, fieldUi, relations.
  • FieldMeta - field type, label, required/writable/readable flags.
  • RelationMeta - link, targetEntity, kind, optional via metadata.
  • RelationViaMeta - type, table, name, link, targetEntity.
  • ViewPreset - named list preset: fields, filter, sort, page size and title field.

Field Security

FieldMeta.security описывает безопасность поля через DTO Security. Свойство является дополнением к protocol contract; если старый provider или consumer не передает security, применяется mode: normal.

Поддерживаются три режима:

  • normal сохраняет прежнее поведение BES;
  • protected разрешает domain-коду работать со значением, но исключает его из generic responses, refs, title resolution, list/view/filter/sort/export и old/new event values;
  • password имеет те же запреты и дополнительно является строго write-only: generic form использует password input, не получает сохраненное значение и не отправляет пустое неизмененное поле при update.

protected и password не входят в автоматически построенные UI schemas. Provider может явно добавить их в ui.formFields; наличие DB column само по себе не делает password-поле generic-writable. Пустая строка не очищает password через generic update. Очистка требует отдельной явно выраженной host-level операции.

Попытка filter/sort по чувствительному полю возвращает search error с message code restricted_field_operation. Error details содержат только operation и имя поля, без присланного значения. Явный fields не может принудительно вернуть чувствительное значение.

FieldSecurityPolicy является общим public contract для formatter/search/UI/event/audit boundaries. В частности, внешний журнал может использовать sanitizeRecord(), sanitizeValidationErrors(), sanitizeChange() и sanitizeMassEvent() перед сериализацией. BES очищает собственные state/activity events и структурированные MEL payloads; отдельной audit-подсистемы в этом репозитории нет.

BES не определяет security mode по имени column или DB type и не выполняет шифрование либо хеширование. Provider задает mode явно, а подготовка hash/ciphertext остается ответственностью владеющего модуля.

Meta, ActiveRecord model, DB schema и relation getters должны быть согласованы. Нельзя объявлять writable поле, которое модель не принимает через load(), или relation в meta без рабочего AR relation getter.

Текущий snapshot всех зарегистрированных meta/UI структур хранится в:

protocol/meta-state.json

Его можно обновить через dev host helper:

/usr/bin/php8.5 dev/host/bin/export-meta-state.php

Snapshot read-only: он не генерирует и не меняет runtime meta.

Soft Delete

Foundation-level semantics:

  • Activity field на текущем этапе определяется конвенцией is_active.
  • BaseActiveRecord::find() возвращает BaseActiveRecordQuery.
  • Query API: deleted(0), deleted(1), deleted(-1).
  • find() и list/search по умолчанию скрывают soft-deleted записи.
  • delete() делает soft delete для моделей с activity field.
  • restore() устанавливает activity field обратно в active.
  • forceDelete() и API permanentDelete удаляют физически.
  • Для моделей без activity field delete() остается hard delete.

Mass operations вроде deleteAll() этим foundation-слоем автоматически не перехватываются.

Event Facade

BES содержит централизованный EventHub, доступный через BaseEntitySystem::getEventHub().

Основной API:

$hub->on($eventName, $callback);
$hub->off($eventName, $callback);
$hub->trigger($eventName, $payload);

Поддерживаемые BES events:

  • entity.activityChanged с ActivityChangedPayload.
  • entity.stateChanged с StateChangedPayload.

MEL events также прокидываются через этот facade, чтобы клиенты могли подписываться через одну точку. Soft delete/restore/permanentDelete не имеют отдельной скрытой event-магии: события активности и состояния срабатывают через существующий afterSave/behavior path.

Schema API

Schema endpoints:

GET  /schema/tables
GET  /schema/table/{name}
POST /schema/diff
POST /schema/migration

Read endpoints доступны для introspection. table/{name} принимает только имена с символами a-zA-Z0-9_.-.

diff и migration принимают JSON body с обязательным объектом desired в формате schema DTO из illusiard/dbtoolkit:

{
  "desired": {
    "tables": []
  },
  "options": {
    "className": "m260101_000001_update_schema",
    "namespace": "app\\migrations",
    "transactional": true,
    "useTablePrefix": false
  }
}

Для migration options также поддерживается flat form на root-level: className, namespace, transactional, useTablePrefix. diff возвращает {"actions": [...]}, migration возвращает {"className": "...", "code": "..."}. Write tooling (diff, migration) должен включаться явно через controlled module config, например allowSchemaWriteTools, и не предназначен для открытой production-среды.

Dev Host

dev/host/ - локальный integration host. Он нужен для ручной проверки, live API integration tests и browser E2E.

Особенности:

  • Собирается через illusiard/yii2-testkit.
  • Использует SQLite: dev/host/runtime/bes-dev.sqlite.
  • Использует Yii FileCache в dev/host/runtime.
  • Создает schema и seed при первом запуске.
  • Регистрирует demo entities: post, author, tag.
  • Использует permissive ACL policy для ручной проверки всех операций.
  • Подключает CORS для frontend dev server.

Запуск из корня репозитория:

composer install
/usr/bin/php8.5 -S 127.0.0.1:8081 -t dev/host/web dev/host/web/router.php

Live API base URL:

http://127.0.0.1:8081/api/v1

Smoke-check dev host:

bash dev/host/bin/smoke-http.sh

Dev host stateful. Если нужно начать с чистого состояния, удалите SQLite/cache runtime файлы в dev/host/runtime/ и запустите host снова.

Frontend SPA

view/ - reference SPA для проверки backend protocol в реальном runtime. Runtime architecture сохраняет направление:

container components -> screen model -> composables -> small UI components

Providers:

  • mock - isolated frontend/dev/test режим без backend.
  • api - live provider поверх BES HTTP API.

Запуск mock provider:

cd view
npm install
VITE_BES_PROVIDER=mock npm run dev

Запуск live API provider:

/usr/bin/php8.5 -S 127.0.0.1:8081 -t dev/host/web dev/host/web/router.php
cd view
VITE_BES_PROVIDER=api VITE_BES_API_BASE_URL=http://127.0.0.1:8081/api/v1 npm run dev

Во frontend refs и ACL bit masks нормализуются через EntityFormatter/runtime provider path. listFields, popup stack и related popup flows закреплены в runtime. Нестандартные scenario/debug controls остаются dev/test harness и не являются частью production UI contract.

Tests

Backend unit/integration tests:

composer install
vendor/bin/phpunit

Dev host HTTP smoke:

/usr/bin/php8.5 -S 127.0.0.1:8081 -t dev/host/web dev/host/web/router.php
bash dev/host/bin/smoke-http.sh

Frontend Vitest mock/integration suite:

cd view
npm install
npm test

Эта suite включает provider/runtime contract tests, включая view/src/integration/__tests__/api-provider.integration.test.js, и работает без backend через mock/stub providers.

Live API integration tests against running dev host:

cd view
npm run test:live

test:live использует:

BES_LIVE_API=1
VITE_BES_PROVIDER=api
VITE_BES_API_BASE_URL=http://127.0.0.1:8081/api/v1

Основной live integration файл: view/src/integration/__tests__/live-api.integration.test.js. Dev host должен быть уже поднят на http://127.0.0.1:8081/api/v1.

Browser E2E через Playwright:

cd view
npm run test:e2e

Только live API E2E сценарии:

cd view
npm run test:e2e-live

Playwright config поднимает dev host и Vite frontend с live API provider. По умолчанию Chromium-compatible browser ожидается как:

/usr/bin/google-chrome

Если Chrome установлен в другом месте, настройте environment для Playwright, например PLAYWRIGHT_CHROMIUM_EXECUTABLE. Base URLs также можно переопределять через PLAYWRIGHT_FRONTEND_BASE_URL и PLAYWRIGHT_BACKEND_BASE_URL.

Current Coverage

Backend tests закрепляют:

  • meta/DTO/schema contract;
  • CRUDL endpoints;
  • soft delete foundation;
  • restore и permanentDelete;
  • deleted=-1|0|1 search/list semantics;
  • ACL operation masks;
  • EventHub subscription/unsubscription и payload triggers;
  • meta-state snapshot generation.

Live frontend integration и browser E2E покрывают:

  • root list;
  • view popup из list;
  • update popup из view;
  • root create;
  • FK selectors и create-select popup chain;
  • refs и ACL bits на live API;
  • soft delete, restore, permanentDelete;
  • deleted query parameter;
  • validation, forbidden, not_found, integrity/internal error paths где они относятся к provider/UI contract.

Mock provider tests остаются отдельным isolation layer для frontend-only сценариев.

Release Readiness

Текущий release status, host-app production contract и regression pack зафиксированы в:

RELEASE.md

На уровне репозитория known release blockers отсутствуют. Production-готовность зависит от host app: authentication, yii2-entity-acl policy, инфраструктура cache/db и закрытый доступ к schema write tooling.

Ограничения

  • Dev host использует permissive ACL, поэтому он удобен для ручных CRUDL проверок, но не моделирует production policy.
  • SQLite/FileCache изолированы в dev/host/runtime и предназначены только для локального dev/test.
  • Soft delete автоматически работает только для моделей с activity field is_active.
  • deleteAll() и другие mass delete operations не переводятся автоматически в soft delete.
  • Schema write endpoints должны быть включены явно и не должны быть открыты в production без дополнительной защиты.
  • Frontend scenario/debug controls относятся только к dev/test harness.