Skip to content

Commit

Permalink
Merge 3409571 into dbbfc13
Browse files Browse the repository at this point in the history
  • Loading branch information
xificurk committed Jul 27, 2022
2 parents dbbfc13 + 3409571 commit c586ac6
Show file tree
Hide file tree
Showing 61 changed files with 2,057 additions and 428 deletions.
1 change: 1 addition & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ updates:
- dependency-type: "development"
ignore:
- dependency-name: "nette/di"
- dependency-name: "nette/caching"
- dependency-name: "nette/schema"
- dependency-name: "nette/application"
- dependency-name: "nette/bootstrap"
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ jobs:
fail-fast: false
matrix:
php:
- 7.4
- 8.0
- 8.1
composer_args:
Expand Down Expand Up @@ -53,7 +52,7 @@ jobs:
- uses: actions/checkout@v3
- uses: shivammathur/setup-php@v2
with:
php-version: 7.4
php-version: 8.0
coverage: none
- name: Build
run: composer update --no-interaction --no-progress --prefer-dist --prefer-stable
Expand Down
1 change: 0 additions & 1 deletion .phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
</rule>

<rule ref="SlevomatCodingStandard.Files.TypeNameMatchesFileName">
<exclude-pattern>*/exceptions.php</exclude-pattern>
<properties>
<property name="rootNamespaces" type="array">
<element key="src" value="Nepada"/>
Expand Down
86 changes: 76 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Bust Cache Macro
================
Bust Cache Latte Tag
====================

[![Build Status](https://github.com/nepada/bust-cache/workflows/CI/badge.svg)](https://github.com/nepada/bust-cache/actions?query=workflow%3ACI+branch%3Amaster)
[![Coverage Status](https://coveralls.io/repos/github/nepada/bust-cache/badge.svg?branch=master)](https://coveralls.io/github/nepada/bust-cache?branch=master)
Expand All @@ -20,12 +20,27 @@ Register the extension in `config.neon`:

```yaml
extensions:
- Nepada\Bridges\BustCacheDI\BustCacheExtension(%wwwDir%, %debugMode%)
bustCache: Nepada\Bridges\BustCacheDI\BustCacheExtension(%wwwDir%, %debugMode%)
```

If you're using stand-alone Latte, install the macro manually:
Overview of available configuration options with their default values:
```yaml
bustCache:
strategy: contentHash # modificationTime in debugMode
autoRefresh: %debugMode%
manifest: true
```

If you're using stand-alone Latte, install the Latte extension manually:

```php
$latte->addMacro('bustCache', new Nepada\BustCache\BustCacheMacro($wwwDir, $debugMode));
$fileSystem = Nepada\BustCache\LocalFileSystem::forDirectory($wwwDir);
$manifestFinder = new Nepada\BustCache\Manifest\AutodetectManifestFinder($fileSystem);
$revisionFinder = new Nepada\BustCache\Manifest\DefaultRevisionFinder($fileSystem, $manifestFinder);
$cache = new Nepada\BustCache\Caching\NullCache(); // or other implementation of Cache
$strategy = new Nepada\BustCache\CacheBustingStrategies\ContentHash(); // or other strategy
$pathProcessor = new Nepada\BustCache\BustCachePathProcessor($fileSystem, $cache, $revisionFinder, $strategy);
$latte->addExtension(new Nepada\Bridges\BustCacheLatte\BustCacheLatteExtension($pathProcessor, $autoRefresh));
```


Expand All @@ -36,19 +51,70 @@ Example:

```latte
<link rel="stylesheet" href="{bustCache /css/style.css}">
<!-- usage with non-trivial basePath of application -->
<link rel="stylesheet" href="{$basePath}{bustCache /css/style.css}">
<!-- generating full absolute URL -->
<link rel="stylesheet" href="{$baseUrl}{bustCache /css/style.css}">
```

In debug mode the macro busts cache by appending timestamp of last file modification:
The resulting path depends on the (auto-)chosen cache busting strategy:

```latte
<!-- using path from revision manifest -->
<link rel="stylesheet" href="/css/style-30cc681d44.css">
<!-- using query cache busting with modificationTime strategy: timestamp of the last file modification -->
<link rel="stylesheet" href="/css/style.css?1449177985">
<!-- using query cache busting with contentHash strategy: first 10 letters of md5 hash of the file content -->
<link rel="stylesheet" href="/css/style.css?a1d0c6e83a">
```

In production mode the macro busts cache by appending first 10 letters of md5 hash of the file content:

```latte
<link rel="stylesheet" href="/css/style.css?a1d0c6e83f">
### Revision manifest support

Revision manifest is a JSON file that contains mapping between original asset path and its revision path.

Example:
```json
{
"css/style.css": "css/style-30cc681d44.css",
"js/app.js": "js/app-68130ccd44.js"
}
```


**Note:** It is not recommended (but supported) to pass variables into the macro, because they need to be resolved in run-time and thus the file is read on every request.
#### Configuration

With default configuration the path of manifest file is auto-detected by traversing up from asset directory and looking for `manifest.json` or `rev-manifest.json`. If a manifest file is found, the contained revision mapping is used instead of cache busting using query parameter.

You can completely disable the revision manifest support by setting `manifest: false` in your config.

You can also bypass the auto-detection and specify the manifest file path statically, e.g. `manifest: "assets/my-manifest.json"`


### Caching

The caching is implemented on two levels - runtime and compile-time.

#### Runtime caching

The cache stores the computed cache busted path for each input path.

DI extension automatically enables this cache, if you have `nette/caching` configured. In production mode with default settings, asset files are not checked for modification to avoid unnecessary I/O, i.e. the cache is not automatically refreshed.

#### Compile time caching

When the file path is specified as literal string, the cache busted path is computed in compile time of Latte template and the cache busted path is directly dumped into the compiled code of template.

With the default settings, this is enabled only in production mode.

#### Cache busting of files that are modified in app runtime

If you want to use cache busting on files that are expected to be modified in app runtime, you can use `dynamic` keyword to opt-out of compile time caching and force auto refresh of cache even in production mode:

```latte
<link rel="stylesheet" href="{bustCache dynamic /css/theme.css}">
```
23 changes: 13 additions & 10 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "nepada/bust-cache",
"type": "library",
"license": "BSD-3-Clause",
"description": "Cache busting macro for Latte.",
"description": "Cache busting Latte tag.",
"keywords": ["latte", "bust", "cache", "nette"],
"support": {
"email": "petr@pada.cz",
Expand All @@ -15,8 +15,8 @@
}
],
"require": {
"php": ">=7.4.0 <8.2",
"latte/latte": "^2.7@dev"
"php": ">=8.0.0 <8.2",
"latte/latte": "^3.0.1@dev"
},
"require-dev": {
"nette/tester": "2.4.2",
Expand All @@ -29,22 +29,25 @@
"php-parallel-lint/php-parallel-lint": "1.3.2",
"nepada/coding-standard": "7.4.3",
"nette/di": "^3.0.6@dev",
"nette/caching": "^3.0.2@dev",
"nette/schema": "^1.0.3@dev",
"nette/application": "^3.1.4@dev",
"nette/application": "^3.1.6@dev",
"nette/bootstrap": ">=3.0@dev",
"nette/component-model": ">=3.0.2"
},
"conflict": {
"nette/di": "<3.0.6",
"nette/caching": "<3.0.2"
},
"suggest": {
"nette/di": "for integration with Nette DI container"
"nette/di": "for integration with Nette DI container",
"nette/caching": "for caching support"
},
"autoload": {
"psr-4": {
"Nepada\\Bridges\\": "src/Bridges/",
"Nepada\\BustCache\\": "src/BustCache/"
},
"classmap": [
"src/BustCache/exceptions.php"
]
}
},
"autoload-dev": {
"psr-4": {
Expand All @@ -59,7 +62,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
"dev-master": "3.0-dev"
}
}
}
2 changes: 1 addition & 1 deletion phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ parameters:
- # used for checksum only
message: "#^Calling md5\\(\\) is forbidden, use hash\\(\\) with at least SHA\\-256 for secure hash, or password_hash\\(\\) for passwords$#"
count: 1
path: src/BustCache/Helpers.php
path: src/BustCache/CacheBustingStrategies/ContentHash.php
89 changes: 79 additions & 10 deletions src/Bridges/BustCacheDI/BustCacheExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,38 @@

namespace Nepada\Bridges\BustCacheDI;

use Latte;
use Nepada\BustCache\BustCacheMacro;
use Nepada\Bridges\BustCacheLatte\BustCacheLatteExtension;
use Nepada\BustCache\BustCachePathProcessor;
use Nepada\BustCache\CacheBustingStrategies\ContentHash;
use Nepada\BustCache\CacheBustingStrategies\ModificationTime;
use Nepada\BustCache\CacheBustingStrategy;
use Nepada\BustCache\Caching\Cache;
use Nepada\BustCache\Caching\NetteCache;
use Nepada\BustCache\Caching\NullCache;
use Nepada\BustCache\FileSystem\FileSystem;
use Nepada\BustCache\FileSystem\LocalFileSystem;
use Nepada\BustCache\Manifest\AutodetectManifestFinder;
use Nepada\BustCache\Manifest\DefaultRevisionFinder;
use Nepada\BustCache\Manifest\ManifestFinder;
use Nepada\BustCache\Manifest\NullManifestFinder;
use Nepada\BustCache\Manifest\RevisionFinder;
use Nepada\BustCache\Manifest\StaticManifestFinder;
use Nette;
use Nette\Bridges\ApplicationLatte\ILatteFactory;
use Nette\Bridges\ApplicationDI\LatteExtension;
use Nette\DI\Definitions\Statement;
use Nette\Schema\Expect;

/**
* @property \stdClass $config
*/
class BustCacheExtension extends Nette\DI\CompilerExtension
{

private const CACHE_BUSTING_STRATEGIES = [
ContentHash::NAME => ContentHash::class,
ModificationTime::NAME => ModificationTime::class,
];

private string $wwwDir;

private bool $debugMode;
Expand All @@ -23,19 +47,64 @@ public function __construct(string $wwwDir, bool $debugMode = false)

public function getConfigSchema(): Nette\Schema\Schema
{
return Nette\Schema\Expect::structure([]);
return Expect::structure([
'strategy' => Expect::anyOf(array_keys(self::CACHE_BUSTING_STRATEGIES))
->default($this->debugMode ? ModificationTime::NAME : ContentHash::NAME),
'autoRefresh' => Expect::bool($this->debugMode),
'manifest' => Expect::anyOf(Expect::bool(), Expect::string())
->default(true),
]);
}

public function loadConfiguration(): void
{
$container = $this->getContainerBuilder();

$container->addDefinition($this->prefix('fileSystem'))
->setType(FileSystem::class)
->setFactory([LocalFileSystem::class, 'forDirectory'], [$this->wwwDir]);

$manifestFinder = $container->addDefinition($this->prefix('manifestFinder'))
->setType(ManifestFinder::class);
if ($this->config->manifest === true) {
$manifestFinder->setFactory(AutodetectManifestFinder::class);
} elseif ($this->config->manifest === false) {
$manifestFinder->setFactory(NullManifestFinder::class);
} else {
$manifestFinder->setFactory([StaticManifestFinder::class, 'forFilePath'], ['manifestFilePath' => $this->config->manifest]);
}

$container->addDefinition($this->prefix('revisionFinder'))
->setType(RevisionFinder::class)
->setFactory(DefaultRevisionFinder::class);

$container->addDefinition($this->prefix('cacheBustingStrategy'))
->setType(CacheBustingStrategy::class)
->setFactory(self::CACHE_BUSTING_STRATEGIES[$this->config->strategy]);

$container->addDefinition($this->prefix('cache'))
->setType(Cache::class)
->setFactory(NullCache::class);

$container->addDefinition($this->prefix('pathProcessor'))
->setType(BustCachePathProcessor::class);
}

public function beforeCompile(): void
{
$container = $this->getContainerBuilder();

$latteFactory = $container->getDefinitionByType(ILatteFactory::class);
assert($latteFactory instanceof Nette\DI\Definitions\FactoryDefinition);
$latteFactory->getResultDefinition()->addSetup(
'?->onCompile[] = function (' . Latte\Engine::class . ' $engine): void { $engine->addMacro("bustCache", new ' . BustCacheMacro::class . '(?, ?)); }',
['@self', $this->wwwDir, $this->debugMode],
);
if ($container->getByType(Nette\Caching\Storage::class) !== null) {
$cache = $container->getDefinitionByType(Cache::class);
assert($cache instanceof Nette\DI\Definitions\ServiceDefinition);
$cache->setFactory([NetteCache::class, 'withStorage']);
}

$pathProcessor = $container->getDefinitionByType(BustCachePathProcessor::class);
/** @var LatteExtension $latteExtension */
foreach ($this->compiler->getExtensions(LatteExtension::class) as $latteExtension) {
$latteExtension->addExtension(new Statement(BustCacheLatteExtension::class, [$pathProcessor, $this->config->autoRefresh]));
}
}

}
52 changes: 52 additions & 0 deletions src/Bridges/BustCacheLatte/BustCacheLatteExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php
declare(strict_types = 1);

namespace Nepada\Bridges\BustCacheLatte;

use Latte;
use Latte\Compiler\Tag;
use Latte\Engine;
use Nepada\Bridges\BustCacheLatte\Nodes\BustCacheNode;
use Nepada\BustCache\BustCachePathProcessor;

final class BustCacheLatteExtension extends Latte\Extension
{

private BustCachePathProcessor $bustCachePathProcessor;

private bool $autoRefresh;

public function __construct(BustCachePathProcessor $bustCachePathProcessor, bool $autoRefresh)
{
$this->bustCachePathProcessor = $bustCachePathProcessor;
$this->autoRefresh = $autoRefresh;
}

/**
* @return array<string, callable(Latte\Compiler\Tag, Latte\Compiler\TemplateParser): (Latte\Compiler\Node|\Generator|void)|\stdClass>
*/
public function getTags(): array
{
return [
'bustCache' => fn (Tag $tag): BustCacheNode => Nodes\BustCacheNode::create($tag, $this->autoRefresh, $this->bustCachePathProcessor),
];
}

/**
* @return array<mixed>
*/
public function getProviders(): array
{
return [
'bustCachePathProcessor' => $this->bustCachePathProcessor,
];
}

public function getCacheKey(Engine $engine): mixed
{
return [
'autoRefresh' => $this->autoRefresh,
];
}

}

0 comments on commit c586ac6

Please sign in to comment.