Skip to content

Commit 608d428

Browse files
committed
feature #32198 [Lock] Split "StoreInterface" into multiple interfaces with less responsability (Simperfit)
This PR was squashed before being merged into the 4.4 branch (closes #32198). Discussion ---------- [Lock] Split "StoreInterface" into multiple interfaces with less responsability | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | no | New feature? | yes <!-- please update src/**/CHANGELOG.md files --> | BC breaks? | no <!-- see https://symfony.com/bc --> | Deprecations? | yes <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tests pass? | yes <!-- please add some, will be required by reviewers --> | Fixed tickets | Contribute to #28694 <!-- #-prefixed issue number(s), if any --> | License | MIT | Doc PR | TODO <!-- required for new features --> <!-- Replace this notice by a short README for your feature/bugfix. This will help people understand your PR and can be used as a start for the documentation. Additionally (see https://symfony.com/roadmap): - Bug fixes must be submitted against the lowest maintained branch where they apply (lowest branches are regularly merged to upper ones so they get the fixes too). - Features and deprecations must be submitted against branch 4.4. - Legacy code removals go to the master branch. --> We are removing the StoreInterface in order to split into multiple interface, it will help reduce de responsability of the StoreInterface. Firstly, since not all stores needs to have all the methods of the StoreInterface, we can split this like this in order to limit the methods that are needed for each store. Secondly, we add supportsX methods in order to avoid throwing exception when a store does not supports a feature it's easier an instance of the special interface or not, and it can return true/false on the support method. **Really big thanks to** @jderusse for working with me on this, 1-2 hours of talking together, and another 1-2 hours of pre-review :). now giving it to the whole community ! *some time has been sponsored by* @izisolutions Commits ------- 91fcbea [Lock] Split \"StoreInterface\" into multiple interfaces with less responsability
2 parents 350ec6c + 91fcbea commit 608d428

16 files changed

+291
-78
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Lock;
13+
14+
use Symfony\Component\Lock\Exception\LockConflictedException;
15+
use Symfony\Component\Lock\Exception\NotSupportedException;
16+
17+
/**
18+
* @author Hamza Amrouche <hamza.simperfit@gmail.com>
19+
*/
20+
interface BlockingStoreInterface
21+
{
22+
/**
23+
* Waits until a key becomes free, then stores the resource.
24+
*
25+
* If the store does not support this feature it should throw a NotSupportedException.
26+
*
27+
* @throws LockConflictedException
28+
* @throws NotSupportedException
29+
*/
30+
public function waitAndSave(Key $key);
31+
32+
/**
33+
* Checks if the store can wait until a key becomes free before storing the resource.
34+
*/
35+
public function supportsWaitAndSave(): bool;
36+
}

src/Symfony/Component/Lock/CHANGELOG.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ CHANGELOG
44
4.4.0
55
-----
66

7-
* added InvalidTtlException
8-
7+
* added InvalidTtlException
8+
* deprecated `Symfony\Component\Lock\StoreInterface` in favor of `Symfony\Component\Lock\BlockingStoreInterface` and `Symfony\Component\Lock\PersistStoreInterface`
9+
910
4.2.0
1011
-----
1112

src/Symfony/Component/Lock/Lock.php

+9-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Symfony\Component\Lock\Exception\LockConflictedException;
2020
use Symfony\Component\Lock\Exception\LockExpiredException;
2121
use Symfony\Component\Lock\Exception\LockReleasingException;
22+
use Symfony\Component\Lock\Exception\NotSupportedException;
2223

2324
/**
2425
* Lock is the default implementation of the LockInterface.
@@ -36,12 +37,12 @@ final class Lock implements LockInterface, LoggerAwareInterface
3637
private $dirty = false;
3738

3839
/**
39-
* @param Key $key Resource to lock
40-
* @param StoreInterface $store Store used to handle lock persistence
41-
* @param float|null $ttl Maximum expected lock duration in seconds
42-
* @param bool $autoRelease Whether to automatically release the lock or not when the lock instance is destroyed
40+
* @param Key $key Resource to lock
41+
* @param PersistStoreInterface $store Store used to handle lock persistence
42+
* @param float|null $ttl Maximum expected lock duration in seconds
43+
* @param bool $autoRelease Whether to automatically release the lock or not when the lock instance is destroyed
4344
*/
44-
public function __construct(Key $key, StoreInterface $store, float $ttl = null, bool $autoRelease = true)
45+
public function __construct(Key $key, PersistStoreInterface $store, float $ttl = null, bool $autoRelease = true)
4546
{
4647
$this->store = $store;
4748
$this->key = $key;
@@ -70,6 +71,9 @@ public function acquire($blocking = false)
7071
{
7172
try {
7273
if ($blocking) {
74+
if (!($this->store instanceof StoreInterface) && !($this->store instanceof BlockingStoreInterface && $this->store->supportsWaitAndSave())) {
75+
throw new NotSupportedException(sprintf('The store "%s" does not support blocking locks.', \get_class($this->store)));
76+
}
7377
$this->store->waitAndSave($this->key);
7478
} else {
7579
$this->store->save($this->key);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Lock;
13+
14+
use Symfony\Component\Lock\Exception\LockAcquiringException;
15+
use Symfony\Component\Lock\Exception\LockConflictedException;
16+
use Symfony\Component\Lock\Exception\LockReleasingException;
17+
18+
/**
19+
* @author Jérémy Derussé <jeremy@derusse.com>
20+
*/
21+
interface PersistStoreInterface
22+
{
23+
/**
24+
* Stores the resource if it's not locked by someone else.
25+
*
26+
* @throws LockAcquiringException
27+
* @throws LockConflictedException
28+
*/
29+
public function save(Key $key);
30+
31+
/**
32+
* Removes a resource from the storage.
33+
*
34+
* @throws LockReleasingException
35+
*/
36+
public function delete(Key $key);
37+
38+
/**
39+
* Returns whether or not the resource exists in the storage.
40+
*
41+
* @return bool
42+
*/
43+
public function exists(Key $key);
44+
45+
/**
46+
* Extends the TTL of a resource.
47+
*
48+
* @param float $ttl amount of seconds to keep the lock in the store
49+
*
50+
* @throws LockConflictedException
51+
*/
52+
public function putOffExpiration(Key $key, $ttl);
53+
}

src/Symfony/Component/Lock/Store/CombinedStore.php

+19-6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\Lock\Exception\LockConflictedException;
1919
use Symfony\Component\Lock\Exception\NotSupportedException;
2020
use Symfony\Component\Lock\Key;
21+
use Symfony\Component\Lock\PersistStoreInterface;
2122
use Symfony\Component\Lock\StoreInterface;
2223
use Symfony\Component\Lock\Strategy\StrategyInterface;
2324

@@ -26,27 +27,27 @@
2627
*
2728
* @author Jérémy Derussé <jeremy@derusse.com>
2829
*/
29-
class CombinedStore implements StoreInterface, LoggerAwareInterface
30+
class CombinedStore implements StoreInterface, PersistStoreInterface, LoggerAwareInterface
3031
{
3132
use LoggerAwareTrait;
3233
use ExpiringStoreTrait;
3334

34-
/** @var StoreInterface[] */
35+
/** @var PersistStoreInterface[] */
3536
private $stores;
3637
/** @var StrategyInterface */
3738
private $strategy;
3839

3940
/**
40-
* @param StoreInterface[] $stores The list of synchronized stores
41-
* @param StrategyInterface $strategy
41+
* @param PersistStoreInterface[] $stores The list of synchronized stores
42+
* @param StrategyInterface $strategy
4243
*
4344
* @throws InvalidArgumentException
4445
*/
4546
public function __construct(array $stores, StrategyInterface $strategy)
4647
{
4748
foreach ($stores as $store) {
48-
if (!$store instanceof StoreInterface) {
49-
throw new InvalidArgumentException(sprintf('The store must implement "%s". Got "%s".', StoreInterface::class, \get_class($store)));
49+
if (!$store instanceof PersistStoreInterface) {
50+
throw new InvalidArgumentException(sprintf('The store must implement "%s". Got "%s".', PersistStoreInterface::class, \get_class($store)));
5051
}
5152
}
5253

@@ -92,8 +93,12 @@ public function save(Key $key)
9293
throw new LockConflictedException();
9394
}
9495

96+
/**
97+
* {@inheritdoc}
98+
*/
9599
public function waitAndSave(Key $key)
96100
{
101+
@trigger_error(sprintf('%s::%s has been deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', \get_class($this), __METHOD__), E_USER_DEPRECATED);
97102
throw new NotSupportedException(sprintf('The store "%s" does not supports blocking locks.', \get_class($this)));
98103
}
99104

@@ -181,4 +186,12 @@ public function exists(Key $key)
181186

182187
return false;
183188
}
189+
190+
/**
191+
* {@inheritdoc}
192+
*/
193+
public function supportsWaitAndSave(): bool
194+
{
195+
return false;
196+
}
184197
}

src/Symfony/Component/Lock/Store/FlockStore.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111

1212
namespace Symfony\Component\Lock\Store;
1313

14+
use Symfony\Component\Lock\BlockingStoreInterface;
1415
use Symfony\Component\Lock\Exception\InvalidArgumentException;
1516
use Symfony\Component\Lock\Exception\LockConflictedException;
1617
use Symfony\Component\Lock\Exception\LockStorageException;
1718
use Symfony\Component\Lock\Key;
19+
use Symfony\Component\Lock\PersistStoreInterface;
1820
use Symfony\Component\Lock\StoreInterface;
1921

2022
/**
@@ -27,7 +29,7 @@
2729
* @author Romain Neutron <imprec@gmail.com>
2830
* @author Nicolas Grekas <p@tchwork.com>
2931
*/
30-
class FlockStore implements StoreInterface
32+
class FlockStore implements StoreInterface, BlockingStoreInterface, PersistStoreInterface
3133
{
3234
private $lockPath;
3335

@@ -64,6 +66,14 @@ public function waitAndSave(Key $key)
6466
$this->lock($key, true);
6567
}
6668

69+
/**
70+
* {@inheritdoc}
71+
*/
72+
public function supportsWaitAndSave(): bool
73+
{
74+
return true;
75+
}
76+
6777
private function lock(Key $key, $blocking)
6878
{
6979
// The lock is maybe already acquired.

src/Symfony/Component/Lock/Store/MemcachedStore.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515
use Symfony\Component\Lock\Exception\InvalidTtlException;
1616
use Symfony\Component\Lock\Exception\LockConflictedException;
1717
use Symfony\Component\Lock\Key;
18+
use Symfony\Component\Lock\PersistStoreInterface;
1819
use Symfony\Component\Lock\StoreInterface;
1920

2021
/**
2122
* MemcachedStore is a StoreInterface implementation using Memcached as store engine.
2223
*
2324
* @author Jérémy Derussé <jeremy@derusse.com>
2425
*/
25-
class MemcachedStore implements StoreInterface
26+
class MemcachedStore implements StoreInterface, PersistStoreInterface
2627
{
2728
use ExpiringStoreTrait;
2829

@@ -69,8 +70,12 @@ public function save(Key $key)
6970
$this->checkNotExpired($key);
7071
}
7172

73+
/**
74+
* {@inheritdoc}
75+
*/
7276
public function waitAndSave(Key $key)
7377
{
78+
@trigger_error(sprintf('%s has been deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', __METHOD__));
7479
throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', \get_class($this)));
7580
}
7681

src/Symfony/Component/Lock/Store/PdoStore.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Symfony\Component\Lock\Exception\LockConflictedException;
2020
use Symfony\Component\Lock\Exception\NotSupportedException;
2121
use Symfony\Component\Lock\Key;
22+
use Symfony\Component\Lock\PersistStoreInterface;
2223
use Symfony\Component\Lock\StoreInterface;
2324

2425
/**
@@ -34,7 +35,7 @@
3435
*
3536
* @author Jérémy Derussé <jeremy@derusse.com>
3637
*/
37-
class PdoStore implements StoreInterface
38+
class PdoStore implements StoreInterface, PersistStoreInterface
3839
{
3940
use ExpiringStoreTrait;
4041

@@ -145,6 +146,7 @@ public function save(Key $key)
145146
*/
146147
public function waitAndSave(Key $key)
147148
{
149+
@trigger_error(sprintf('%s has been deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', __METHOD__));
148150
throw new NotSupportedException(sprintf('The store "%s" does not supports blocking locks.', __METHOD__));
149151
}
150152

src/Symfony/Component/Lock/Store/RedisStore.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@
1717
use Symfony\Component\Lock\Exception\InvalidTtlException;
1818
use Symfony\Component\Lock\Exception\LockConflictedException;
1919
use Symfony\Component\Lock\Key;
20+
use Symfony\Component\Lock\PersistStoreInterface;
2021
use Symfony\Component\Lock\StoreInterface;
2122

2223
/**
2324
* RedisStore is a StoreInterface implementation using Redis as store engine.
2425
*
2526
* @author Jérémy Derussé <jeremy@derusse.com>
2627
*/
27-
class RedisStore implements StoreInterface
28+
class RedisStore implements StoreInterface, PersistStoreInterface
2829
{
2930
use ExpiringStoreTrait;
3031

@@ -77,6 +78,7 @@ public function save(Key $key)
7778
*/
7879
public function waitAndSave(Key $key)
7980
{
81+
@trigger_error(sprintf('%s::%s has been deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', \get_class($this), __METHOD__), E_USER_DEPRECATED);
8082
throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', \get_class($this)));
8183
}
8284

src/Symfony/Component/Lock/Store/RetryTillSaveStore.php

+15-5
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
use Psr\Log\LoggerAwareInterface;
1515
use Psr\Log\LoggerAwareTrait;
1616
use Psr\Log\NullLogger;
17+
use Symfony\Component\Lock\BlockingStoreInterface;
1718
use Symfony\Component\Lock\Exception\LockConflictedException;
1819
use Symfony\Component\Lock\Key;
20+
use Symfony\Component\Lock\PersistStoreInterface;
1921
use Symfony\Component\Lock\StoreInterface;
2022

2123
/**
@@ -24,7 +26,7 @@
2426
*
2527
* @author Jérémy Derussé <jeremy@derusse.com>
2628
*/
27-
class RetryTillSaveStore implements StoreInterface, LoggerAwareInterface
29+
class RetryTillSaveStore implements PersistStoreInterface, BlockingStoreInterface, StoreInterface, LoggerAwareInterface
2830
{
2931
use LoggerAwareTrait;
3032

@@ -33,11 +35,11 @@ class RetryTillSaveStore implements StoreInterface, LoggerAwareInterface
3335
private $retryCount;
3436

3537
/**
36-
* @param StoreInterface $decorated The decorated StoreInterface
37-
* @param int $retrySleep Duration in ms between 2 retry
38-
* @param int $retryCount Maximum amount of retry
38+
* @param PersistStoreInterface $decorated The decorated StoreInterface
39+
* @param int $retrySleep Duration in ms between 2 retry
40+
* @param int $retryCount Maximum amount of retry
3941
*/
40-
public function __construct(StoreInterface $decorated, int $retrySleep = 100, int $retryCount = PHP_INT_MAX)
42+
public function __construct(PersistStoreInterface $decorated, int $retrySleep = 100, int $retryCount = PHP_INT_MAX)
4143
{
4244
$this->decorated = $decorated;
4345
$this->retrySleep = $retrySleep;
@@ -99,4 +101,12 @@ public function exists(Key $key)
99101
{
100102
return $this->decorated->exists($key);
101103
}
104+
105+
/**
106+
* {@inheritdoc}
107+
*/
108+
public function supportsWaitAndSave(): bool
109+
{
110+
return true;
111+
}
102112
}

0 commit comments

Comments
 (0)