Skip to content

Commit 531f1c2

Browse files
authored
Merge pull request #412 from bavix/transtate
[7.1] Transaction State
2 parents 4a0cea9 + b59ffa6 commit 531f1c2

30 files changed

+634
-247
lines changed

.phpstorm.meta.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
use Bavix\Wallet\Services\CommonServiceLegacy;
4040
use Bavix\Wallet\Services\MetaServiceLegacy;
4141
use Bavix\Wallet\Services\TaxServiceInterface;
42-
use Bavix\Wallet\Services\WalletServiceLegacy;
4342

4443
override(\app(0), map([
4544
// internal.assembler
@@ -93,7 +92,6 @@
9392
// lagacy.services
9493
CommonServiceLegacy::class => CommonServiceLegacy::class,
9594
MetaServiceLegacy::class => MetaServiceLegacy::class,
96-
WalletServiceLegacy::class => WalletServiceLegacy::class,
9795
]));
9896

9997
}

changelog.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
### Added
10+
- Transaction support.
11+
- Now, within the transaction, the wallet has its own balance state.
12+
13+
### Updated
14+
- Due to the state within transactions, I was able to speed up the computation up to 25 times for complex transfers.
15+
16+
### Removed
17+
- class `WalletServiceLegacy`
18+
919
## [7.0.0] - 2021-11-25
1020
### Updated
1121
- Optimization of the `payFreeCart` and `payFree` request. Now the package does not update the repository. But there is no point in updating it, because the client does not pay anything.

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
"vimeo/psalm": "^4.12"
4747
},
4848
"suggest": {
49-
"bavix/laravel-wallet-swap": "Addition to the laravel-wallet library for quick setting of exchange rates"
49+
"bavix/laravel-wallet-swap": "Addition to the laravel-wallet library for quick setting of exchange rates",
50+
"bavix/laravel-wallet-warmup": "Addition to the laravel-wallet library for refresh balance wallets"
5051
},
5152
"autoload": {
5253
"psr-4": {

config/config.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
use Bavix\Wallet\Services\ExchangeService;
3434
use Bavix\Wallet\Services\PrepareService;
3535
use Bavix\Wallet\Services\PurchaseService;
36+
use Bavix\Wallet\Services\RegulatorService;
3637
use Bavix\Wallet\Services\TaxService;
3738

3839
return [
@@ -78,6 +79,7 @@
7879
'atomic' => AtomicService::class,
7980
'basket' => BasketService::class,
8081
'bookkeeper' => BookkeeperService::class,
82+
'regulator' => RegulatorService::class,
8183
'cast' => CastService::class,
8284
'consistency' => ConsistencyService::class,
8385
'discount' => DiscountService::class,

docs/_sidebar.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@
2828
- [Refund](refund)
2929
- [Gift](gift)
3030
- [Cart](cart)
31-
31+
32+
- Transactions
33+
34+
- [Transaction](transaction)
35+
- [Race condition](race-condition)
36+
3237
- Additions
3338

3439
- [Wallet Swap](laravel-wallet-swap)

docs/race-condition.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
## Race Condition
2+
3+
A common issue in the issue is about race conditions.
4+
5+
If you have not yet imported the config into the project, then you need to do this.
6+
```bash
7+
php artisan vendor:publish --tag=laravel-wallet-config
8+
```
9+
10+
Previously, there was a vacuum package, but now it is a part of the core. You just need to configure the lock service and the cache service in the package configuration `wallet.php`.
11+
12+
```php
13+
/**
14+
* A system for dealing with race conditions.
15+
*/
16+
'lock' => [
17+
'driver' => 'array',
18+
'seconds' => 1,
19+
],
20+
```
21+
22+
To enable the fight against race conditions, you need to select a provider that supports work with locks. I recommend `redis`.
23+
24+
There is a setting for storing the state of the wallet, I recommend choosing `redis` here too.
25+
26+
```php
27+
/**
28+
* Storage of the state of the balance of wallets.
29+
*/
30+
'cache' => ['driver' => 'array'],
31+
```
32+
33+
You need `redis-server` and `php-redis`.
34+
35+
Redis is recommended but not required. You can choose whatever the [framework](https://laravel.com/docs/8.x/cache#introduction) offers you.
36+
37+
It worked!

docs/transaction.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
## Transaction
2+
3+
Sometimes you need to execute many simple queries. You want to keep the data atomic. To do this, you need `laravel-wallet` v7.1+.
4+
5+
It is necessary to write off the amount from the balance and raise the ad in the search. What happens if the service for raising an ad fails? We wrote off the money, but did not raise the ad. Received reputational losses. We can imagine the opposite situation, we first raise the ad in the search, but it does not work to write off the money. There are not enough funds. This functionality will help to solve all this. We monitor ONLY the state of the wallet, the rest falls on the developer. Let's take an unsuccessful lift, for example.
6+
7+
```php
8+
use Bavix\Wallet\Internal\Service\DatabaseServiceInterface;
9+
10+
/** @var object $businessLogicService */
11+
/** @var \Bavix\Wallet\Models\Wallet $payer */
12+
$payer->balanceInt; // 9999
13+
app(DatabaseServiceInterface::class)->transaction(statuc function () use ($payer) {
14+
$payer->withdraw(1000); // 8999
15+
$businessLogicService->doingMagic($payer); // throws an exception
16+
}); // rollback payer balance
17+
18+
$payer->balanceInt; // 9999
19+
```
20+
21+
Now let's look at the successful raising of the ad.
22+
23+
```php
24+
use Bavix\Wallet\Internal\Service\DatabaseServiceInterface;
25+
26+
/** @var object $businessLogicService */
27+
/** @var \Bavix\Wallet\Models\Wallet $payer */
28+
$payer->balanceInt; // 9999
29+
app(DatabaseServiceInterface::class)->transaction(statuc function () use ($payer) {
30+
$payer->withdraw(1000); // 8999
31+
$businessLogicService->doingMagic($payer); // successfully
32+
}); // commit payer balance
33+
34+
$payer->balanceInt; // 8999
35+
```
36+
37+
It worked!

rector.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Rector\Core\Configuration\Option;
66
use Rector\Laravel\Set\LaravelSetList;
77
use Rector\Php74\Rector\Property\TypedPropertyRector;
8+
use Rector\PHPUnit\Set\PHPUnitSetList;
89
use Rector\Set\ValueObject\SetList;
910
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
1011

@@ -17,6 +18,7 @@
1718
]);
1819

1920
// Define what rule sets will be applied
21+
$containerConfigurator->import(PHPUnitSetList::PHPUNIT_91);
2022
$containerConfigurator->import(LaravelSetList::LARAVEL_60);
2123
$containerConfigurator->import(SetList::DEAD_CODE);
2224
$containerConfigurator->import(SetList::PHP_74);

src/Internal/Service/DatabaseService.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use Bavix\Wallet\Internal\Exceptions\ExceptionInterface;
88
use Bavix\Wallet\Internal\Exceptions\TransactionFailedException;
9-
use Closure;
9+
use Bavix\Wallet\Services\RegulatorServiceInterface;
1010
use Illuminate\Config\Repository as ConfigRepository;
1111
use Illuminate\Database\ConnectionInterface;
1212
use Illuminate\Database\ConnectionResolverInterface;
@@ -15,12 +15,15 @@
1515

1616
final class DatabaseService implements DatabaseServiceInterface
1717
{
18+
private RegulatorServiceInterface $regulatorService;
1819
private ConnectionInterface $connection;
1920

2021
public function __construct(
2122
ConnectionResolverInterface $connectionResolver,
23+
RegulatorServiceInterface $regulatorService,
2224
ConfigRepository $config
2325
) {
26+
$this->regulatorService = $regulatorService;
2427
$this->connection = $connectionResolver->connection(
2528
$config->get('wallet.database.connection')
2629
);
@@ -40,10 +43,25 @@ public function transaction(callable $callback)
4043
return $callback();
4144
}
4245

43-
return $this->connection->transaction(Closure::fromCallable($callback));
46+
$this->regulatorService->purge();
47+
48+
return $this->connection->transaction(function () use ($callback) {
49+
$result = $callback();
50+
if ($result === false || (is_countable($result) && count($result) === 0)) {
51+
$this->regulatorService->purge();
52+
} else {
53+
$this->regulatorService->approve();
54+
}
55+
56+
return $result;
57+
});
4458
} catch (RecordsNotFoundException|ExceptionInterface $exception) {
59+
$this->regulatorService->purge();
60+
4561
throw $exception;
4662
} catch (Throwable $throwable) {
63+
$this->regulatorService->purge();
64+
4765
throw new TransactionFailedException(
4866
'Transaction failed',
4967
ExceptionInterface::TRANSACTION_FAILED,

src/Internal/Service/StorageService.php

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,38 @@
77
use Bavix\Wallet\Internal\Exceptions\ExceptionInterface;
88
use Bavix\Wallet\Internal\Exceptions\LockProviderNotFoundException;
99
use Bavix\Wallet\Internal\Exceptions\RecordNotFoundException;
10-
use Illuminate\Cache\CacheManager;
11-
use Illuminate\Config\Repository as ConfigRepository;
1210
use Illuminate\Contracts\Cache\Repository as CacheRepository;
1311

1412
final class StorageService implements StorageServiceInterface
1513
{
1614
private LockServiceInterface $lockService;
1715
private MathServiceInterface $mathService;
18-
private CacheRepository $cache;
16+
private CacheRepository $cacheRepository;
1917

2018
public function __construct(
21-
CacheManager $cacheManager,
22-
ConfigRepository $config,
2319
LockServiceInterface $lockService,
24-
MathServiceInterface $mathService
20+
MathServiceInterface $mathService,
21+
CacheRepository $cacheRepository
2522
) {
23+
$this->cacheRepository = $cacheRepository;
2624
$this->mathService = $mathService;
2725
$this->lockService = $lockService;
28-
$this->cache = $cacheManager->driver(
29-
$config->get('wallet.cache.driver', 'array')
30-
);
3126
}
3227

3328
public function flush(): bool
3429
{
35-
return $this->cache->clear();
30+
return $this->cacheRepository->clear();
3631
}
3732

3833
public function missing(string $key): bool
3934
{
40-
return $this->cache->forget($key);
35+
return $this->cacheRepository->forget($key);
4136
}
4237

4338
/** @throws RecordNotFoundException */
4439
public function get(string $key): string
4540
{
46-
$value = $this->cache->get($key);
41+
$value = $this->cacheRepository->get($key);
4742
if ($value === null) {
4843
throw new RecordNotFoundException(
4944
'The repository did not find the object',
@@ -54,9 +49,10 @@ public function get(string $key): string
5449
return $this->mathService->round($value);
5550
}
5651

52+
/** @param float|int|string $value */
5753
public function sync(string $key, $value): bool
5854
{
59-
return $this->cache->set($key, $value);
55+
return $this->cacheRepository->set($key, $value);
6056
}
6157

6258
/**

0 commit comments

Comments
 (0)