diff --git a/src/ContainerBuilder.php b/src/ContainerBuilder.php index 1869b9e1e..cc8ad9038 100644 --- a/src/ContainerBuilder.php +++ b/src/ContainerBuilder.php @@ -100,6 +100,11 @@ class ContainerBuilder */ private $sourceCache = false; + /** + * @var string + */ + protected $sourceCacheNamespace; + /** * Build a container configured for the dev environment. */ @@ -155,7 +160,7 @@ public function build() throw new \Exception('APCu is not enabled, PHP-DI cannot use it as a cache'); } // Wrap the source with the cache decorator - $source = new SourceCache($source); + $source = new SourceCache($source, $this->sourceCacheNamespace); } $proxyFactory = new ProxyFactory( @@ -350,13 +355,15 @@ public function addDefinitions(...$definitions) : self * * @see http://php-di.org/doc/performances.html * + * @param string $cacheNamespace use unique namespace per container when sharing a single APC memory pool to prevent cache collisions * @return $this */ - public function enableDefinitionCache() : self + public function enableDefinitionCache(string $cacheNamespace = '') : self { $this->ensureNotLocked(); $this->sourceCache = true; + $this->sourceCacheNamespace = $cacheNamespace; return $this; } diff --git a/src/Definition/Source/SourceCache.php b/src/Definition/Source/SourceCache.php index 063b757c0..6487c2c0c 100644 --- a/src/Definition/Source/SourceCache.php +++ b/src/Definition/Source/SourceCache.php @@ -23,21 +23,27 @@ class SourceCache implements DefinitionSource, MutableDefinitionSource */ private $cachedSource; - public function __construct(DefinitionSource $cachedSource) + /** + * @var string + */ + private $cacheNamespace; + + public function __construct(DefinitionSource $cachedSource, string $cacheNamespace = '') { $this->cachedSource = $cachedSource; + $this->cacheNamespace = $cacheNamespace; } public function getDefinition(string $name) { - $definition = apcu_fetch(self::CACHE_KEY . $name); + $definition = apcu_fetch($this->getCacheKey($name)); if ($definition === false) { $definition = $this->cachedSource->getDefinition($name); // Update the cache if ($this->shouldBeCached($definition)) { - apcu_store(self::CACHE_KEY . $name, $definition); + apcu_store($this->getCacheKey($name), $definition); } } @@ -59,6 +65,11 @@ public static function isSupported() : bool && ! ('cli' === \PHP_SAPI && ! ini_get('apc.enable_cli')); } + public function getCacheKey(string $name) : string + { + return self::CACHE_KEY . $this->cacheNamespace . $name; + } + public function addDefinition(Definition $definition) { throw new \LogicException('You cannot set a definition at runtime on a container that has caching enabled. Doing so would risk caching the definition for the next execution, where it might be different. You can either put your definitions in a file, remove the cache or ->set() a raw value directly (PHP object, string, int, ...) instead of a PHP-DI definition.'); diff --git a/tests/UnitTest/ContainerBuilderTest.php b/tests/UnitTest/ContainerBuilderTest.php index 9874d4526..8e2ebf9cc 100644 --- a/tests/UnitTest/ContainerBuilderTest.php +++ b/tests/UnitTest/ContainerBuilderTest.php @@ -58,6 +58,28 @@ public function should_allow_to_configure_a_cache() $this->assertInstanceOf(SourceCache::class, $container->definitionSource); } + /** + * @test + */ + public function should_allow_to_configure_a_cache_with_a_namespace() + { + if (! SourceCache::isSupported()) { + $this->markTestSkipped('APCu extension is required'); + return; + } + + $namespace = 'staging'; + $builder = new ContainerBuilder(FakeContainer::class); + $builder->enableDefinitionCache($namespace); + + /** @var FakeContainer $container */ + $container = $builder->build(); + $source = $container->definitionSource; + + $this->assertInstanceOf(SourceCache::class, $source); + $this->assertSame($source->getCacheKey('foo'), SourceCache::CACHE_KEY . $namespace . 'foo'); + } + /** * @test */ diff --git a/tests/UnitTest/Definition/Source/SourceCacheTest.php b/tests/UnitTest/Definition/Source/SourceCacheTest.php index d619b92a8..47a39a536 100644 --- a/tests/UnitTest/Definition/Source/SourceCacheTest.php +++ b/tests/UnitTest/Definition/Source/SourceCacheTest.php @@ -90,6 +90,16 @@ public function should_only_cache_object_and_autowire_definitions() self::assertSame($definition, $source->getDefinition('foo')); } + /** + * @test + */ + public function should_use_namespaced_cache_keys() + { + $namespace = 'staging'; + $source = new SourceCache(new DefinitionArray, $namespace); + self::assertSame($source->getCacheKey('foo'), SourceCache::CACHE_KEY . $namespace . 'foo'); + } + private static function assertSavedInCache(string $definitionName, $expectedValue) { $definition = apcu_fetch(SourceCache::CACHE_KEY . $definitionName);