Skip to content

Commit

Permalink
Merge d6b369e into 00dee13
Browse files Browse the repository at this point in the history
  • Loading branch information
mnapoli committed May 24, 2017
2 parents 00dee13 + d6b369e commit 733421c
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 128 deletions.
5 changes: 5 additions & 0 deletions .travis.yml
Expand Up @@ -11,6 +11,10 @@ php:
- 7.1
- nightly

cache:
directories:
- $HOME/.composer/cache

matrix:
fast_finish: true
allow_failures:
Expand All @@ -20,6 +24,7 @@ matrix:
env: dependencies=lowest

before_script:
- phpenv config-add travis.php.ini
- if [[ $(phpenv version-name) == '7.1' ]]; then composer require satooshi/php-coveralls:dev-master -n ; fi
- if [[ $(phpenv version-name) != '7.1' ]]; then composer install -n ; fi
- if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --prefer-stable -n; fi;
Expand Down
5 changes: 4 additions & 1 deletion src/DI/ContainerBuilder.php
Expand Up @@ -133,7 +133,10 @@ public function build()
$chain = new SourceChain($sources);

if ($this->cache) {
$source = new CachedDefinitionSource($chain, $this->cache);
if (!CachedDefinitionSource::isSupported()) {
throw new \Exception('APCu is not enabled, PHP-DI cannot use it as a cache');
}
$source = new CachedDefinitionSource($chain);
$chain->setRootDefinitionSource($source);
} else {
$source = $chain;
Expand Down
75 changes: 21 additions & 54 deletions src/DI/Definition/Source/CachedDefinitionSource.php
Expand Up @@ -4,7 +4,6 @@

use DI\Definition\CacheableDefinition;
use DI\Definition\Definition;
use Psr\SimpleCache\CacheInterface;

/**
* Caches another definition source.
Expand All @@ -14,85 +13,53 @@
class CachedDefinitionSource implements DefinitionSource
{
/**
* Prefix for cache key, to avoid conflicts with other systems using the same cache.
* @var string
*/
const CACHE_PREFIX = 'DI.Definition.';
const CACHE_KEY = 'php-di.definitions';

/**
* @var DefinitionSource
*/
private $source;
private $cachedSource;

/**
* @var CacheInterface
* Definitions loaded from the cache (or null if not loaded yet).
*
* @var Definition[]|null
*/
private $cache;
private $cachedDefinitions;

public function __construct(DefinitionSource $source, CacheInterface $cache)
public function __construct(DefinitionSource $cachedSource)
{
$this->source = $source;
$this->cache = $cache;
$this->cachedSource = $cachedSource;
}

public function getDefinition(string $name)
{
if ($this->cachedDefinitions === null) {
$this->cachedDefinitions = apcu_fetch(self::CACHE_KEY) ?: [];
}

// Look in cache
$definition = $this->fetchFromCache($name);
$definition = $this->cachedDefinitions[$name] ?? false;

if ($definition === false) {
$definition = $this->source->getDefinition($name);
$definition = $this->cachedSource->getDefinition($name);

// Save to cache
// Update the cache
if ($definition === null || ($definition instanceof CacheableDefinition)) {
$this->saveToCache($name, $definition);
$this->cachedDefinitions[$name] = $definition;
apcu_store(self::CACHE_KEY, $this->cachedDefinitions);
}
}

return $definition;
}

public function getCache() : CacheInterface
{
return $this->cache;
}

/**
* Fetches a definition from the cache.
*
* @param string $name Entry name
* @return Definition|null|bool The cached definition, null or false if the value is not already cached
*/
private function fetchFromCache(string $name)
{
$data = $this->cache->get($this->getCacheKey($name), false);

if ($data !== false) {
return $data;
}

return false;
}

/**
* Saves a definition to the cache.
*
* @param string $name Entry name
*/
private function saveToCache(string $name, Definition $definition = null)
{
$this->cache->set($this->getCacheKey($name), $definition);
}

/**
* Get normalized cache key.
*
* @param string $name
*
* @return string
*/
private function getCacheKey(string $name) : string
public static function isSupported() : bool
{
return self::CACHE_PREFIX . str_replace(['{', '}', '(', ')', '/', '\\', '@', ':'], '.', $name);
return function_exists('apcu_fetch')
&& ini_get('apc.enabled')
&& !('cli' === PHP_SAPI && !ini_get('apc.enable_cli'));
}
}
12 changes: 12 additions & 0 deletions tests/IntegrationTest/CacheTest.php
Expand Up @@ -4,6 +4,7 @@

use DI\Cache\ArrayCache;
use DI\ContainerBuilder;
use DI\Definition\Source\CachedDefinitionSource;

/**
* Test caching.
Expand All @@ -12,6 +13,17 @@
*/
class CacheTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
parent::setUp();

if (!CachedDefinitionSource::isSupported()) {
$this->markTestSkipped('APCu extension is required');
}

apcu_clear_cache();
}

/**
* @test
*/
Expand Down
17 changes: 0 additions & 17 deletions tests/UnitTest/ContainerBuilderTest.php
Expand Up @@ -35,23 +35,6 @@ public function should_configure_for_development_by_default()
$this->assertFalse($this->getObjectAttribute($container->proxyFactory, 'writeProxiesToFile'));
}

/**
* @test
*/
public function should_allow_to_configure_a_cache()
{
$cache = $this->easyMock(CacheInterface::class);

$builder = new ContainerBuilder(FakeContainer::class);
$builder->setDefinitionCache($cache);

/** @var FakeContainer $container */
$container = $builder->build();

$this->assertTrue($container->definitionSource instanceof CachedDefinitionSource);
$this->assertSame($cache, $container->definitionSource->getCache());
}

/**
* @test
*/
Expand Down
89 changes: 33 additions & 56 deletions tests/UnitTest/Definition/Source/CachedDefinitionSourceTest.php
Expand Up @@ -5,99 +5,76 @@
use DI\Definition\ObjectDefinition;
use DI\Definition\Source\CachedDefinitionSource;
use DI\Definition\Source\DefinitionArray;
use EasyMock\EasyMock;
use Psr\SimpleCache\CacheInterface;
use DI\Definition\Source\DefinitionSource;

/**
* @covers \DI\Definition\Source\CachedDefinitionSource
*/
class CachedDefinitionSourceTest extends \PHPUnit_Framework_TestCase
{
use EasyMock;

/**
* @test
*/
public function should_get_from_cache()
public function setUp()
{
/** @var CacheInterface $cache */
$cache = $this->easySpy(CacheInterface::class, [
'get' => 'foo',
]);
parent::setUp();

$source = new CachedDefinitionSource(new DefinitionArray(), $cache);
if (!CachedDefinitionSource::isSupported()) {
$this->markTestSkipped('APCu extension is required');
}

$this->assertEquals('foo', $source->getDefinition('foo'));
apcu_clear_cache();
}

/**
* @dataProvider cacheKeyProvider
* @test
*/
public function testCacheKey($rawName, $cacheName)
public function should_get_from_cache()
{
$cache = $this->easySpy(CacheInterface::class);

$source = new CachedDefinitionSource(new DefinitionArray(), $cache);

$cache->expects($this->once())
->method('get')
->with(CachedDefinitionSource::CACHE_PREFIX . $cacheName)
->will($this->returnValue(false));
$source = $this->createMock(DefinitionSource::class);
$source
->expects($this->once()) // The sub-source should be called ONLY ONCE
->method('getDefinition')
->willReturn('bar');

$cache->expects($this->once())
->method('set')
->with(CachedDefinitionSource::CACHE_PREFIX . $cacheName);
$source = new CachedDefinitionSource($source);

$source->getDefinition($rawName);
}

public function cacheKeyProvider()
{
return [
['foo{bar}baz', 'foo.bar.baz'],
['foo(bar)baz', 'foo.bar.baz'],
['foo/bar\baz', 'foo.bar.baz'],
['foo@bar:baz', 'foo.bar.baz'],
];
self::assertEquals('bar', $source->getDefinition('foo'));
self::assertEquals('bar', $source->getDefinition('foo'));
}

/**
* @test
*/
public function should_save_to_cache_and_return()
{
$cache = $this->easySpy(CacheInterface::class, [
'get' => false,
]);

$cachedSource = new DefinitionArray([
'foo' => \DI\create(),
]);

$source = new CachedDefinitionSource($cachedSource, $cache);
$source = new CachedDefinitionSource($cachedSource);

$expectedDefinition = new ObjectDefinition('foo');
$cache->expects($this->once())
->method('set')
->with($this->isType('string'), $expectedDefinition);
// Sanity check
self::assertSavedInCache('foo', null);

$this->assertEquals($expectedDefinition, $source->getDefinition('foo'));
// Return the definition
self::assertEquals(new ObjectDefinition('foo'), $source->getDefinition('foo'));

// The definition is saved in the cache
self::assertSavedInCache('foo', new ObjectDefinition('foo'));
}

/**
* @test
*/
public function should_save_null_to_cache_and_return_null()
{
$cache = $this->easySpy(CacheInterface::class, [
'get' => false,
]);
$source = new CachedDefinitionSource(new DefinitionArray());

$source = new CachedDefinitionSource(new DefinitionArray(), $cache);
self::assertNull($source->getDefinition('foo'));
self::assertSavedInCache('foo', null);
}

$cache->expects($this->once())
->method('set')
->with($this->isType('string'), null);
$this->assertNull($source->getDefinition('foo'));
private static function assertSavedInCache(string $definitionName, $expectedValue)
{
$definitions = apcu_fetch(CachedDefinitionSource::CACHE_KEY);
self::assertEquals($expectedValue, $definitions[$definitionName]);
}
}
2 changes: 2 additions & 0 deletions travis.php.ini
@@ -0,0 +1,2 @@
# Enable APC on the CLI to run all tests
apc.enable_cli=1

0 comments on commit 733421c

Please sign in to comment.