diff --git a/changelog.md b/changelog.md index 7c2c9bde7..3185dec19 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [9.6.0] - 2022-10-27 +### Added +* Full support for standard transactions and laravel nova #589 + ## [9.5.0] - 2022-10-06 ### Added * Improved performance api handles #576 @@ -944,7 +948,8 @@ The operation is now executed in the transaction and updates the new `refund` fi - Exceptions: AmountInvalid, BalanceIsEmpty. - Models: Transfer, Transaction. -[Unreleased]: https://github.com/bavix/laravel-wallet/compare/9.5.0...master +[Unreleased]: https://github.com/bavix/laravel-wallet/compare/9.6.0...master +[9.6.0]: https://github.com/bavix/laravel-wallet/compare/9.5.0...9.6.0 [9.5.0]: https://github.com/bavix/laravel-wallet/compare/9.4.0...9.5.0 [9.4.0]: https://github.com/bavix/laravel-wallet/compare/9.3.0...9.4.0 [9.3.0]: https://github.com/bavix/laravel-wallet/compare/9.2.0...9.3.0 diff --git a/composer.json b/composer.json index 65d1bf50b..795781cc6 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "brick/math": "~0.8", "doctrine/dbal": "^2.8|^3.0", "illuminate/contracts": "^9.0", - "illuminate/database": "^9.0", + "illuminate/database": "^9.37", "ramsey/uuid": "^4.0" }, "require-dev": { diff --git a/config/config.php b/config/config.php index 4c75d58d6..0e24030da 100644 --- a/config/config.php +++ b/config/config.php @@ -19,6 +19,7 @@ use Bavix\Wallet\Internal\Repository\TransferRepository; use Bavix\Wallet\Internal\Repository\WalletRepository; use Bavix\Wallet\Internal\Service\ClockService; +use Bavix\Wallet\Internal\Service\ConnectionService; use Bavix\Wallet\Internal\Service\DatabaseService; use Bavix\Wallet\Internal\Service\DispatcherService; use Bavix\Wallet\Internal\Service\JsonService; @@ -82,6 +83,7 @@ */ 'internal' => [ 'clock' => ClockService::class, + 'connection' => ConnectionService::class, 'database' => DatabaseService::class, 'dispatcher' => DispatcherService::class, 'json' => JsonService::class, diff --git a/deptrac.yaml b/deptrac.yaml index f4a7e779f..10c17c18c 100644 --- a/deptrac.yaml +++ b/deptrac.yaml @@ -50,6 +50,11 @@ parameters: - type: className regex: ^Bavix\\.*\\Internal\\Exceptions\\.* + - name: InternalListener + collectors: + - type: className + regex: ^Bavix\\.*\\Internal\\Listeners\\.* + - name: Event collectors: - type: className @@ -250,6 +255,9 @@ parameters: - UIException - Model + InternalListener: + - ServiceInterface + ServiceInterface: - InternalException - EventInterface diff --git a/docs/nova-action.md b/docs/nova-action.md index 37902bf1e..6c8d55381 100644 --- a/docs/nova-action.md +++ b/docs/nova-action.md @@ -1,5 +1,7 @@ ## Nova Action +> Use only if you have a package version below 9.6 + As you know, the package works with internal state. You can read more [here](https://github.com/bavix/laravel-wallet/pull/412) and [here](https://github.com/bavix/laravel-wallet/issues/455). The action runs inside a transaction, which means you need to reset the transaction manually. diff --git a/docs/transaction.md b/docs/transaction.md index 255daa0a4..df482002c 100644 --- a/docs/transaction.md +++ b/docs/transaction.md @@ -1,5 +1,28 @@ ## Transaction +> Starting from version 9.6 you can safely use standard framework transactions. + +You definitely need to know the feature of transactions. The wallet is automatically blocked from the moment it is used until the end of the transaction. Therefore, it is necessary to use the wallet closer to the end of the transaction. + +Very important! Almost all wallet transactions are blocking. + +```php +use Illuminate\Support\Facades\DB; + +DB::beginTransaction(); +$wallet->balanceInt; // now the wallet is blocked +doingMagic(); // running for a long time. +DB::commit(); // here will unlock the wallet +``` + +The point is that you need to minimize operations within transactions as much as possible. The longer the transaction, the longer the wallet lock. +The maximum wallet blocking time is set in the configuration. The longer the transaction takes, the more likely it is to get a race for the wallet. + +--- + +If you are using a version below 9.6, then the following is relevant for you: +-- + > Since version 9.2 it is safer to use AtomicServiceInterface. > Is it possible to use laravel's inline transactions? No, It is Immpossible. This limitation is due to the internal architecture of the package. To achieve the maximum speed of work, work with an internal state of balance was needed. Starting with version 8.2, a special error has appeared that will inform you about incorrect work with the `TransactionStartException` package. diff --git a/ecs.php b/ecs.php index c0d1c6e33..274fbc90f 100644 --- a/ecs.php +++ b/ecs.php @@ -9,10 +9,10 @@ use Symplify\CodingStandard\Fixer\LineLength\DocBlockLineLengthFixer; use Symplify\CodingStandard\Fixer\LineLength\LineLengthFixer; use Symplify\EasyCodingStandard\Config\ECSConfig; -use Symplify\EasyCodingStandard\ValueObject\Option; use Symplify\EasyCodingStandard\ValueObject\Set\SetList; return static function (ECSConfig $containerConfigurator): void { + $containerConfigurator->parallel(); $services = $containerConfigurator->services(); $services->set(ArraySyntaxFixer::class) ->call('configure', [[ @@ -22,9 +22,6 @@ $services->set(DeclareStrictTypesFixer::class); $services->set(LineLengthFixer::class); - $parameters = $containerConfigurator->parameters(); - $parameters->set(Option::PARALLEL, true); - $containerConfigurator->paths([ __DIR__ . '/config', __DIR__ . '/database', diff --git a/rector.php b/rector.php index 18b319a46..bb721e85f 100644 --- a/rector.php +++ b/rector.php @@ -4,12 +4,15 @@ use Rector\CodeQuality\Rector\PropertyFetch\ExplicitMethodCallOverMagicGetSetRector; use Rector\Config\RectorConfig; +use Rector\Laravel\Rector\Assign\CallOnAppArrayAccessToStandaloneAssignRector; +use Rector\Laravel\Rector\ClassMethod\AddParentRegisterToEventServiceProviderRector; use Rector\Laravel\Set\LaravelSetList; use Rector\Php74\Rector\Property\TypedPropertyRector; use Rector\PHPUnit\Set\PHPUnitSetList; use Rector\Set\ValueObject\SetList; return static function (RectorConfig $containerConfigurator): void { + $containerConfigurator->parallel(); $containerConfigurator->paths([ __DIR__ . '/src', __DIR__ . '/tests', @@ -29,4 +32,6 @@ // register a single rule $services->set(TypedPropertyRector::class); + $services->set(CallOnAppArrayAccessToStandaloneAssignRector::class); + $services->set(AddParentRegisterToEventServiceProviderRector::class); }; diff --git a/src/Internal/Exceptions/ExceptionInterface.php b/src/Internal/Exceptions/ExceptionInterface.php index f4774fb8b..f2c5fb4b0 100644 --- a/src/Internal/Exceptions/ExceptionInterface.php +++ b/src/Internal/Exceptions/ExceptionInterface.php @@ -31,6 +31,4 @@ interface ExceptionInterface extends Throwable public const TRANSACTION_FAILED = 1 << 10; public const MODEL_NOT_FOUND = 1 << 11; - - public const TRANSACTION_START = 1 << 12; } diff --git a/src/Internal/Exceptions/TransactionRollbackException.php b/src/Internal/Exceptions/TransactionRollbackException.php new file mode 100644 index 000000000..83f885fe8 --- /dev/null +++ b/src/Internal/Exceptions/TransactionRollbackException.php @@ -0,0 +1,21 @@ +result; + } +} diff --git a/src/Internal/Exceptions/TransactionStartException.php b/src/Internal/Exceptions/TransactionStartException.php deleted file mode 100644 index deb30f370..000000000 --- a/src/Internal/Exceptions/TransactionStartException.php +++ /dev/null @@ -1,11 +0,0 @@ -get()->transactionLevel() === 1) { + app(RegulatorServiceInterface::class)->purge(); + } + } +} diff --git a/src/Internal/Listeners/TransactionCommittedListener.php b/src/Internal/Listeners/TransactionCommittedListener.php new file mode 100644 index 000000000..10029d332 --- /dev/null +++ b/src/Internal/Listeners/TransactionCommittedListener.php @@ -0,0 +1,18 @@ +get()->transactionLevel() === 0) { + app(RegulatorServiceInterface::class)->committed(); + } + } +} diff --git a/src/Internal/Listeners/TransactionCommittingListener.php b/src/Internal/Listeners/TransactionCommittingListener.php new file mode 100644 index 000000000..12b7ad872 --- /dev/null +++ b/src/Internal/Listeners/TransactionCommittingListener.php @@ -0,0 +1,22 @@ +get()->transactionLevel() === 1) { + app(RegulatorServiceInterface::class)->committing(); + } + } +} diff --git a/src/Internal/Listeners/TransactionRolledBackListener.php b/src/Internal/Listeners/TransactionRolledBackListener.php new file mode 100644 index 000000000..9983ea88a --- /dev/null +++ b/src/Internal/Listeners/TransactionRolledBackListener.php @@ -0,0 +1,18 @@ +get()->transactionLevel() === 0) { + app(RegulatorServiceInterface::class)->purge(); + } + } +} diff --git a/src/Internal/Repository/WalletRepository.php b/src/Internal/Repository/WalletRepository.php index bc1a04c9e..2b885725a 100644 --- a/src/Internal/Repository/WalletRepository.php +++ b/src/Internal/Repository/WalletRepository.php @@ -29,6 +29,15 @@ public function create(array $attributes): Wallet */ public function updateBalances(array $data): int { + // One element gives x10 speedup, on some data + if (count($data) === 1) { + return $this->wallet->newQuery() + ->whereKey(key($data)) + ->update([ + 'balance' => current($data), + ]); + } + $cases = []; foreach ($data as $walletId => $balance) { $cases[] = 'WHEN id = ' . $walletId . ' THEN ' . $balance; diff --git a/src/Internal/Service/ConnectionService.php b/src/Internal/Service/ConnectionService.php new file mode 100644 index 000000000..f91f5fa02 --- /dev/null +++ b/src/Internal/Service/ConnectionService.php @@ -0,0 +1,26 @@ +connection = $connectionResolver->connection(config('wallet.database.connection')); + } + + public function get(): ConnectionInterface + { + return $this->connection; + } +} diff --git a/src/Internal/Service/ConnectionServiceInterface.php b/src/Internal/Service/ConnectionServiceInterface.php new file mode 100644 index 000000000..b8e96f81c --- /dev/null +++ b/src/Internal/Service/ConnectionServiceInterface.php @@ -0,0 +1,12 @@ +connection = $connectionResolver->connection(config('wallet.database.connection')); } /** @@ -33,39 +24,23 @@ public function __construct( */ public function transaction(callable $callback): mixed { - $level = $this->connection->transactionLevel(); - if ($level > 0 && ! $this->init) { - throw new TransactionStartException( - 'Working inside an embedded transaction is not possible. https://bavix.github.io/laravel-wallet/#/transaction', - ExceptionInterface::TRANSACTION_START, - ); - } - - if ($level > 0) { - return $callback(); - } - - $this->init = true; - try { - $this->regulatorService->purge(); + $connection = $this->connectionService->get(); + if ($connection->transactionLevel() > 0) { + return $callback(); + } - return $this->connection->transaction(function () use ($callback) { + return $connection->transaction(function () use ($callback) { $result = $callback(); - $this->init = false; - if ($result === false) { - return false; + if ($result === false || (is_countable($result) && count($result) === 0)) { + throw new TransactionRollbackException($result); } - if (is_countable($result) && count($result) === 0) { - return $result; - } - - $this->regulatorService->approve(); - return $result; }); + } catch (TransactionRollbackException $rollbackException) { + return $rollbackException->getResult(); } catch (RecordsNotFoundException|ExceptionInterface $exception) { throw $exception; } catch (Throwable $throwable) { @@ -74,9 +49,6 @@ public function transaction(callable $callback): mixed ExceptionInterface::TRANSACTION_FAILED, $throwable ); - } finally { - $this->regulatorService->purge(); - $this->init = false; } } } diff --git a/src/Internal/Service/DatabaseServiceInterface.php b/src/Internal/Service/DatabaseServiceInterface.php index 37c00d959..b844366c9 100644 --- a/src/Internal/Service/DatabaseServiceInterface.php +++ b/src/Internal/Service/DatabaseServiceInterface.php @@ -6,7 +6,6 @@ use Bavix\Wallet\Internal\Exceptions\ExceptionInterface; use Bavix\Wallet\Internal\Exceptions\TransactionFailedException; -use Bavix\Wallet\Internal\Exceptions\TransactionStartException; use Illuminate\Database\RecordsNotFoundException; interface DatabaseServiceInterface @@ -17,7 +16,6 @@ interface DatabaseServiceInterface * @return T * * @throws RecordsNotFoundException - * @throws TransactionStartException * @throws TransactionFailedException * @throws ExceptionInterface */ diff --git a/src/Internal/Service/LockService.php b/src/Internal/Service/LockService.php index 8ef9e9384..a4fffe651 100644 --- a/src/Internal/Service/LockService.php +++ b/src/Internal/Service/LockService.php @@ -23,6 +23,7 @@ final class LockService implements LockServiceInterface private CacheRepository $cache; public function __construct( + private ConnectionServiceInterface $connectionService, CacheFactory $cacheFactory, private int $seconds ) { @@ -43,6 +44,13 @@ public function block(string $key, callable $callback): mixed ->lock(self::LOCK_KEY . $key, $this->seconds); $this->lockedKeys->put(self::INNER_KEYS . $key, true, $this->seconds); + // let's release the lock after the transaction, the fight against the race + if ($this->connectionService->get()->transactionLevel() > 0) { + $lock->block($this->seconds); + + return $callback(); + } + try { return $lock->block($this->seconds, $callback); } finally { @@ -71,6 +79,23 @@ public function blocks(array $keys, callable $callback): mixed return $callable(); } + public function releases(array $keys): void + { + $lockProvider = $this->getLockProvider(); + + foreach ($keys as $key) { + if (! $this->isBlocked($key)) { + continue; + } + + $lockProvider + ->lock(self::LOCK_KEY . $key, $this->seconds) + ->forceRelease(); + + $this->lockedKeys->delete(self::INNER_KEYS . $key); + } + } + public function isBlocked(string $key): bool { return $this->lockedKeys->get(self::INNER_KEYS . $key) === true; diff --git a/src/Internal/Service/LockServiceInterface.php b/src/Internal/Service/LockServiceInterface.php index 37a7622aa..373f51f61 100644 --- a/src/Internal/Service/LockServiceInterface.php +++ b/src/Internal/Service/LockServiceInterface.php @@ -29,5 +29,10 @@ public function block(string $key, callable $callback): mixed; */ public function blocks(array $keys, callable $callback): mixed; + /** + * @param string[] $keys + */ + public function releases(array $keys): void; + public function isBlocked(string $key): bool; } diff --git a/src/Services/RegulatorService.php b/src/Services/RegulatorService.php index ddfabdea5..ff815618a 100644 --- a/src/Services/RegulatorService.php +++ b/src/Services/RegulatorService.php @@ -8,6 +8,7 @@ use Bavix\Wallet\Internal\Exceptions\RecordNotFoundException; use Bavix\Wallet\Internal\Repository\WalletRepositoryInterface; use Bavix\Wallet\Internal\Service\DispatcherServiceInterface; +use Bavix\Wallet\Internal\Service\LockServiceInterface; use Bavix\Wallet\Internal\Service\MathServiceInterface; use Bavix\Wallet\Internal\Service\StorageServiceInterface; use Bavix\Wallet\Models\Wallet; @@ -22,12 +23,18 @@ final class RegulatorService implements RegulatorServiceInterface */ private array $wallets = []; + /** + * @var array + */ + private array $multiIncrease = []; + public function __construct( private BalanceUpdatedEventAssemblerInterface $balanceUpdatedEventAssembler, private BookkeeperServiceInterface $bookkeeperService, private DispatcherServiceInterface $dispatcherService, private StorageServiceInterface $storageService, private MathServiceInterface $mathService, + private LockServiceInterface $lockService, private WalletRepositoryInterface $walletRepository ) { } @@ -86,29 +93,32 @@ public function decrease(Wallet $wallet, float|int|string $value): string return $this->increase($wallet, $this->mathService->negative($value)); } - public function approve(): void + public function committing(): void { - try { - $balances = []; - $incrementValues = []; - foreach ($this->wallets as $wallet) { - $diffValue = $this->diff($wallet); - if ($this->mathService->compare($diffValue, 0) === 0) { - continue; - } - - $incrementValues[$wallet->uuid] = $diffValue; - $balances[$wallet->getKey()] = $this->amount($wallet); + $balances = []; + $incrementValues = []; + foreach ($this->wallets as $wallet) { + $diffValue = $this->diff($wallet); + if ($this->mathService->compare($diffValue, 0) === 0) { + continue; } - if ($balances === [] || $incrementValues === [] || $this->wallets === []) { - return; - } + $balances[$wallet->getKey()] = $this->amount($wallet); + $incrementValues[$wallet->uuid] = $this->diff($wallet); + } - $this->walletRepository->updateBalances($balances); - $multiIncrease = $this->bookkeeperService->multiIncrease($this->wallets, $incrementValues); + if ($balances === [] || $incrementValues === [] || $this->wallets === []) { + return; + } - foreach ($multiIncrease as $uuid => $balance) { + $this->walletRepository->updateBalances($balances); + $this->multiIncrease = $this->bookkeeperService->multiIncrease($this->wallets, $incrementValues); + } + + public function committed(): void + { + try { + foreach ($this->multiIncrease as $uuid => $balance) { $wallet = $this->wallets[$uuid]; $wallet->fill([ @@ -124,13 +134,29 @@ public function approve(): void } } - public function purge(): void + /** + * @codeCoverageIgnore + */ + public function approve(): void { - foreach ($this->wallets as $wallet) { - $this->missing($wallet); + try { + $this->committing(); + } finally { + $this->committed(); } + } - $this->dispatcherService->forgot(); + public function purge(): void + { + try { + $this->lockService->releases(array_keys($this->wallets)); + $this->multiIncrease = []; + foreach ($this->wallets as $wallet) { + $this->missing($wallet); + } + } finally { + $this->dispatcherService->forgot(); + } } private function persist(Wallet $wallet): void diff --git a/src/Services/RegulatorServiceInterface.php b/src/Services/RegulatorServiceInterface.php index 77e8f0c6b..0bd16c387 100644 --- a/src/Services/RegulatorServiceInterface.php +++ b/src/Services/RegulatorServiceInterface.php @@ -20,6 +20,16 @@ public function increase(Wallet $wallet, float|int|string $value): string; public function decrease(Wallet $wallet, float|int|string $value): string; + public function committing(): void; + + public function committed(): void; + + /** + * @deprecated + * + * @see committing + * @see committed + */ public function approve(): void; public function purge(): void; diff --git a/src/WalletServiceProvider.php b/src/WalletServiceProvider.php index 60b97952a..dcc07ab8f 100644 --- a/src/WalletServiceProvider.php +++ b/src/WalletServiceProvider.php @@ -46,6 +46,8 @@ use Bavix\Wallet\Internal\Repository\WalletRepositoryInterface; use Bavix\Wallet\Internal\Service\ClockService; use Bavix\Wallet\Internal\Service\ClockServiceInterface; +use Bavix\Wallet\Internal\Service\ConnectionService; +use Bavix\Wallet\Internal\Service\ConnectionServiceInterface; use Bavix\Wallet\Internal\Service\DatabaseService; use Bavix\Wallet\Internal\Service\DatabaseServiceInterface; use Bavix\Wallet\Internal\Service\DispatcherService; @@ -109,6 +111,11 @@ use function dirname; use function function_exists; use Illuminate\Contracts\Cache\Factory as CacheFactory; +use Illuminate\Database\Events\TransactionBeginning; +use Illuminate\Database\Events\TransactionCommitted; +use Illuminate\Database\Events\TransactionCommitting; +use Illuminate\Database\Events\TransactionRolledBack; +use Illuminate\Support\Facades\Event; use Illuminate\Support\ServiceProvider; final class WalletServiceProvider extends ServiceProvider @@ -122,6 +129,11 @@ public function boot(): void { $this->loadTranslationsFrom(dirname(__DIR__) . '/resources/lang', 'wallet'); + Event::listen(TransactionBeginning::class, Internal\Listeners\TransactionBeginningListener::class); + Event::listen(TransactionCommitting::class, Internal\Listeners\TransactionCommittingListener::class); + Event::listen(TransactionCommitted::class, Internal\Listeners\TransactionCommittedListener::class); + Event::listen(TransactionRolledBack::class, Internal\Listeners\TransactionRolledBackListener::class); + if (! $this->app->runningInConsole()) { return; } @@ -213,6 +225,7 @@ private function internal(array $configure): void ->giveConfig('wallet.cache.ttl'); $this->app->singleton(ClockServiceInterface::class, $configure['clock'] ?? ClockService::class); + $this->app->singleton(ConnectionServiceInterface::class, $configure['connection'] ?? ConnectionService::class); $this->app->singleton(DatabaseServiceInterface::class, $configure['database'] ?? DatabaseService::class); $this->app->singleton(DispatcherServiceInterface::class, $configure['dispatcher'] ?? DispatcherService::class); $this->app->singleton(JsonServiceInterface::class, $configure['json'] ?? JsonService::class); diff --git a/tests/Units/Domain/EventTest.php b/tests/Units/Domain/EventTest.php index c0a981272..984be31bc 100644 --- a/tests/Units/Domain/EventTest.php +++ b/tests/Units/Domain/EventTest.php @@ -77,7 +77,6 @@ public function testBalanceUpdatedThrowDateListener(): void // unit $this->expectException(UnknownEventException::class); - $this->expectExceptionMessage(ClockFakeService::FAKE_DATETIME); $this->expectExceptionCode(789); $buyer->deposit(789); diff --git a/tests/Units/Service/DatabaseTest.php b/tests/Units/Service/DatabaseTest.php index 480dcb29a..991fadea9 100644 --- a/tests/Units/Service/DatabaseTest.php +++ b/tests/Units/Service/DatabaseTest.php @@ -6,10 +6,8 @@ use Bavix\Wallet\Internal\Exceptions\ExceptionInterface; use Bavix\Wallet\Internal\Exceptions\TransactionFailedException; -use Bavix\Wallet\Internal\Exceptions\TransactionStartException; use Bavix\Wallet\Internal\Service\DatabaseServiceInterface; use Bavix\Wallet\Test\Infra\TestCase; -use Illuminate\Support\Facades\DB; /** * @internal @@ -29,29 +27,4 @@ public function testCheckCode(): void throw new \RuntimeException('hello'); }); } - - /** - * @throws ExceptionInterface - */ - public function testCheckInTransaction(): void - { - $this->expectException(TransactionStartException::class); - $this->expectExceptionCode(ExceptionInterface::TRANSACTION_START); - - DB::beginTransaction(); - app(DatabaseServiceInterface::class)->transaction(static fn (): int => 42); - } - - public function testInterceptionErrorStartTransaction(): void - { - try { - DB::beginTransaction(); - app(DatabaseServiceInterface::class)->transaction(static fn (): int => 42); - self::fail(__METHOD__); - } catch (TransactionStartException) { - DB::rollBack(0); // for success - $result = app(DatabaseServiceInterface::class)->transaction(static fn (): int => 42); - self::assertSame(42, $result); - } - } } diff --git a/tests/Units/Service/LockServiceTest.php b/tests/Units/Service/LockServiceTest.php index 06d011861..2413c5edf 100644 --- a/tests/Units/Service/LockServiceTest.php +++ b/tests/Units/Service/LockServiceTest.php @@ -6,6 +6,7 @@ use Bavix\Wallet\Internal\Service\LockServiceInterface; use Bavix\Wallet\Test\Infra\TestCase; +use Illuminate\Support\Facades\DB; /** * @internal @@ -63,4 +64,45 @@ public function testLockDeep(): void self::assertTrue($checkIsBlock); self::assertFalse($lock->isBlocked($blockKey)); } + + public function testInTransactionLockable(): void + { + $blockKey1 = __METHOD__ . '1'; + $blockKey2 = __METHOD__ . '2'; + $lock = app(LockServiceInterface::class); + self::assertFalse($lock->isBlocked($blockKey1)); + self::assertFalse($lock->isBlocked($blockKey2)); + + $checkIsBlock1 = $lock->block($blockKey1, static fn () => $lock->isBlocked($blockKey1)); + self::assertTrue($checkIsBlock1); + self::assertFalse($lock->isBlocked($blockKey1)); + self::assertFalse($lock->isBlocked($blockKey2)); + + $checkIsBlock2 = $lock->block($blockKey2, static fn () => $lock->isBlocked($blockKey2)); + self::assertTrue($checkIsBlock2); + self::assertFalse($lock->isBlocked($blockKey1)); + self::assertFalse($lock->isBlocked($blockKey2)); + + DB::beginTransaction(); + + $checkIsBlock1 = $lock->block($blockKey1, static fn () => $lock->isBlocked($blockKey1)); + self::assertTrue($checkIsBlock1); + self::assertTrue($lock->isBlocked($blockKey1)); + self::assertFalse($lock->isBlocked($blockKey2)); + + $checkIsBlock2 = $lock->block($blockKey2, static fn () => $lock->isBlocked($blockKey2)); + self::assertTrue($checkIsBlock2); + self::assertTrue($lock->isBlocked($blockKey1)); + self::assertTrue($lock->isBlocked($blockKey2)); + + DB::commit(); + + self::assertTrue($lock->isBlocked($blockKey1)); + self::assertTrue($lock->isBlocked($blockKey2)); + + $lock->releases([$blockKey1, $blockKey2]); + + self::assertFalse($lock->isBlocked($blockKey1)); + self::assertFalse($lock->isBlocked($blockKey2)); + } }