Skip to content

Commit f226dd1

Browse files
committed
Allow to use AtomicServiceInterface in a broad sense.
1 parent e46e640 commit f226dd1

11 files changed

+158
-11
lines changed

src/Internal/Service/LockService.php

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212

1313
final class LockService implements LockServiceInterface
1414
{
15+
/**
16+
* @var array<string, bool>
17+
*/
18+
private array $lockedKeys = [];
19+
1520
private CacheRepository $cache;
1621

1722
private int $seconds;
@@ -27,10 +32,20 @@ public function __construct(CacheFactory $cacheFactory)
2732
*/
2833
public function block(string $key, callable $callback): mixed
2934
{
30-
return $this->getLockProvider()
31-
->lock($key)
32-
->block($this->seconds, $callback)
33-
;
35+
if (array_key_exists($key, $this->lockedKeys)) {
36+
return $callback();
37+
}
38+
39+
$this->lockedKeys[$key] = true;
40+
41+
try {
42+
return $this->getLockProvider()
43+
->lock($key)
44+
->block($this->seconds, $callback)
45+
;
46+
} finally {
47+
unset($this->lockedKeys[$key]);
48+
}
3449
}
3550

3651
/**

src/Services/AssistantServiceInterface.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,26 @@
1212
interface AssistantServiceInterface
1313
{
1414
/**
15+
* Helps to quickly extract the uuid from an object.
16+
*
1517
* @param non-empty-array<array-key, TransactionDtoInterface|TransferDtoInterface> $objects
1618
*
1719
* @return non-empty-array<array-key, string>
1820
*/
1921
public function getUuids(array $objects): array;
2022

2123
/**
24+
* Helps to quickly calculate the amount.
25+
*
2226
* @param non-empty-array<TransactionDtoInterface> $transactions
2327
*
2428
* @return array<int, string>
2529
*/
2630
public function getSums(array $transactions): array;
2731

2832
/**
33+
* Helps to get cart meta data for a product.
34+
*
2935
* @return array<mixed>|null
3036
*/
3137
public function getMeta(BasketDtoInterface $basketDto, ProductInterface $product): ?array;

src/Services/AtmServiceInterface.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@
1212
interface AtmServiceInterface
1313
{
1414
/**
15+
* Helps to get to create a bunch of transaction objects.
16+
*
1517
* @param non-empty-array<array-key, TransactionDtoInterface> $objects
1618
*
1719
* @return non-empty-array<string, Transaction>
1820
*/
1921
public function makeTransactions(array $objects): array;
2022

2123
/**
24+
* Helps to get to create a bunch of transfer objects.
25+
*
2226
* @param non-empty-array<array-key, TransferDtoInterface> $objects
2327
*
2428
* @return non-empty-array<string, Transfer>

src/Services/AtomicService.php

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,24 @@ public function __construct(
2626
) {
2727
}
2828

29+
/**
30+
* @param non-empty-array<Wallet> $objects
31+
*
32+
* @throws LockProviderNotFoundException
33+
* @throws RecordsNotFoundException
34+
* @throws TransactionFailedException
35+
* @throws ExceptionInterface
36+
*/
37+
public function blocks(array $objects, callable $callback): mixed
38+
{
39+
$callable = fn () => $this->databaseService->transaction($callback);
40+
foreach ($objects as $object) {
41+
$callable = fn () => $this->lockService->block($this->key($object), $callable);
42+
}
43+
44+
return $callable();
45+
}
46+
2947
/**
3048
* @throws LockProviderNotFoundException
3149
* @throws RecordsNotFoundException
@@ -34,10 +52,7 @@ public function __construct(
3452
*/
3553
public function block(Wallet $object, callable $callback): mixed
3654
{
37-
return $this->lockService->block(
38-
$this->key($object),
39-
fn () => $this->databaseService->transaction($callback)
40-
);
55+
return $this->blocks([$object], $callback);
4156
}
4257

4358
private function key(Wallet $object): string

src/Services/AtomicServiceInterface.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
interface AtomicServiceInterface
1414
{
1515
/**
16+
* The method atomically locks the transaction for other concurrent requests.
17+
*
1618
* @template T
1719
* @param callable(): T $callback
1820
* @return T
@@ -23,4 +25,20 @@ interface AtomicServiceInterface
2325
* @throws ExceptionInterface
2426
*/
2527
public function block(Wallet $object, callable $callback): mixed;
28+
29+
/**
30+
* Use when you need to atomically change a lot of wallets and atomic in its pure form is not suitable. Use with
31+
* caution, generates N requests to the lock service.
32+
*
33+
* @template T
34+
* @param non-empty-array<Wallet> $objects
35+
* @param callable(): T $callback
36+
* @return T
37+
*
38+
* @throws LockProviderNotFoundException
39+
* @throws RecordsNotFoundException
40+
* @throws TransactionFailedException
41+
* @throws ExceptionInterface
42+
*/
43+
public function blocks(array $objects, callable $callback): mixed;
2644
}

src/Services/BasketServiceInterface.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,8 @@
88

99
interface BasketServiceInterface
1010
{
11+
/**
12+
* A quick way to check stock. Able to check in batches, necessary for quick payments.
13+
*/
1114
public function availability(AvailabilityDtoInterface $availabilityDto): bool;
1215
}

src/Services/EagerLoaderServiceInterface.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
use Bavix\Wallet\Internal\Dto\BasketDtoInterface;
88

9+
/**
10+
* Ad hoc solution... Needed for internal purposes only. Helps to optimize greedy queries inside laravel.
11+
*/
912
interface EagerLoaderServiceInterface
1013
{
1114
public function loadWalletsByBasket(BasketDtoInterface $basketDto): void;

src/Services/ExchangeServiceInterface.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44

55
namespace Bavix\Wallet\Services;
66

7+
/**
8+
* Currency exchange contract between wallets.
9+
*/
710
interface ExchangeServiceInterface
811
{
12+
/**
13+
* Currency conversion method.
14+
*/
915
public function convertTo(string $fromCurrency, string $toCurrency, float|int|string $amount): string;
1016
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bavix\Wallet\Test\Units\Service;
6+
7+
use Bavix\Wallet\Services\AtomicServiceInterface;
8+
use Bavix\Wallet\Test\Infra\Factories\BuyerFactory;
9+
use Bavix\Wallet\Test\Infra\Models\Buyer;
10+
use Bavix\Wallet\Test\Infra\TestCase;
11+
12+
/**
13+
* @internal
14+
*/
15+
final class AtomicServiceTest extends TestCase
16+
{
17+
public function testBlock(): void
18+
{
19+
$atomic = app(AtomicServiceInterface::class);
20+
21+
/** @var Buyer $user1 */
22+
/** @var Buyer $user2 */
23+
[$user1, $user2] = BuyerFactory::times(2)->create();
24+
25+
$user1->deposit(1000);
26+
27+
$atomic->blocks(
28+
[$user1->wallet, $user2->wallet],
29+
fn () => collect([
30+
fn () => $user1->transfer($user2, 500),
31+
fn () => $user1->transfer($user2, 500),
32+
fn () => $user2->transfer($user1, 500),
33+
])
34+
->map(fn ($fx) => $fx()),
35+
);
36+
37+
self::assertSame(1, $user2->transfers()->count());
38+
self::assertSame(2, $user1->transfers()->count());
39+
self::assertSame(3, $user2->transactions()->count());
40+
self::assertSame(4, $user1->transactions()->count());
41+
42+
self::assertSame(500, $user1->balanceInt);
43+
self::assertSame(500, $user2->balanceInt);
44+
}
45+
}

tests/Units/Service/LockServiceTest.php

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,40 @@ final class LockServiceTest extends TestCase
1515
public function testBlock(): void
1616
{
1717
$lock = app(LockServiceInterface::class);
18-
$lock->block('hello', static fn () => 'hello world');
19-
$lock->block('hello', static fn () => 'hello world');
18+
19+
$message = $lock->block(__METHOD__, static fn () => 'hello world');
20+
self::assertSame('hello world', $message);
21+
22+
$message = $lock->block(__METHOD__, static fn () => 'hello world');
23+
self::assertSame('hello world', $message);
24+
25+
self::assertTrue(true);
26+
}
27+
28+
public function testLockFailed(): void
29+
{
30+
$lock = app(LockServiceInterface::class);
31+
32+
try {
33+
$lock->block(__METHOD__, static fn () => throw new \Exception('hello world'));
34+
} catch (\Throwable $throwable) {
35+
self::assertSame('hello world', $throwable->getMessage());
36+
}
37+
38+
$message = $lock->block(__METHOD__, static fn () => 'hello world');
39+
self::assertSame('hello world', $message);
40+
self::assertTrue(true);
41+
}
42+
43+
public function testLockDeep(): void
44+
{
45+
$lock = app(LockServiceInterface::class);
46+
$message = $lock->block(
47+
__METHOD__,
48+
static fn () => $lock->block(__METHOD__, static fn () => 'hello world'),
49+
);
50+
51+
self::assertSame('hello world', $message);
2052
self::assertTrue(true);
2153
}
2254
}

0 commit comments

Comments
 (0)