sbooker / transaction-manager
Abstraction for transaction control on an application tier.
Installs: 6 889
Dependents: 6
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 1
Open Issues: 1
Requires
- php: ^7.4 || ^8.0
Requires (Dev)
- phpunit/phpunit: ^9.5
Suggests
- sbooker/doctrine-transaction-handler: For usage with Doctrine ORM
- sbooker/yii2-ar-transaction-handler: For usage with Yii2 Active Record
This package is auto-updated.
Last update: 2025-08-23 11:49:33 UTC
README
Менеджер Транзакций (sbooker/transaction-manager
)
Реализация паттерна Unit of Work, которая навязывает безопасные и явные практики управления транзакциями и предоставляет мощный механизм хуков перед коммитом.
Философия и назначение
Библиотека спроектирована с учетом работы в долгоживущих процессах (воркерах, консьюмерах) и построена на трех ключевых принципах:
-
Явные границы транзакций. Библиотека намеренно не предоставляет публичные методы
begin()
иcommit()
. Единственный способ выполнить операцию — через замыканиеtransactional()
. Такой подход делает границы транзакции абсолютно явными и защищает от трудноуловимых багов, когдаbegin()
иcommit()
разнесены по разным частям кода. -
Безопасная работа с сущностями. Библиотека предоставляет единственный метод для извлечения сущностей с целью их изменения —
getLocked()
. Это также сделано намеренно, чтобы:- Заставить разработчика использовать блокировки (пессимистичные или оптимистичные, в зависимости от реализации
TransactionHandler
), что предотвращает гонки данных по умолчанию. - Устранить необходимость в репозиториях внутри кода, который изменяет состояние системы. Ваш прикладной код зависит только от
TransactionManager
, что делает его проще и чище.
- Заставить разработчика использовать блокировки (пессимистичные или оптимистичные, в зависимости от реализации
-
Автоматическое управление состоянием. После каждой операции transactional() менеджер транзакций полностью очищает свое внутреннее состояние. Это предотвращает утечки памяти и гарантирует, что каждая транзакция начинается "с чистого листа", что критически важно для надежной работы воркеров.
Ключевые особенности
- Автоматическая очистка состояния: После каждого коммита или отката менеджер транзакций полностью очищает свое внутреннее состояние (Unit of Work) и состояние нижележащего обработчика. Это предотвращает утечки памяти и обеспечивает изоляцию операций в долгоживущих процессах.
- Явные границы транзакций: Метод
transactional()
— единственный способ выполнить атомарную операцию. - Единый механизм загрузки с блокировкой: Метод
getLocked()
— единственный способ получить сущность для изменения, что заставляет использовать блокировки и предотвращает проблемы параллельного доступа. - Паттерн Unit of Work: Управляет списком измененных и новых объектов и сохраняет их все в одной транзакции.
- Абстракция над ORM: Ваша бизнес-логика зависит только от
TransactionManager
. - Поддержка вложенных транзакций: Безопасные вызовы
transactional()
внутри другогоtransactional()
. - Хук перед коммитом (
PreCommitEntityProcessor
): Позволяет создавать мощные инструменты, такие как сохранение доменных событий.
Установка
composer require sbooker/transaction-manager
Быстрый старт
Шаг 1: Подключите TransactionHandler
Для работы TransactionManager
требуется "мост" к вашей ORM. Мы предоставляем готовые реализации:
- Для Doctrine ORM:
composer require sbooker/doctrine-transaction-handler
- Для Yii2 Active Record:
composer require sbooker/yii2-ar-transaction-handler
Если вы используете другую ORM, вам нужно будет создать свой адаптер, реализующий интерфейс TransactionHandler
.
Шаг 2: Соберите TransactionManager
// bootstrap.php или ваш DI-контейнер /** @var Sbooker\DoctrineTransactionHandler\TransactionHandler $transactionHandler */ $transactionManager = new Sbooker\TransactionManager\TransactionManager($transactionHandler);
Шаг 3: Используйте в прикладном коде
Пример создания сущности
// src/Products/Application/Handler.php final class Handler { private TransactionManager $transactionManager; // ... public function handle(Command $command): void { $this->transactionManager->transactional(function () use ($command): void { $product = new Product(/* ... */); // Регистрируем новую сущность для сохранения $this->transactionManager->persist($product); }); // После выхода из этого блока, внутреннее состояние менеджера полностью очищено. } }
Пример изменения сущности
Этот пример демонстрирует всю мощь подхода. Обратите внимание: здесь нет репозиториев.
// src/Products/Application/Handler.php final class Handler { private TransactionManager $transactionManager; // ... public function handle(Command $command): void { $this->transactionManager->transactional(function () use ($command): void { // 1. Получаем сущность с блокировкой. Это единственный способ. /** @var Product|null $product */ $product = $this->transactionManager->getLocked(Product::class, $command->getProductId()); if (null === $product) { throw new Exception('Product not found.'); } // 2. Выполняем бизнес-логику $product->changeName($command->getNewName()); // 3. НЕ НУЖНО вызывать persist() или save()! // Сущность, полученная через getLocked(), уже находится под управлением Unit of Work. }); // Здесь Unit of Work также полностью очищен. } }
Шаг 4 (Продвинутый): Добавление PreCommitProcessor
Зарегистрируйте ваш процессор в конструкторе TransactionManager
, и он будет автоматически вызываться для всех сущностей (new Product
из первого примера и $product
из второго) перед коммитом.
// bootstrap.php или ваш DI-контейнер $loggingProcessor = new LoggingProcessor($logger); $transactionManager = new Sbooker\TransactionManager\TransactionManager( $transactionHandler, $loggingProcessor );
Внимание!
$entityId = ...; $transactionManager->transactional(function () use ($transactionManager, $entityId) { $entity = new SomeEntity($entityId); $transactionManager->persist($entity); // Depends on TransactionHandler implementation $persistedEntity may be null in same transaction with persist $persistedEntity = $transactionManager->getLocked($entityId); }
License
See LICENSE file.