8000 feature #38593 [Lock][Semaphore] Add Factory::createFromKey and depre… · symfony/symfony@6e4d6eb · GitHub
[go: up one dir, main page]

Skip to content

Commit 6e4d6eb

Browse files
committed
feature #38593 [Lock][Semaphore] Add Factory::createFromKey and deprecate lock.store services (jderusse)
This PR was merged into the 5.x branch. Discussion ---------- [Lock][Semaphore] Add Factory::createFromKey and deprecate lock.store services I| Q | A | ------------- | --- | Branch? | 5.x | Bug fix? | no | New feature? | no | Deprecations? | yes | Tickets | / | License | MIT | Doc PR | todo The `lock` service and aliases have been deprecated in #38576. This PR also deprecate the `lock.store` service and aliases. People should always inject the factory into their services, except for one scenario: When they want building the `Lock` manually because they want to keep the key. This scenario require declaring `lock.store` service and `PersisingStoreInterface` aliases. This could be avoided if people had a method to create a lock from a given key. This PR adds a `LockFactory::createFromKey()` method and deprecate all `lock.store` services I also updated the Semaphore component for consistency, and helping people to only inject the SemahoreFactory. nb: use cases for serializing the keys: - Netflix allows only 5 users of the same family at the same time. - An holiday apartment rental avoid users putting the same apartment in the basket - ... Commits ------- 91fa3e3 Deprecate lock.store aliases
2 parents 72be305 + 91fa3e3 commit 6e4d6eb

File tree

9 files changed

+136
-17
lines changed

9 files changed

+136
-17
lines changed

UPGRADE-5.2.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ FrameworkBundle
1515
keep cache namespaces separated by environment of the app. The `%kernel.container_class%` (which includes the environment)
1616
used to be added by default to the seed, which is not the case anymore. This allows sharing caches between
1717
apps or different environments.
18-
* Deprecated the `lock.RESOURCE_NAME` service and the `lock` and `LockInterface` aliases, use `lock.RESOURCE_NAME.factory`, `lock.factory` or `LockFactory` instead.
18+
* Deprecated the `lock.RESOURCE_NAME` and `lock.RESOURCE_NAME.store` services and the `lock`, `LockInterface`, `lock.store` and `PersistingStoreInterface` aliases, use `lock.RESOURCE_NAME.factory`, `lock.factory` or `LockFactory` instead.
1919

2020
Form
2121
----

UPGRADE-6.0.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ FrameworkBundle
5858
* Removed `session.attribute_bag` service and `session.flash_bag` service.
5959
* The `form.factory`, `form.type.file`, `translator`, `security.csrf.token_manager`, `serializer`,
6060
`cache_clearer`, `filesystem` and `validator` services are now private.
61-
* Removed the `lock.RESOURCE_NAME` service and the `lock` and `LockInterface` aliases, use `lock.RESOURCE_NAME.factory`, `lock.factory` or `LockFactory` instead.
61+
* Removed the `lock.RESOURCE_NAME` and `lock.RESOURCE_NAME.store` services and the `lock`, `LockInterface`, `lock.store` and `PersistingStoreInterface` aliases, use `lock.RESOURCE_NAME.factory`, `lock.factory` or `LockFactory` instead.
6262

6363
HttpFoundation
6464
--------------

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ CHANGELOG
1414
* added `assertCheckboxChecked()` and `assertCheckboxNotChecked()` in `WebTestCase`
1515
* added `assertFormValue()` and `assertNoFormValue()` in `WebTestCase`
1616
* Added "--as-tree=3" option to `translation:update` command to dump messages as a tree-like structure. The given value defines the level where to switch to inline YAML
17-
* Deprecated the `lock.RESOURCE_NAME` service and the `lock` and `LockInterface` aliases, use `lock.RESOURCE_NAME.factory`, `lock.factory` or `LockFactory` instead.
17+
* Deprecated the `lock.RESOURCE_NAME` and `lock.RESOURCE_NAME.store` services and the `lock`, `LockInterface`, `lock.store` and `PersistingStoreInterface` aliases, use `lock.RESOURCE_NAME.factory`, `lock.factory` or `LockFactory` instead.
1818

1919
5.1.0
2020
-----

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,14 +1674,15 @@ private function registerLockConfiguration(array $config, ContainerBuilder $cont
16741674
if (\count($storeDefinitions) > 1) {
16751675
$combinedDefinition = new ChildDefinition('lock.store.combined.abstract');
16761676
$combinedDefinition->replaceArgument(0, $storeDefinitions);
1677-
$container->setDefinition('lock.'.$resourceName.'.store', $combinedDefinition);
1677+
$container->setDefinition('lock.'.$resourceName.'.store', $combinedDefinition)->setDeprecated('symfony/framework-bundle', '5.2', 'The "%service_id%" service is deprecated, use "lock.'.$resourceName.'.factory" instead.');
1678+
$container->setDefinition($storeDefinitionId = '.lock.'.$resourceName.'.store.'.$container->hash($resourceStores), $combinedDefinition);
16781679
} else {
1679-
$container->setAlias('lock.'.$resourceName. F438 '.store', new Alias((string) $storeDefinitions[0], false));
1680+
$container->setAlias('lock.'.$resourceName.'.store', (new Alias($storeDefinitionId, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "lock.'.$resourceName.'.factory" instead.'));
16801681
}
16811682

16821683
// Generate factories for each resource
16831684
$factoryDefinition = new ChildDefinition('lock.factory.abstract');
1684-
$factoryDefinition->replaceArgument(0, new Reference('lock.'.$resourceName.'.store'));
1685+
$factoryDefinition->replaceArgument(0, new Reference($storeDefinitionId));
16851686
$container->setDefinition('lock.'.$resourceName.'.factory', $factoryDefinition);
16861687

16871688
// Generate services for lock instances
@@ -1693,14 +1694,14 @@ private function registerLockConfiguration(array $config, ContainerBuilder $cont
16931694

16941695
// provide alias for default resource
16951696
if ('default' === $resourceName) {
1696-
$container->setAlias('lock.store', new Alias('lock.'.$resourceName.'.store', false));
1697+
$container->setAlias('lock.store', (new Alias($storeDefinitionId, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "lock.factory" instead.'));
16971698
$container->setAlias('lock.factory', new Alias('lock.'.$resourceName.'.factory', false));
16981699
$container->setAlias('lock', (new Alias('lock.'.$resourceName, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "lock.factory" instead.'));
1699-
$container->setAlias(PersistingStoreInterface::class, new Alias('lock.store', false));
1700+
$container->setAlias(PersistingStoreInterface::class, (new Alias($storeDefinitionId, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.'" instead.'));
17001701
$container->setAlias(LockFactory::class, new Alias('lock.factory', false));
17011702
$container->setAlias(LockInterface::class, (new Alias('lock.'.$resourceName, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.'" instead.'));
17021703
} else {
1703-
$container->registerAliasForArgument('lock.'.$resourceName.'.store', PersistingStoreInterface::class, $resourceName.'.lock.store');
1704+
$container->registerAliasForArgument($storeDefinitionId, PersistingStoreInterface::class, $resourceName.'.lock.store')->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.' '.$resourceName.'LockFactory" instead.');
17041705
$container->registerAliasForArgument('lock.'.$resourceName.'.factory', LockFactory::class, $resourceName.'.lock.factory');
17051706
$container->registerAliasForArgument('lock.'.$resourceName, LockInterface::class, $resourceName.'.lock')->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.' $'.$resourceName.'LockFactory" instead.');
17061707
}

src/Symfony/Component/Lock/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ CHANGELOG
1111
* deprecated `RetryTillSaveStore`, logic has been moved in `Lock` and is not needed anymore.
1212
* added `InMemoryStore`
1313
* added `PostgreSqlStore`
14+
* added the `LockFactory::CreateLockFromKey()` method.
1415

1516
5.1.0
1617
-----

src/Symfony/Component/Lock/LockFactory.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,19 @@ public function __construct(PersistingStoreInterface $store)
4343
*/
4444
public function createLock(string $resource, ?float $ttl = 300.0, bool $autoRelease = true): LockInterface
4545
{
46-
$lock = new Lock(new Key($resource), $this->store, $ttl, $autoRelease);
46+
return $this->createLockFromKey(new Key($resource), $ttl, $autoRelease);
47+
}
48+
49+
/**
50+
* Creates a lock from the given key.
51+
*
52+
* @param Key $key The key containing the lock's state
53+
* @param float|null $ttl Maximum expected lock duration in seconds
54+
* @param bool $autoRelease Whether to automatically release the lock or not when the lock instance is destroyed
55+
*/
56+
public function createLockFromKey(Key $key, ?float $ttl = 300.0, bool $autoRelease = true): LockInterface
57+
{
58+
$lock = new Lock($key, $this->store, $ttl, $autoRelease);
4759
$lock->setLogger($this->logger);
4860

4961
return $lock;

src/Symfony/Component/Lock/Tests/LockFactoryTest.php

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Psr\Log\LoggerInterface;
16+
use Symfony\Component\Lock\Key;
1617
use Symfony\Component\Lock\LockFactory;
17-
use Symfony\Component\Lock\LockInterface;
1818
use Symfony\Component\Lock\PersistingStoreInterface;
1919

2020
/**
@@ -25,12 +25,61 @@ class LockFactoryTest extends TestCase
2525
public function testCreateLock()
2626
{
2727
$store = $this->getMockBuilder(PersistingStoreInterface::class)->getMock();
28+
$store->expects($this->any())->method('exists')->willReturn(false);
29+
30+
$keys = [];
31+
$store
32+
->expects($this->exactly(2))
33+
->method('save')
34+
->with($this->callback(function ($key) use (&$keys) {
35+
$keys[] = $key;
36+
37+
return true;
38+
}))
39+
->willReturn(true);
40+
2841
$logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
2942
$factory = new LockFactory($store);
3043
$factory->setLogger($logger);
3144

32-
$lock = $factory->createLock('foo');
45+
$lock1 = $factory->createLock('foo');
46+
$lock2 = $factory->createLock('foo');
47+
48+
// assert lock1 and lock2 don't share the same state
49+
$lock1->acquire();
50+
$lock2->acquire();
51+
52+
$this->assertNotSame($keys[0], $keys[1]);
53+
}
54+
55+
public function testCreateLockFromKey()
56+
{
57+
$store = $this->getMockBuilder(PersistingStoreInterface::class)->getMock();
58+
$store->expects($this->any())->method('exists')->willReturn(false);
59+
60+
$keys = [];
61+
$store
62+
->expects($this->exactly(2))
63+
->method('save')
64+
->with($this->callback(function ($key) use (&$keys) {
65+
$keys[] = $key;
66+
67+
return true;
68+
}))
69+
->willReturn(true);
70+
71+
$logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
72+
$factory = new LockFactory($store);
73+
$factory->setLogger($logger);
74+
75+
$key = new Key('foo');
76+
$lock1 = $factory->createLockFromKey($key);
77+
$lock2 = $factory->createLockFromKey($key);
78+
79+
// assert lock1 and lock2 share the same state
80+
$lock1->acquire();
81+
$lock2->acquire();
3382

34-
$this->assertInstanceOf(LockInterface::class, $lock);
83+
$this->assertSame($keys[0], $keys[1]);
3584
}
3685
}

src/Symfony/Component/Semaphore/SemaphoreFactory.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,16 @@ public function __construct(PersistingStoreInterface $store)
4242
*/
4343
public function createSemaphore(string $resource, int $limit, int $weight = 1, ?float $ttlInSecond = 300.0, bool $autoRelease = true): SemaphoreInterface
4444
{
45-
$semaphore = new Semaphore(new Key($resource, $limit, $weight), $this->store, $ttlInSecond, $autoRelease);
45+
return $this->createSemaphoreFromKey(new Key($resource, $limit, $weight), $ttlInSecond, $autoRelease);
46+
}
47+
48+
/**
49+
* @param float|null $ttlInSecond Maximum expected semaphore duration in seconds
50+
* @param bool $autoRelease Whether to automatically release the semaphore or not when the semaphore instance is destroyed
51+
*/
52+
public function createSemaphoreFromKey(Key $key, ?float $ttlInSecond = 300.0, bool $autoRelease = true): SemaphoreInterface
53+
{
54+
$semaphore = new Semaphore($key, $this->store, $ttlInSecond, $autoRelease);
4655
$semaphore->setLogger($this->logger);
4756

4857
return $semaphore;

src/Symfony/Component/Semaphore/Tests/SemaphoreFactoryTest.php

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Psr\Log\LoggerInterface;
16+
use Symfony\Component\Semaphore\Key;
1617
use Symfony\Component\Semaphore\PersistingStoreInterface;
1718
use Symfony\Component\Semaphore\SemaphoreFactory;
18-
use Symfony\Component\Semaphore\SemaphoreInterface;
1919

2020
/**
2121
* @author Jérémy Derussé <jeremy@derusse.com>
@@ -26,12 +26,59 @@ class SemaphoreFactoryTest extends TestCase
2626
public function testCreateSemaphore()
2727
{
2828
$store = $this->getMockBuilder(PersistingStoreInterface::class)->getMock();
29+
30+
$keys = [];
31+
$store
32+
->expects($this->exactly(2))
33+
->method('save')
34+
->with($this->callback(function ($key) use (&$keys) {
35+
$keys[] = $key;
36+
37+
return true;
38+
}))
39+
->willReturn(true);
40+
2941
$logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
3042
$factory = new SemaphoreFactory($store);
3143
$factory->setLogger($logger);
3244

33-
$semaphore = $factory->createSemaphore('foo', 4);
45+
$semaphore1 = $factory->createSemaphore('foo', 4);
46+
$semaphore2 = $factory->createSemaphore('foo', 4);
47+
48+
// assert lock1 and lock2 don't share the same state
49+
$semaphore1->acquire();
50+
$semaphore2->acquire();
51+
52+
$this->assertNotSame($keys[0], $keys[1]);
53+
}
54+
55+
public function testCreateSemaphoreFromKey()
56+
{
57+
$store = $this->getMockBuilder(PersistingStoreInterface::class)->getMock();
58+
59+
$keys = [];
60+
$store
61+
->expects($this->exactly(2))
62+
->method('save')
63+
->with($this->callback(function ($key) use (&$keys) {
64+
$keys[] = $key;
65+
66+
return true;
67+
}))
68+
->willReturn(true);
69+
70+
$logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
71+
$factory = new SemaphoreFactory($store);
72+
$factory->setLogger($logger);
73+
74+
$key = new Key('foo', 4);
75+
$semaphore1 = $factory->createSemaphoreFromKey($key);
76+
$semaphore2 = $factory->createSemaphoreFromKey($key);
77+
78+
// assert lock1 and lock2 share the same state
79+
$semaphore1->acquire();
80+
$semaphore2->acquire();
3481

35-
$this->assertInstanceOf(SemaphoreInterface::class, $semaphore);
82+
$this->assertSame($keys[0], $keys[1]);
3683
}
3784
}

0 commit comments

Comments
 (0)
0