Skip to content

Commit 3149cca

Browse files
authored
Merge pull request #1 from Incenteev/cache
Implement caching of the asset hashes for prod
2 parents 192fbcf + cedf958 commit 3149cca

21 files changed

+406
-63
lines changed

.travis.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@ matrix:
1111
fast_finish: true
1212
include:
1313
- php: 7.0
14-
env: COMPOSER_FLAGS="--prefer-lowest"SYMFONY_DEPRECATIONS_HELPER=weak
14+
env: COMPOSER_FLAGS="--prefer-lowest" SYMFONY_DEPRECATIONS_HELPER=weak
1515
- php: 7.1
1616
env: DEPENDENCIES=dev SYMFONY_DEPRECATIONS_HELPER=weak
17-
- php: 7.0
18-
env: SYMFONY_VERSION=2.8.*
1917
allow_failures:
2018
- php: nightly
2119

@@ -24,7 +22,6 @@ cache:
2422
- $HOME/.composer/cache/files
2523

2624
before_install:
27-
- if [ "$SYMFONY_VERSION" != "" ]; then composer require "symfony/symfony:${SYMFONY_VERSION}" --no-update; fi;
2825
- if [ "$DEPENDENCIES" = "dev" ]; then perl -pi -e 's/^}$/,"minimum-stability":"dev"}/' composer.json; fi;
2926

3027
install: composer update $COMPOSER_FLAGS

README.md

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,6 @@ framework:
4343
version_strategy: incenteev_hashed_asset.strategy
4444
```
4545
46-
If you are using Symfony <3.1, this configuration setting is not available.
47-
Here is the workaround:
48-
49-
```yaml
50-
# app/config/config.yml
51-
framework:
52-
assets:
53-
version: dummy # set a dummy version so that the package does not use the empty version
54-
55-
services:
56-
assets._version__default:
57-
alias: incenteev_hashed_asset.strategy
58-
# If you use additional packages, you may need to create additional aliases for other packages than `_default`
59-
```
60-
6146
## Advanced configuration
6247
6348
The default configuration should fit common needs, but the bundle exposes

composer.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@
1111
],
1212
"require": {
1313
"php": "^7.0",
14-
"symfony/asset": "^2.8 || ^3.0",
15-
"symfony/config": "^2.8 || ^3.0",
16-
"symfony/dependency-injection": "^2.8 || ^3.0",
17-
"symfony/http-kernel": "^2.8 || ^3.0"
14+
"symfony/asset": "^3.2",
15+
"symfony/cache": "^3.2",
16+
"symfony/config": "^3.2",
17+
"symfony/dependency-injection": "^3.2",
18+
"symfony/finder": "^3.2",
19+
"symfony/framework-bundle": "^3.2",
20+
"symfony/http-kernel": "^3.2"
1821
},
1922
"require-dev": {
2023
"symfony/phpunit-bridge": "^3.2"

src/Asset/HashingVersionStrategy.php

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,28 @@
22

33
namespace Incenteev\HashedAssetBundle\Asset;
44

5+
use Incenteev\HashedAssetBundle\Hashing\AssetHasherInterface;
56
use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface;
67

78
class HashingVersionStrategy implements VersionStrategyInterface
89
{
9-
private $webRoot;
10+
private $hasher;
1011
private $format;
1112

12-
public function __construct(string $webRoot, string $format = null)
13+
public function __construct(AssetHasherInterface $hasher, string $format = null)
1314
{
1415
$this->format = $format ?: '%s?%s';
15-
$this->webRoot = $webRoot;
16+
$this->hasher = $hasher;
1617
}
1718

1819
public function getVersion($path)
1920
{
20-
$fullPath = $this->webRoot.'/'.ltrim($path, '/');
21-
22-
if (!is_file($fullPath)) {
23-
return '';
24-
}
25-
26-
return substr(sha1_file($fullPath), 0, 7);
21+
return $this->hasher->computeHash($path);
2722
}
2823

2924
public function applyVersion($path)
3025
{
31-
$versionized = sprintf($this->format, ltrim($path, '/'), $this->getVersion($path));
26+
$versionized = sprintf($this->format, ltrim($path, '/'), $this->hasher->computeHash($path));
3227

3328
if ($path && '/' === $path[0]) {
3429
return '/'.$versionized;

src/CacheWarmer/AssetFinder.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Incenteev\HashedAssetBundle\CacheWarmer;
4+
5+
use Symfony\Component\Finder\Finder;
6+
use Symfony\Component\Finder\SplFileInfo;
7+
8+
class AssetFinder
9+
{
10+
private $webRoot;
11+
12+
public function __construct(string $webRoot)
13+
{
14+
$this->webRoot = $webRoot;
15+
}
16+
17+
public function getAssetPaths(): \Traversable
18+
{
19+
$finder = (new Finder())->files()
20+
->in($this->webRoot);
21+
22+
/** @var SplFileInfo $file */
23+
foreach ($finder as $file) {
24+
yield $file->getRelativePathname();
25+
}
26+
}
27+
}

src/CacheWarmer/HashCacheWarmer.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace Incenteev\HashedAssetBundle\CacheWarmer;
4+
5+
use Incenteev\HashedAssetBundle\Hashing\AssetHasherInterface;
6+
use Incenteev\HashedAssetBundle\Hashing\CachedHasher;
7+
use Psr\Cache\CacheItemPoolInterface;
8+
use Symfony\Component\Cache\Adapter\AdapterInterface;
9+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
10+
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
11+
use Symfony\Component\Cache\Adapter\ProxyAdapter;
12+
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
13+
14+
final class HashCacheWarmer implements CacheWarmerInterface
15+
{
16+
private $assetFinder;
17+
private $cacheFile;
18+
private $hasher;
19+
private $fallbackPool;
20+
21+
public function __construct(AssetFinder $assetFinder, string $cacheFile, AssetHasherInterface $hasher, CacheItemPoolInterface $fallbackPool)
22+
{
23+
$this->assetFinder = $assetFinder;
24+
$this->cacheFile = $cacheFile;
25+
$this->hasher = $hasher;
26+
27+
if (!$fallbackPool instanceof AdapterInterface) {
28+
$fallbackPool = new ProxyAdapter($fallbackPool);
29+
}
30+
31+
$this->fallbackPool = $fallbackPool;
32+
}
33+
34+
public function warmUp($cacheDir)
35+
{
36+
$phpArrayPool = new PhpArrayAdapter($this->cacheFile, $this->fallbackPool);
37+
$arrayPool = new ArrayAdapter(0, false);
38+
39+
$hasher = new CachedHasher($this->hasher, $arrayPool);
40+
41+
foreach ($this->assetFinder->getAssetPaths() as $path) {
42+
$hasher->computeHash($path);
43+
}
44+
45+
$values = $arrayPool->getValues();
46+
$phpArrayPool->warmUp($values);
47+
48+
foreach ($values as $k => $v) {
49+
$item = $this->fallbackPool->getItem($k);
50+
$this->fallbackPool->saveDeferred($item->set($v));
51+
}
52+
$this->fallbackPool->commit();
53+
}
54+
55+
public function isOptional()
56+
{
57+
return true;
58+
}
59+
}

src/DependencyInjection/IncenteevHashedAssetExtension.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,17 @@ protected function loadInternal(array $config, ContainerBuilder $container)
1717
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
1818
$loader->load('services.xml');
1919

20+
$container->getDefinition('incenteev_hashed_asset.file_hasher')
21+
->replaceArgument(0, $config['web_root']);
22+
2023
$container->getDefinition('incenteev_hashed_asset.strategy')
21-
->replaceArgument(0, $config['web_root'])
2224
->replaceArgument(1, $config['version_format']);
25+
26+
if (!$container->getParameter('kernel.debug')) {
27+
$loader->load('cache.xml');
28+
29+
$container->getDefinition('incenteev_hashed_asset.asset_finder')
30+
->replaceArgument(0, $config['web_root']);
31+
}
2332
}
2433
}

src/Hashing/AssetHasherInterface.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Incenteev\HashedAssetBundle\Hashing;
4+
5+
interface AssetHasherInterface
6+
{
7+
public function computeHash(string $path): string;
8+
}

src/Hashing/CachedHasher.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Incenteev\HashedAssetBundle\Hashing;
4+
5+
use Psr\Cache\CacheItemPoolInterface;
6+
7+
final class CachedHasher implements AssetHasherInterface
8+
{
9+
private $hasher;
10+
private $cache;
11+
12+
public function __construct(AssetHasherInterface $hasher, CacheItemPoolInterface $cache)
13+
{
14+
$this->hasher = $hasher;
15+
$this->cache = $cache;
16+
}
17+
18+
public function computeHash(string $path): string
19+
{
20+
// The hashing implementation does not care about leading slashes in the path, so share cache keys for them
21+
$item = $this->cache->getItem(base64_encode(ltrim($path, '/')));
22+
23+
if ($item->isHit()) {
24+
return $item->get();
25+
}
26+
27+
$hash = $this->hasher->computeHash($path);
28+
29+
$item->set($hash);
30+
$this->cache->save($item);
31+
32+
return $hash;
33+
}
34+
}

src/Hashing/FileHasher.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Incenteev\HashedAssetBundle\Hashing;
4+
5+
final class FileHasher implements AssetHasherInterface
6+
{
7+
private $webRoot;
8+
9+
public function __construct(string $webRoot)
10+
{
11+
$this->webRoot = $webRoot;
12+
}
13+
public function computeHash(string $path): string
14+
{
15+
$fullPath = $this->webRoot.'/'.ltrim($path, '/');
16+
17+
if (!is_file($fullPath)) {
18+
return '';
19+
}
20+
21+
return substr(sha1_file($fullPath), 0, 7);
22+
}
23+
}

0 commit comments

Comments
 (0)