Skip to content

Commit

Permalink
🔀 Merge pull request #62 from carbontwelve/issue15-invalidate-cache-c…
Browse files Browse the repository at this point in the history
…ontrol

Closes #15
  • Loading branch information
carbontwelve committed Jan 10, 2017
2 parents 9d96019 + 9929411 commit 3415691
Show file tree
Hide file tree
Showing 8 changed files with 365 additions and 13 deletions.
32 changes: 22 additions & 10 deletions src/Entities/Cache.php
Expand Up @@ -5,52 +5,64 @@
class Cache
{
/**
* @var array
* @var CacheStore
*/
private $items = [];
private $store;

/**
* @var string
*/
private $path;

/**
* @var string
*/
private $hash;

/**
* Cache constructor.
*
* @param string $path
* @param string $hash
*/
public function __construct($path)
public function __construct($path, $hash)
{
clearstatcache();
$this->path = $path;
$this->hash = $hash;
$this->store = new CacheStore($this->hash);
}

public function load()
{
if (file_exists($this->path)) {
$this->items = unserialize(file_get_contents($this->path));
$this->store = unserialize(file_get_contents($this->path));
$this->store->validate($this->hash);
}
}

public function save()
{
file_put_contents($this->path, serialize($this->items));
file_put_contents($this->path, serialize($this->store));
}

public function setItem($key, $value)
{
$this->items[$key] = $value;
$this->store->setItem($key, $value);
}

public function getItem($key)
{
if (isset($this->items[$key])) {
return $this->items[$key];
}
return $this->store->getItem($key);
}

public function count()
{
return $this->store->count();
}

public function reset()
{
$this->items = [];
$this->store->reset();
}
}
46 changes: 46 additions & 0 deletions src/Entities/CacheStore.php
@@ -0,0 +1,46 @@
<?php

namespace Tapestry\Entities;

class CacheStore
{
private $items = [];

private $hash;

public function __construct($hash)
{
$this->hash = $hash;
}

public function validate($hash)
{
if ($hash !== $this->hash) {
$this->reset();
}
}

public function setItem($key, $value)
{
$this->items[$key] = $value;
}

public function getItem($key)
{
if (isset($this->items[$key])) {
return $this->items[$key];
}

return null;
}

public function count()
{
return count($this->items);
}

public function reset()
{
$this->items = [];
}
}
48 changes: 45 additions & 3 deletions src/Modules/Content/ReadCache.php
Expand Up @@ -3,26 +3,68 @@
namespace Tapestry\Modules\Content;

use Tapestry\Step;
use Tapestry\Tapestry;
use Tapestry\Entities\Cache;
use Tapestry\Entities\Project;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\Console\Output\OutputInterface;

class ReadCache implements Step
{
/**
* Process the Project at current.
* @var Finder
*/
private $finder;

/**
* ReadCache constructor.
* @param Finder $finder
*/
public function __construct(Finder $finder)
{
$this->finder = $finder;
}

/**
* Invoke a new instance of the Cache system, load it and then inject it into the Project container.
*
* @param Project $project
* @param Project $project
* @param OutputInterface $output
*
* @return bool
*/
public function __invoke(Project $project, OutputInterface $output)
{
$cache = new Cache($project->currentWorkingDirectory.DIRECTORY_SEPARATOR.'.'.$project->environment.'_cache');
$cache = new Cache($project->currentWorkingDirectory.DIRECTORY_SEPARATOR.'.'.$project->environment.'_cache',
$this->createInvalidationHash($project));
$cache->load();
$project->set('cache', $cache);

return true;
}

/**
* Find files non recursively within the src folder and create a hash of their content salted with the applications
* version number. This ensures that the cache is invalidated upon either the base directory changing (including
* config.php and kernel.php, or files such as Gulp, Grunt config;) as well as if the user updates their version of
* the application.
*
* @param Project $project
* @return string
*/
private function createInvalidationHash(Project $project)
{
$files = $this->finder->files()->in($project->currentWorkingDirectory);
$hash = [];

/** @var SplFileInfo $file */
foreach ($files as $file) {
array_push($hash, sha1_file($file->getPathname()));
}

array_push($hash, sha1(Tapestry::VERSION));

return sha1(implode('.', $hash));
}
}
182 changes: 182 additions & 0 deletions tests/CacheTest.php
@@ -0,0 +1,182 @@
<?php

namespace Tapestry\Tests;

use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Finder\Finder;
use Tapestry\Entities\Cache;
use Tapestry\Entities\CacheStore;
use Tapestry\Entities\Project;
use Tapestry\Modules\Content\ReadCache;

class CacheTest extends CommandTestBase
{
public function testCacheStoreValidateMethod()
{
$hashA = sha1('Hello World');
$hashB = sha1('World Hello');

$store = new CacheStore($hashA);
$store->setItem('A', 'B');
$store->setItem('B', 'C');
$this->assertEquals(2, $store->count());

$store->validate($hashA);
$this->assertEquals(2, $store->count());

$store = new CacheStore($hashA);
$store->setItem('A', 'B');
$store->setItem('B', 'C');
$this->assertEquals(2, $store->count());

$store->validate($hashB);
$this->assertEquals(0, $store->count());
}

public function testCacheStoreSetGet()
{
$hashA = sha1('Hello World');

$store = new CacheStore($hashA, '1.0.0');
$store->setItem('A', 'B');
$store->setItem('B', 'C');
$store->setItem('D', true);
$store->setItem('E', false);
$store->setItem('F', null);

$this->assertEquals('B', $store->getItem('A'));
$this->assertEquals('C', $store->getItem('B'));
$this->assertEquals(true, $store->getItem('D'));
$this->assertEquals(false, $store->getItem('E'));
$this->assertEquals(null, $store->getItem('F'));
$this->assertEquals(null, $store->getItem('X'));
$this->assertEquals(5, $store->count());
}

public function testCacheStoreReset()
{
$hashA = sha1('Hello World');

$store = new CacheStore($hashA, '1.0.0');
$store->setItem('A', 'B');
$store->setItem('B', 'C');

$this->assertEquals(2, $store->count());
$store->reset();
$this->assertEquals(0, $store->count());
}

public function testCacheInitialLoad()
{
$hashA = sha1('Hello World');
$cache = new Cache(__DIR__ . '/_tmp/cache.bin', $hashA);

$cache->load();
$this->assertEquals(0, $cache->count());
}

public function testCacheSaveAndLoad()
{
$hashA = sha1('Hello World');
$cache = new Cache(__DIR__ . '/_tmp/cache.bin', $hashA);

$cache->setItem('A', 'B');
$cache->setItem('B', 'C');
$cache->setItem('X', false);
$cache->setItem('Y', null);

$this->assertEquals(4, $cache->count());

$cache->save();

$cache = new Cache(__DIR__ . '/_tmp/cache.bin', $hashA);
$this->assertEquals(0, $cache->count());
$cache->load();
$this->assertEquals(4, $cache->count());

$this->assertEquals('B', $cache->getItem('A'));
$this->assertEquals('C', $cache->getItem('B'));
$this->assertEquals(false, $cache->getItem('X'));
$this->assertEquals(null, $cache->getItem('Y'));
$this->assertEquals(null, $cache->getItem('Z'));
}

public function testCacheReset()
{
$hashA = sha1('Hello World');
$cache = new Cache(__DIR__ . '/_tmp/cache.bin', $hashA);
$cache->setItem('A', 'B');
$this->assertEquals(1, $cache->count());
$cache->reset();
$this->assertEquals(0, $cache->count());
}

public function testCacheInvalidationByHash()
{
$hashA = sha1('Hello World');
$hashB = sha1('World Hello');
$cache = new Cache(__DIR__ . '/_tmp/cache.bin', $hashA);
$cache->setItem('A', 'B');
$cache->setItem('B', 'C');

$this->assertEquals(2, $cache->count());

$cache->save();

$cache = new Cache(__DIR__ . '/_tmp/cache.bin', $hashB);
$cache->load();
$this->assertEquals(0, $cache->count());
}

public function testReadCacheModule()
{
$this->copyDirectory('/assets/build_test_21/src', '/_tmp');

$module = new ReadCache(new Finder());
$project = new Project(__DIR__ . '/_tmp', 'test');

$this->assertEquals(false, $project->has('cache'));

$result = $module->__invoke($project, new NullOutput());

$this->assertEquals(true, $project->has('cache'));
$this->assertEquals(true, $result);
$this->assertInstanceOf(Cache::class, $project->get('cache'));

$project->get('cache')->setItem('A', 'B');
$project->get('cache')->setItem('B', 'C');
$this->assertEquals(2, $project->get('cache')->count());
$project->get('cache')->save();

// Reset $module && $project variables to test loading from save

unset($module, $project);
$module = new ReadCache(new Finder());
$project = new Project(__DIR__ . '/_tmp', 'test');
$module->__invoke($project, new NullOutput());
$this->assertEquals(2, $project->get('cache')->count());
$this->assertEquals('B', $project->get('cache')->getItem('A'));
$this->assertEquals('C', $project->get('cache')->getItem('B'));


// Reset $module && $project variables and modify the src directory to test cache invalidation
unset($module, $project);

self::$fileSystem->copy(
__DIR__.DIRECTORY_SEPARATOR.'/assets/build_test_21/src_replace/config.php',
__DIR__.DIRECTORY_SEPARATOR.'/_tmp/config.php',
true
);

self::$fileSystem->copy(
__DIR__.DIRECTORY_SEPARATOR.'/assets/build_test_21/src_replace/kernel.php',
__DIR__.DIRECTORY_SEPARATOR.'/_tmp/kernel.php',
true
);

$module = new ReadCache(new Finder());
$project = new Project(__DIR__ . '/_tmp', 'test');
$module->__invoke($project, new NullOutput());
$this->assertEquals(0, $project->get('cache')->count());
}
}
6 changes: 6 additions & 0 deletions tests/assets/build_test_21/src/config.php
@@ -0,0 +1,6 @@
<?php

return [
'debug' => false,
'kernel' => \SiteTwentyOne\TestKernel::class,
];

0 comments on commit 3415691

Please sign in to comment.