92 lines
11 KiB
Markdown
92 lines
11 KiB
Markdown
---
|
||
name: roadmap
|
||
description: Статус слоя БД (MariaDB/Mongo/Elasticsearch) в Bicycle — что реализовано, что ждёт внешней инфраструктуры
|
||
metadata:
|
||
type: project
|
||
---
|
||
|
||
Реализовано (2026-08-07) — три независимых сервиса, каждый со своей фабрикой в духе `Services\Auth`,
|
||
без общего интерфейса поверх всех трёх (SQL/документы/поиск — слишком разные модели, чтобы прятать
|
||
за одной абстракцией). Подробности использования — CLAUDE.md → разделы DataBase/Elasticsearch/Mongo.
|
||
|
||
- **Реляционная БД (MariaDB, полностью рабочая и протестированная, реальное подключение)** —
|
||
`Services\Database` (фабрика), `Services\DataBase\Classes\PdoDriver` (failover по хостам, транзакции
|
||
с вложенными SAVEPOINT — единственный источник истины по вложенности, общий на всё подключение),
|
||
`Services\DataBase\Classes\Statement` (`showQuery()`/`sq()`, `execute()` профилирует в `Profiler`), `Services\DataBase\Classes\Profiler`
|
||
(тайминги, см. также `System\Classes\ProfilerToolbar`).
|
||
- **`System\Classes\Repository`** (`@template T of object`, abstract CRUD) — портирован с
|
||
`eoffice_v3/System/Classes/Repository.php` (пользователь явно попросил именно этот паттерн), но НЕ
|
||
один-в-один — два сознательных отличия от оригинала (см. CLAUDE.md → раздел DataBase за подробностями):
|
||
1. `getList()` по умолчанию `PDO::FETCH_CLASS` с проброшенным `$this->obj_class` (в оригинале —
|
||
`PDO::FETCH_KEY_PAIR` по умолчанию, и `FETCH_CLASS` не получает `obj_class` вовсе — нестыковка
|
||
PHPDoc/кода в оригинале, не повторяли).
|
||
2. Транзакции (`beginTransaction/commit/rollBack`) — тонкие делегаты в `PdoDriver` (см. выше), а не свой
|
||
счётчик вложенности в каждом `Repository`, как в оригинале (там это ломается, если два репозитория
|
||
на одном соединении оба вызывают `beginTransaction()` — оба думают, что «внешние»).
|
||
Методы: `get()/getItemWhere()/getList()/create()/update()/delete()/deleteWhere()`, `primary_col`/`is_auto_increment`
|
||
публичные, `processData()` — сборка bind-параметров с автоопределением `PDO::PARAM_*`.
|
||
Конкретные репозитории — в `App/Repositories/*` (например `App\Repositories\BudgetRepository`), НЕ в
|
||
`Services\DataBase` — так решил пользователь явно; таблица/класс строки задаются через конструктор
|
||
(`parent::__construct('budget', Model::class)`), не переопределением свойства.
|
||
- **`Services\DataBase\Model`** (`#[AllowDynamicProperties]`) — класс строки по умолчанию для `T`.
|
||
Важно: свойства настоящие (не спрятаны за внутренним `$attributes`-массивом) — `PDO::FETCH_CLASS`/
|
||
`fetchObject()` выставляет их напрямую, **минуя `__set()`** (внутренний механизм PDO, до вызова
|
||
конструктора). Версия со скрытым массивом (первая, что я написал) была рабочей только для ручного
|
||
`new Model([...])`, но ломалась именно в главном сценарии использования — пользователь сам это поймал
|
||
и прислал правильный вариант.
|
||
- **Elasticsearch** — `Services\Elasticsearch` + `Services\Elasticsearch\Client`, тонкая REST-обёртка на
|
||
уже существующем `System\Classes\HTTP\Client\Curl` (без тяжёлой composer-зависимости
|
||
`elasticsearch/elasticsearch` — см. `feedback_educational_project.md`).
|
||
- **Mongo** — `Services\Mongo` + `Services\Mongo\MongoDriver`, обёртка над `MongoDB\Client`
|
||
(композер-пакет `mongodb/mongodb` уже поставлен через `composer.phar require --ignore-platform-req=ext-mongodb`,
|
||
плюс `"platform": {"ext-mongodb": "2.3"}` в `composer.json`, чтобы дальнейшие `composer install` не
|
||
требовали флага).
|
||
- **`System\Classes\ProfilerToolbar`** — debug-панель внизу страницы (аналог ProfilerToolbar для Kohana),
|
||
время/память/SQL из `Profiler`, видна только `role === 'admin'`, подключена в `App/view/layout.html`.
|
||
|
||
**Профилирование — в `Statement::execute()`, не в `PdoDriver::query()` (важно, был реальный баг):**
|
||
первая версия вешала таймер только на `PdoDriver::query()` через отдельный класс `ProfilerPDO::wrap()`
|
||
(теперь удалён) — но `Repository::get()/getList()/create()/update()/delete()/deleteWhere()` сами делают
|
||
`$this->pdo->prepare($sql); $stmt->execute();` напрямую, в обход `query()` — то есть почти все реальные
|
||
запросы через `Repository` не логировались вообще (пользователь поймал это по `ProfilerToolbar`,
|
||
показывавшему "SQL: 0" при заведомо выполненном запросе). Раз `PDO::ATTR_STATEMENT_CLASS` гарантирует,
|
||
что через `Statement` проходит вообще любое выполнение запроса (и `query()`, и ручной `prepare()+execute()`),
|
||
таймер и вызов `Profiler::log()` теперь в `Statement::execute()` — единственной точке, которую нельзя
|
||
обойти. Общий урок: если добавляешь профилирование/логирование поверх PDO-обёртки — вешать его на
|
||
низкоуровневый общий метод (`Statement`/`PDOStatement`), а не на удобный высокоуровневый (`PdoDriver::query()`),
|
||
если у обёртки есть другие пути выполнения запроса в обход этого высокоуровневого метода.
|
||
|
||
**Свойство `Repository`/`PdoDriver` называется `$pdo`** (не `$_driver`/`$_pdo` с подчёркиванием, хотя это
|
||
нарушает общий стиль `_prefixed` protected-свойств проекта) — пользователь явно попросил именно так,
|
||
дважды сам написал `$this->pdo` в своём коде раньше, чем я успел объяснить структуру. См. также
|
||
`PdoDriver::rollback()` — с маленькой буквы (не `rollBack()`, хотя нативный `\PDO::rollBack()` — с
|
||
большой); `Repository::rollBack()` (с большой буквы) — публичный метод, который делегирует в
|
||
`$this->pdo->rollback()` (с маленькой) — это НЕ опечатка, два разных метода двух разных классов.
|
||
|
||
**Что ждёт внешней инфраструктуры (не моя зона — пользователь занимается сам):**
|
||
|
||
- `ext-mongodb` не установлен в `docker/php-apache/8.2.8/Dockerfile` (общий для нескольких проектов
|
||
на хосте — мне трогать нельзя). Пользователь добавит сам. Код написан безопасно без расширения —
|
||
падает только на реальном `new MongoDB\Client()` внутри `MongoDriver::__construct()`, не раньше.
|
||
- Контейнеров `mongo` и `elasticsearch` в `docker-dev/docker-compose.yml` нет — пользователь поднимет сам
|
||
(там уже есть закомментированный черновик блока `elasticsearch` в файле).
|
||
- MariaDB — пользователь сам прописал в `App/config/config.php`/`config.local.php` реальное подключение
|
||
(внешний хост, не контейнер `db` из docker-dev) — эта часть инфраструктуры уже готова пользователем,
|
||
тесты по MariaDB реально гоняются, не skip.
|
||
|
||
**Тесты**: `PdoDriverTest`/`StatementTest`/`RepositoryTest` бьют по реальной MariaDB (не мокают) — гейт
|
||
через `markTestSkipped()` в `setUp()` при неудачном подключении остался в коде на случай, если у
|
||
кого-то ещё не настроен `config.local.php`, но сейчас реально проходят (не skip). `ElasticsearchClientTest` —
|
||
через `Client::ping()`. `MongoDriverTest` — через `extension_loaded('mongodb')`. Эти два оживут сами,
|
||
без правки кода, когда появится инфраструктура — сейчас (2026-08-07) 8 тестов из 239 пропущены именно
|
||
поэтому.
|
||
|
||
**How to apply:** При запросах, касающихся БД/моделей/репозиториев — этот слой уже есть, не писать
|
||
заново с нуля, расширять существующие классы. Новый конкретный репозиторий — в `App/Repositories/*
|
||
extends System\Classes\Repository`, не в `Services\DataBase`. Если пользователь присылает код из другого
|
||
своего проекта («у меня так работает») как референс — не копировать один-в-один вслепую: проверять на
|
||
реальные баги/нестыковки (как с `getList()`/`FETCH_CLASS` и дублированием transaction-счётчика выше) и
|
||
чинить под Bicycle, если он прямо говорит «не так же, а правильно». См. также `idea_cli_entrypoint.md` —
|
||
CLI-точка входа отложена именно до появления этого слоя (первый реальный потребитель — миграции; теперь,
|
||
когда слой БД есть, эта идея стала актуальнее).
|