Skip to content

Commit bf8bc7f

Browse files
authored
Merge pull request #286 from InvisibleSmiley/test-284
Infinite loop when fetching some cache-related services via container in projects without registered `config` service
2 parents 66cf9a2 + 734d78e commit bf8bc7f

6 files changed

+132
-8
lines changed

docs/book/v3/application-integration/usage-in-a-laminas-mvc-application.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ return [
3737
];
3838
```
3939
40-
The factory `Laminas\Cache\Service\StorageCacheAbstractServiceFactory` uses the configuration, searches for the configuration key `caches` and creates the storage adapters using the discovered configuration.
40+
The factory `Laminas\Cache\Service\StorageCacheAbstractServiceFactory` uses the configuration, searches for the configuration key `caches` and creates the storage adapters using the discovered configuration.
41+
42+
WARNING: **Cache Named `config` Is Not Possible**
43+
A cache named `config` is not possible due to internal service conflicts with MVC configuration.
44+
The service named `config` is reserved for project configuration and thus cannot be used with the `caches` configuration.
4145
4246
## Create Controller
4347

src/Command/DeprecatedStorageFactoryConfigurationCheckCommandFactory.php

+16-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,21 @@ final class DeprecatedStorageFactoryConfigurationCheckCommandFactory
1919
{
2020
public function __invoke(ContainerInterface $container): DeprecatedStorageFactoryConfigurationCheckCommand
2121
{
22+
$config = $this->detectConfigFromContainer($container);
23+
24+
$schemaDetector = new DeprecatedSchemaDetector();
25+
return new DeprecatedStorageFactoryConfigurationCheckCommand(
26+
$config,
27+
$schemaDetector
28+
);
29+
}
30+
31+
private function detectConfigFromContainer(ContainerInterface $container): ArrayAccess
32+
{
33+
if (! $container->has('config')) {
34+
return new ArrayObject([]);
35+
}
36+
2237
$config = $container->get('config');
2338
if (is_array($config)) {
2439
$config = new ArrayObject($config);
@@ -28,10 +43,6 @@ public function __invoke(ContainerInterface $container): DeprecatedStorageFactor
2843
throw new RuntimeException('Configuration from container must be either `ArrayAccess` or an array.');
2944
}
3045

31-
$schemaDetector = new DeprecatedSchemaDetector();
32-
return new DeprecatedStorageFactoryConfigurationCheckCommand(
33-
$config,
34-
$schemaDetector
35-
);
46+
return $config;
3647
}
3748
}

src/ConfigProvider.php

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@
1010
use Laminas\Cache\Service\StoragePluginFactory;
1111
use Laminas\Cache\Service\StoragePluginFactoryFactory;
1212
use Laminas\Cache\Service\StoragePluginFactoryInterface;
13+
use Laminas\ServiceManager\ServiceManager;
1314
use Symfony\Component\Console\Command\Command;
1415

1516
use function class_exists;
1617

18+
/**
19+
* @psalm-import-type ServiceManagerConfiguration from ServiceManager
20+
*/
1721
class ConfigProvider
1822
{
1923
public const ADAPTER_PLUGIN_MANAGER_CONFIGURATION_KEY = 'storage_adapters';
@@ -34,7 +38,7 @@ public function __invoke()
3438
/**
3539
* Return default service mappings for laminas-cache.
3640
*
37-
* @return array
41+
* @return ServiceManagerConfiguration
3842
*/
3943
public function getDependencyConfig()
4044
{

src/Service/StorageCacheAbstractServiceFactory.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
*/
1515
class StorageCacheAbstractServiceFactory implements AbstractFactoryInterface
1616
{
17-
public const CACHES_CONFIGURATION_KEY = 'caches';
17+
public const CACHES_CONFIGURATION_KEY = 'caches';
18+
private const RESERVED_CONFIG_SERVICE_NAME = 'config';
1819

1920
/** @var array<string,mixed>|null */
2021
protected $config;
@@ -32,6 +33,10 @@ class StorageCacheAbstractServiceFactory implements AbstractFactoryInterface
3233
*/
3334
public function canCreate(ContainerInterface $container, $requestedName)
3435
{
36+
if ($requestedName === self::RESERVED_CONFIG_SERVICE_NAME) {
37+
return false;
38+
}
39+
3540
$config = $this->getConfig($container);
3641
if (empty($config)) {
3742
return false;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LaminasTest\Cache\Service;
6+
7+
use Generator;
8+
use InvalidArgumentException;
9+
use Laminas\Cache\ConfigProvider;
10+
use Laminas\ServiceManager\ServiceManager;
11+
use PHPUnit\Framework\TestCase;
12+
use Psr\Container\ContainerInterface;
13+
14+
use function array_keys;
15+
use function array_merge;
16+
use function array_unique;
17+
use function is_array;
18+
use function is_string;
19+
20+
final class ConfigProviderIntegrationTest extends TestCase
21+
{
22+
private ContainerInterface $container;
23+
24+
protected function setUp(): void
25+
{
26+
parent::setUp();
27+
$this->container = $this->createContainer();
28+
}
29+
30+
private function createContainer(): ContainerInterface
31+
{
32+
return new ServiceManager((new ConfigProvider())->getDependencyConfig());
33+
}
34+
35+
/**
36+
* @dataProvider servicesProvidedByConfigProvider
37+
*/
38+
public function testContainerCanProvideRegisteredServices(string $serviceName): void
39+
{
40+
$instance = $this->container->get($serviceName);
41+
self::assertIsObject($instance);
42+
}
43+
44+
/**
45+
* @return Generator<string, array{string}>
46+
*/
47+
public function servicesProvidedByConfigProvider(): Generator
48+
{
49+
$provider = new ConfigProvider();
50+
$dependencies = $provider->getDependencyConfig();
51+
52+
$factories = $dependencies['factories'] ?? [];
53+
self::assertArrayIsMappedWithStrings($factories);
54+
$invokables = $dependencies['invokables'] ?? [];
55+
self::assertArrayIsMappedWithStrings($invokables);
56+
$services = $dependencies['services'] ?? [];
57+
self::assertArrayIsMappedWithStrings($services);
58+
$aliases = $dependencies['aliases'] ?? [];
59+
self::assertArrayIsMappedWithStrings($aliases);
60+
61+
$serviceNames = array_unique(
62+
array_merge(
63+
array_keys($factories),
64+
array_keys($invokables),
65+
array_keys($services),
66+
array_keys($aliases),
67+
),
68+
);
69+
70+
foreach ($serviceNames as $serviceName) {
71+
yield $serviceName => [$serviceName];
72+
}
73+
}
74+
75+
/**
76+
* @psalm-assert array<string,mixed> $array
77+
*/
78+
private static function assertArrayIsMappedWithStrings(mixed $array): void
79+
{
80+
if (! is_array($array)) {
81+
throw new InvalidArgumentException('Expecting value to be an array.');
82+
}
83+
84+
foreach (array_keys($array) as $value) {
85+
if (is_string($value)) {
86+
continue;
87+
}
88+
89+
throw new InvalidArgumentException('Expecting all values to are mapped with a string.');
90+
}
91+
}
92+
}

test/Service/StorageCacheAbstractServiceFactoryTest.php

+8
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ public function testWillPassInvalidArgumentExceptionFromConfigurationValidityAss
9494
($this->factory)($this->container, 'Foo');
9595
}
9696

97+
public function testNeverCallsContainerWhenConfigServiceIsCheckedForCreation(): void
98+
{
99+
$container = $this->createMock(ContainerInterface::class);
100+
$container->expects(self::never())->method(self::anything());
101+
102+
self::assertFalse($this->factory->canCreate($container, 'config'));
103+
}
104+
97105
public function testInvalidCacheServiceNameWillBeIgnored(): void
98106
{
99107
self::assertFalse(

0 commit comments

Comments
 (0)