From f6d64e18b06193a0914b6f28d809f3fa30039e2b Mon Sep 17 00:00:00 2001 From: Kyle Date: Wed, 29 Nov 2017 14:24:25 +0100 Subject: [PATCH] Cache imports handle (#41) * Handle imports last modified times for cache expiration * Enable include cache optimization only for Phug engine * Refactor compilers using traits * Send coverage in only one build * Isolate import test in a dedicated method --- .travis.yml | 36 ++++---- src/PugBladeCompiler.php | 79 ++--------------- src/PugCompiler.php | 69 ++------------- src/PugHandlerTrait.php | 152 +++++++++++++++++++++++++++++++++ src/ServiceProvider.php | 2 + tests/PugBladeCompilerTest.php | 75 ++++++++++++++-- tests/PugCompilerTest.php | 66 ++++++++++++-- tests/include.pug | 1 + 8 files changed, 311 insertions(+), 169 deletions(-) create mode 100644 src/PugHandlerTrait.php create mode 100644 tests/include.pug diff --git a/.travis.yml b/.travis.yml index 4d7c7cb..3e6a09f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ matrix: - php: 5.5 env: - LARAVEL_VERSION='4.2.*' - - PUG_VERSION='^3.0.0@beta' + - PUG_VERSION='^3.0.0' - php: 5.5 env: - LARAVEL_VERSION='5.2.*' @@ -24,7 +24,7 @@ matrix: - php: 5.5 env: - LARAVEL_VERSION='5.2.*' - - PUG_VERSION='^3.0.0@beta' + - PUG_VERSION='^3.0.0' - php: 5.6 env: - LARAVEL_VERSION='4.2.*' @@ -32,7 +32,7 @@ matrix: - php: 5.6 env: - LARAVEL_VERSION='4.2.*' - - PUG_VERSION='^3.0.0@beta' + - PUG_VERSION='^3.0.0' - php: 5.6 env: - LARAVEL_VERSION='5.4.*' @@ -40,7 +40,7 @@ matrix: - php: 5.6 env: - LARAVEL_VERSION='5.4.*' - - PUG_VERSION='^3.0.0@beta' + - PUG_VERSION='^3.0.0' - php: 7.0 env: - LARAVEL_VERSION='4.2.*' @@ -48,7 +48,7 @@ matrix: - php: 7.0 env: - LARAVEL_VERSION='4.2.*' - - PUG_VERSION='^3.0.0@beta' + - PUG_VERSION='^3.0.0' - php: 7.0 env: - LARAVEL_VERSION='5.4.*' @@ -56,7 +56,7 @@ matrix: - php: 7.0 env: - LARAVEL_VERSION='5.4.*' - - PUG_VERSION='^3.0.0@beta' + - PUG_VERSION='^3.0.0' - php: 7.0 env: - LARAVEL_VERSION='5.5.*' @@ -64,7 +64,7 @@ matrix: - php: 7.0 env: - LARAVEL_VERSION='5.5.*' - - PUG_VERSION='^3.0.0@beta' + - PUG_VERSION='^3.0.0' - php: 7.1 env: - LARAVEL_VERSION='4.2.*' @@ -72,7 +72,7 @@ matrix: - php: 7.1 env: - LARAVEL_VERSION='4.2.*' - - PUG_VERSION='^3.0.0@beta' + - PUG_VERSION='^3.0.0' - php: 7.1 env: - LARAVEL_VERSION='5.4.*' @@ -80,7 +80,7 @@ matrix: - php: 7.1 env: - LARAVEL_VERSION='5.4.*' - - PUG_VERSION='^3.0.0@beta' + - PUG_VERSION='^3.0.0' - php: 7.1 env: - LARAVEL_VERSION='5.5.*' @@ -88,7 +88,7 @@ matrix: - php: 7.1 env: - LARAVEL_VERSION='5.5.*' - - PUG_VERSION='^3.0.0@beta' + - PUG_VERSION='^3.0.0' - php: 7.2.0RC5 env: - LARAVEL_VERSION='4.2.*' @@ -96,7 +96,7 @@ matrix: - php: 7.2.0RC5 env: - LARAVEL_VERSION='4.2.*' - - PUG_VERSION='^3.0.0@beta' + - PUG_VERSION='^3.0.0' - php: 7.2.0RC5 env: - LARAVEL_VERSION='5.4.*' @@ -104,7 +104,7 @@ matrix: - php: 7.2.0RC5 env: - LARAVEL_VERSION='5.4.*' - - PUG_VERSION='^3.0.0@beta' + - PUG_VERSION='^3.0.0' - php: 7.2.0RC5 env: - LARAVEL_VERSION='5.5.*' @@ -112,7 +112,7 @@ matrix: - php: 7.2.0RC5 env: - LARAVEL_VERSION='5.5.*' - - PUG_VERSION='^3.0.0@beta' + - PUG_VERSION='^3.0.0' - php: hhvm env: - LARAVEL_VERSION='4.2.*' @@ -122,7 +122,7 @@ matrix: - php: hhvm env: - LARAVEL_VERSION='4.2.*' - - PUG_VERSION='^3.0.0@beta' + - PUG_VERSION='^3.0.0' dist: trusty sudo: required - php: hhvm @@ -134,23 +134,23 @@ matrix: - php: hhvm env: - LARAVEL_VERSION='5.4.*' - - PUG_VERSION='^3.0.0@beta' + - PUG_VERSION='^3.0.0' dist: trusty sudo: required install: - travis_retry composer self-update - - if [ "LARAVEL_VERSION" != "" ]; then travis_retry php tests/setDependenciesVersions.php $LARAVEL_VERSION $PUG_VERSION; fi; + - travis_retry php tests/setDependenciesVersions.php $LARAVEL_VERSION $PUG_VERSION - travis_retry composer update --no-interaction --prefer-stable script: - vendor/bin/phpunit --verbose --coverage-text --coverage-clover=coverage.xml after_script: - - vendor/bin/test-reporter --coverage-report coverage.xml + - if [[ $LARAVEL_VERSION == "5.4.*" && $PUG_VERSION == "^3.0.0" && ${TRAVIS_PHP_VERSION:0:3} == "5.6" ]]; then vendor/bin/test-reporter --coverage-report coverage.xml; fi; after_success: - - bash <(curl -s https://codecov.io/bash) + - if [[ $LARAVEL_VERSION == "5.4.*" && $PUG_VERSION == "^3.0.0" && ${TRAVIS_PHP_VERSION:0:3} == "5.6" ]]; then bash <(curl -s https://codecov.io/bash); fi; addons: code_climate: diff --git a/src/PugBladeCompiler.php b/src/PugBladeCompiler.php index 9be4a19..2cbec80 100644 --- a/src/PugBladeCompiler.php +++ b/src/PugBladeCompiler.php @@ -2,76 +2,25 @@ namespace Bkwld\LaravelPug; -// Dependencies use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Facades\Blade; use Illuminate\View\Compilers\BladeCompiler; use Illuminate\View\Compilers\CompilerInterface; -use InvalidArgumentException; use Pug\Pug; class PugBladeCompiler extends BladeCompiler implements CompilerInterface { - /** - * The MtHaml instance. - * - * @var Pug - */ - protected $pug; + use PugHandlerTrait; /** * Create a new compiler instance. * * @param Pug $pug * @param Filesystem $files - * @param string $cachePath */ public function __construct(Pug $pug, Filesystem $files) { - $this->pug = $pug; - $cachePath = $this->getOption('cache'); - if (!is_string($cachePath)) { - $cachePath = $this->getOption('defaultCache'); - } - parent::__construct($files, $cachePath); - } - - /** - * Get an option from pug engine or default value. - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function getOption($name, $default = null) - { - if (method_exists($this->pug, 'hasOption') && !$this->pug->hasOption($name)) { - return $default; - } - - return $this->pug->getOption($name); - } - - /** - * @param string $cachePath - */ - public function setCachePath($cachePath) - { - $this->cachePath = $cachePath; - $this->pug->setOption('cache', $cachePath); - } - - /** - * Determine if the view at the given path is expired. - * - * @param string $path - * - * @return bool - */ - public function isExpired($path) - { - return !$this->getOption('cache') || parent::isExpired($path); + parent::__construct($files, $this->getCachePath($pug)); } /** @@ -79,6 +28,8 @@ public function isExpired($path) * * @param string $path * + * @throws \InvalidArgumentException + * * @return void */ public function compile($path = null) @@ -90,27 +41,7 @@ public function compile($path = null) $this->directive($name, $directive); } } - if ($path && method_exists($this, 'setPath')) { - $this->setPath($path); - } - if (!$path && method_exists($this, 'getPath')) { - $path = $this->getPath(); - } - if (!$path) { - throw new InvalidArgumentException('Missing path argument.'); - } - $this->footer = array(); - - if ($this->cachePath) { - // First compile the Pug syntax - $contents = $this->pug->compile($this->files->get($path), $path); - - // Then the Blade syntax - $contents = $this->compileString($contents); - - // Save - $this->files->put($this->getCompiledPath($path), $contents); - } + $this->compileWith($path, array($this, 'compileString')); } } diff --git a/src/PugCompiler.php b/src/PugCompiler.php index ad1adf5..9202e30 100644 --- a/src/PugCompiler.php +++ b/src/PugCompiler.php @@ -2,73 +2,24 @@ namespace Bkwld\LaravelPug; -// Dependencies use Illuminate\Filesystem\Filesystem; use Illuminate\View\Compilers\Compiler; use Illuminate\View\Compilers\CompilerInterface; -use InvalidArgumentException; use Pug\Pug; class PugCompiler extends Compiler implements CompilerInterface { - /** - * @var Pug - */ - protected $pug; + use PugHandlerTrait; /** * Create a new compiler instance. * * @param Pug $pug * @param Filesystem $files - * @param string $cachePath */ public function __construct(Pug $pug, Filesystem $files) { - $this->pug = $pug; - $cachePath = $this->getOption('cache'); - if (!is_string($cachePath)) { - $cachePath = $this->getOption('defaultCache'); - } - parent::__construct($files, $cachePath); - } - - /** - * Get an option from pug engine or default value. - * - * @param string $name - * @param null $default - * - * @return mixed|null - */ - public function getOption($name, $default = null) - { - if (method_exists($this->pug, 'hasOption') && !$this->pug->hasOption($name)) { - return $default; - } - - return $this->pug->getOption($name); - } - - /** - * @param string $cachePath - */ - public function setCachePath($cachePath) - { - $this->cachePath = $cachePath; - $this->pug->setOption('cache', $cachePath); - } - - /** - * Determine if the view at the given path is expired. - * - * @param string $path - * - * @return bool - */ - public function isExpired($path) - { - return !$this->getOption('cache') || parent::isExpired($path); + parent::__construct($files, $this->getCachePath($pug)); } /** @@ -76,22 +27,12 @@ public function isExpired($path) * * @param string $path * + * @throws \InvalidArgumentException + * * @return void */ public function compile($path) { - if ($path && method_exists($this, 'setPath')) { - $this->setPath($path); - } - if (!$path && method_exists($this, 'getPath')) { - $path = $this->getPath(); - } - if (!$path) { - throw new InvalidArgumentException('Missing path argument.'); - } - if ($this->cachePath) { - $contents = $this->pug->compile($this->files->get($path), $path); - $this->files->put($this->getCompiledPath($path), $contents); - } + $this->compileWith($path); } } diff --git a/src/PugHandlerTrait.php b/src/PugHandlerTrait.php new file mode 100644 index 0000000..6f5cb9b --- /dev/null +++ b/src/PugHandlerTrait.php @@ -0,0 +1,152 @@ +pug = $pug; + $cachePath = $this->getOption('cache'); + + return is_string($cachePath) ? $cachePath : $this->getOption('defaultCache'); + } + + /** + * Get an option from pug engine or default value. + * + * @param string $name + * @param null $default + * + * @return mixed|null + */ + public function getOption($name, $default = null) + { + if (method_exists($this->pug, 'hasOption') && !$this->pug->hasOption($name)) { + return $default; + } + + return $this->pug->getOption($name); + } + + /** + * @param string $cachePath + */ + public function setCachePath($cachePath) + { + $this->cachePath = $cachePath; + $this->pug->setOption('cache', $cachePath); + } + + /** + * Returns true if the path has an expired imports linked. + * + * @param $path + * + * @return bool + */ + private function hasExpiredImport($path) + { + $compiled = $this->getCompiledPath($path); + $importsMap = $compiled . '.imports.serialize.txt'; + $files = $this->files; + + if (!$files->exists($importsMap)) { + return true; + } + + $importPaths = unserialize($files->get($importsMap)); + $time = $files->lastModified($compiled); + foreach ($importPaths as $importPath) { + if (!$files->exists($importPath) || $files->lastModified($importPath) >= $time) { + return true; + } + } + + return false; + } + + /** + * Determine if the view at the given path is expired. + * + * @param string $path + * + * @return bool + */ + public function isExpired($path) + { + if (!$this->getOption('cache') || parent::isExpired($path)) { + return true; + } + + return $this->pug instanceof \Phug\Renderer && $this->hasExpiredImport($path); + } + + /** + * Return path and set it or get it from the instance. + * + * @param string $path + * + * @throws \InvalidArgumentException + * + * @return string + */ + public function extractPath($path) + { + if ($path && method_exists($this, 'setPath')) { + $this->setPath($path); + } + if (!$path && method_exists($this, 'getPath')) { + $path = $this->getPath(); + } + if (!$path) { + throw new InvalidArgumentException('Missing path argument.'); + } + + return $path; + } + + /** + * Compile the view at the given path. + * + * @param string $path + * @param callable|null $callback + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function compileWith($path, callable $callback = null) + { + $path = $this->extractPath($path); + if ($this->cachePath) { + $compiled = $this->getCompiledPath($path); + $contents = $this->pug->compile($this->files->get($path), $path); + if ($callback) { + $contents = call_user_func($callback, $contents); + } + if ($this->pug instanceof \Phug\Renderer) { + $this->files->put( + $compiled . '.imports.serialize.txt', + serialize($this->pug->getCompiler()->getCurrentImportPaths()) + ); + } + $this->files->put($compiled, $contents); + } + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index d1e8818..436cfdb 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -17,11 +17,13 @@ protected function setDefaultOption(Pug $pug, $name, $value) return; } + // @codeCoverageIgnoreStart try { $pug->getOption($name); } catch (\InvalidArgumentException $exception) { $pug->setCustomOption($name, call_user_func($value)); } + // @codeCoverageIgnoreEnd } /** diff --git a/tests/PugBladeCompilerTest.php b/tests/PugBladeCompilerTest.php index f8314d9..4caf974 100644 --- a/tests/PugBladeCompilerTest.php +++ b/tests/PugBladeCompilerTest.php @@ -28,9 +28,11 @@ public function setPath($path) class PugBladeCompilerTest extends TestCase { /** - * @covers ::getOption - * @covers ::isExpired * @covers ::__construct + * @covers \Bkwld\LaravelPug\PugHandlerTrait::getCachePath + * @covers \Bkwld\LaravelPug\PugHandlerTrait::hasExpiredImport + * @covers \Bkwld\LaravelPug\PugHandlerTrait::isExpired + * @covers \Bkwld\LaravelPug\PugHandlerTrait::getOption */ public function testIsExpired() { @@ -51,6 +53,7 @@ public function testIsExpired() self::assertTrue($compiler->isExpired($path)); $compiler->compile($path); + touch(__DIR__ . '/example.pug', time() - 3600); clearstatcache(); self::assertFalse($compiler->isExpired($path)); @@ -64,20 +67,72 @@ public function testIsExpired() unlink($compiledPath); clearstatcache(); } + } + /** + * @covers ::__construct + * @covers \Bkwld\LaravelPug\PugHandlerTrait::getCachePath + * @covers \Bkwld\LaravelPug\PugHandlerTrait::hasExpiredImport + * @covers \Bkwld\LaravelPug\PugHandlerTrait::isExpired + * @covers \Bkwld\LaravelPug\PugHandlerTrait::getOption + */ + public function testIncludeIsExpired() + { $cache = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'foo'; $pug = new Pug([ 'cache' => $cache, 'defaultCache' => sys_get_temp_dir(), ]); - $compiler = new PugBladeCompiler($pug, new Filesystem()); + + if (!($pug instanceof \Phug\Renderer)) { + self::markTestSkipped('Include cache expiration only available since pug-php 3.'); + } + + $files = new Filesystem(); + if (!$files->exists($cache)) { + $files->makeDirectory($cache); + } + $path = realpath(__DIR__ . '/example.pug'); + $compiler = new PugBladeCompiler($pug, $files); $compiledPath = $compiler->getCompiledPath($path); self::assertSame($cache, dirname($compiledPath)); + + $pug->setOption('cache', true); + $path = realpath(__DIR__ . '/include.pug'); + $compiledPath = $compiler->getCompiledPath($path); + + touch(__DIR__ . '/include.pug', time() - 3600); + touch(__DIR__ . '/example.pug', time() - 3600); + $compiler->compile($path); + clearstatcache(); + + self::assertFileExists($compiledPath); + + self::assertFalse($compiler->isExpired($path)); + + touch(__DIR__ . '/example.pug', time() + 3600); + clearstatcache(); + + self::assertTrue($compiler->isExpired($path)); + + touch(__DIR__ . '/example.pug', time() - 3600); + unlink($compiledPath . '.imports.serialize.txt'); + clearstatcache(); + + self::assertTrue($compiler->isExpired($path)); + + // Cleanup + if (file_exists($compiledPath)) { + unlink($compiledPath); + clearstatcache(); + } } /** - * @covers ::getOption + * @covers \Bkwld\LaravelPug\PugHandlerTrait::getOption + * @covers \Bkwld\LaravelPug\PugHandlerTrait::extractPath + * @covers \Bkwld\LaravelPug\PugHandlerTrait::compileWith * @covers ::compile */ public function testCompile() @@ -105,7 +160,9 @@ public function testCompile() } /** - * @covers ::getOption + * @covers \Bkwld\LaravelPug\PugHandlerTrait::getOption + * @covers \Bkwld\LaravelPug\PugHandlerTrait::extractPath + * @covers \Bkwld\LaravelPug\PugHandlerTrait::compileWith * @covers ::compile */ public function testGetAndSetPath() @@ -151,6 +208,8 @@ public function testGetAndSetPath() } /** + * @covers \Bkwld\LaravelPug\PugHandlerTrait::extractPath + * @covers \Bkwld\LaravelPug\PugHandlerTrait::compileWith * @covers ::compile */ public function testPhpDirective() @@ -193,8 +252,8 @@ public function testPhpDirective() } /** - * @covers ::getOption - * @covers ::setCachePath + * @covers \Bkwld\LaravelPug\PugHandlerTrait::getOption + * @covers \Bkwld\LaravelPug\PugHandlerTrait::setCachePath */ public function testSetCachePath() { @@ -209,7 +268,7 @@ public function testSetCachePath() } /** - * @covers ::compile + * @covers \Bkwld\LaravelPug\PugHandlerTrait::extractPath * @expectedException \InvalidArgumentException * @expectedExceptionMessage Missing path argument. */ diff --git a/tests/PugCompilerTest.php b/tests/PugCompilerTest.php index 53efb8b..4d5e58c 100644 --- a/tests/PugCompilerTest.php +++ b/tests/PugCompilerTest.php @@ -28,8 +28,10 @@ public function setPath($path) class PugCompilerTest extends TestCase { /** - * @covers ::isExpired * @covers ::__construct + * @covers \Bkwld\LaravelPug\PugHandlerTrait::getCachePath + * @covers \Bkwld\LaravelPug\PugHandlerTrait::hasExpiredImport + * @covers \Bkwld\LaravelPug\PugHandlerTrait::isExpired */ public function testIsExpired() { @@ -52,6 +54,7 @@ public function testIsExpired() self::assertTrue($compiler->isExpired($path)); $compiler->compile($path); + touch(__DIR__ . '/example.pug', time() - 3600); clearstatcache(); self::assertFalse($compiler->isExpired($path)); @@ -65,19 +68,68 @@ public function testIsExpired() unlink($compiledPath); clearstatcache(); } + } + /** + * @covers ::__construct + * @covers \Bkwld\LaravelPug\PugHandlerTrait::getCachePath + * @covers \Bkwld\LaravelPug\PugHandlerTrait::hasExpiredImport + * @covers \Bkwld\LaravelPug\PugHandlerTrait::isExpired + */ + public function testIncludeIsExpired() + { $cache = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'foo'; $pug = new Pug([ 'cache' => $cache, 'defaultCache' => sys_get_temp_dir(), ]); - $compiler = new PugCompiler($pug, new Filesystem()); + + if (!($pug instanceof \Phug\Renderer)) { + self::markTestSkipped('Include cache expiration only available since pug-php 3.'); + } + + $files = new Filesystem(); + if (!$files->exists($cache)) { + $files->makeDirectory($cache); + } + $path = realpath(__DIR__ . '/example.pug'); + $compiler = new PugCompiler($pug, $files); $compiledPath = $compiler->getCompiledPath($path); self::assertSame($cache, dirname($compiledPath)); + + $pug->setOption('cache', true); + $path = realpath(__DIR__ . '/include.pug'); + $compiledPath = $compiler->getCompiledPath($path); + + touch(__DIR__ . '/include.pug', time() - 3600); + touch(__DIR__ . '/example.pug', time() - 3600); + $compiler->compile($path); + clearstatcache(); + + self::assertFalse($compiler->isExpired($path)); + + touch(__DIR__ . '/example.pug', time() + 3600); + clearstatcache(); + + self::assertTrue($compiler->isExpired($path)); + + touch(__DIR__ . '/example.pug', time() - 3600); + unlink($compiledPath . '.imports.serialize.txt'); + clearstatcache(); + + self::assertTrue($compiler->isExpired($path)); + + // Cleanup + if (file_exists($compiledPath)) { + unlink($compiledPath); + clearstatcache(); + } } /** + * @covers \Bkwld\LaravelPug\PugHandlerTrait::extractPath + * @covers \Bkwld\LaravelPug\PugHandlerTrait::compileWith * @covers ::compile */ public function testCompile() @@ -104,6 +156,8 @@ public function testCompile() } /** + * @covers \Bkwld\LaravelPug\PugHandlerTrait::extractPath + * @covers \Bkwld\LaravelPug\PugHandlerTrait::compileWith * @covers ::compile */ public function testGetAndSetPath() @@ -149,8 +203,8 @@ public function testGetAndSetPath() } /** - * @covers ::getOption - * @covers ::setCachePath + * @covers \Bkwld\LaravelPug\PugHandlerTrait::getOption + * @covers \Bkwld\LaravelPug\PugHandlerTrait::setCachePath */ public function testSetCachePath() { @@ -165,7 +219,7 @@ public function testSetCachePath() } /** - * @covers ::compile + * @covers \Bkwld\LaravelPug\PugHandlerTrait::extractPath * @expectedException \InvalidArgumentException * @expectedExceptionMessage Missing path argument. */ @@ -179,6 +233,8 @@ public function testCompilePathException() } /** + * @covers \Bkwld\LaravelPug\PugHandlerTrait::extractPath + * @covers \Bkwld\LaravelPug\PugHandlerTrait::compileWith * @covers ::compile */ public function testRender() diff --git a/tests/include.pug b/tests/include.pug new file mode 100644 index 0000000..cca7614 --- /dev/null +++ b/tests/include.pug @@ -0,0 +1 @@ +include example.pug