Commit 9ec06367 authored by alexpott's avatar alexpott

Issue #2544932 by dawehner, Fabianx, Wim Leers, Gábor Hojtsy, alexpott, catch,...

Issue #2544932 by dawehner, Fabianx, Wim Leers, Gábor Hojtsy, alexpott, catch, chx, msonnabaum: Twig should not rely on loading php from shared file system
parent 79bb8810
......@@ -1439,7 +1439,7 @@ services:
class: Drupal\Core\Extension\InfoParser
twig:
class: Drupal\Core\Template\TwigEnvironment
arguments: ['@app.root', '@cache.default', '@twig.loader', '%twig.config%']
arguments: ['@app.root', '@cache.default', '%twig_extension_hash%', '@twig.loader', '%twig.config%']
tags:
- { name: service_collector, tag: 'twig.extension', call: addExtension }
twig.extension:
......
......@@ -19,6 +19,7 @@
use Drupal\Core\DependencyInjection\Compiler\StackedKernelPass;
use Drupal\Core\DependencyInjection\Compiler\StackedSessionHandlerPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterStreamWrappersPass;
use Drupal\Core\DependencyInjection\Compiler\TwigExtensionPass;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass;
......@@ -78,6 +79,8 @@ public function register(ContainerBuilder $container) {
$container->addCompilerPass(new RegisterStreamWrappersPass());
$container->addCompilerPass(new GuzzleMiddlewarePass());
$container->addCompilerPass(new TwigExtensionPass());
// Add a compiler pass for registering event subscribers.
$container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
......
<?php
/**
* @file
* Contains \Drupal\Core\DependencyInjection\Compiler\TwigExtensionPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Adds the twig_extension_hash parameter to the container.
*
* twig_extension_hash is a hash of all extension mtimes for Twig template
* invalidation.
*/
class TwigExtensionPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
$twig_extension_hash = '';
foreach (array_keys($container->findTaggedServiceIds('twig.extension')) as $service_id) {
$class_name = $container->getDefinition($service_id)->getClass();
$reflection = new \ReflectionClass($class_name);
// We use the class names as hash in order to invalidate on new extensions
// and mtime for every time we change an existing file.
$twig_extension_hash .= $class_name . filemtime($reflection->getFileName());
}
$container->setParameter('twig_extension_hash', hash('crc32b', $twig_extension_hash));
}
}
......@@ -21,9 +21,28 @@
* @see core\vendor\twig\twig\lib\Twig\Environment.php
*/
class TwigEnvironment extends \Twig_Environment {
/**
* The cache object used for auto-refresh via mtime.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cache_object = NULL;
/**
* The PhpStorage object used for storing the templates.
*
* @var \Drupal\Core\PhpStorage\PhpStorageFactory
*/
protected $storage = NULL;
/**
* The template cache filename prefix.
*
* @var string
*/
protected $templateCacheFilenamePrefix;
/**
* Static cache of template classes.
*
......@@ -39,13 +58,16 @@ class TwigEnvironment extends \Twig_Environment {
* The app root.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache bin.
* @param string $twig_extension_hash
* The Twig extension hash.
* @param \Twig_LoaderInterface $loader
* The Twig loader or loader chain.
* @param array $options
* The options for the Twig environment.
*/
public function __construct($root, CacheBackendInterface $cache, \Twig_LoaderInterface $loader = NULL, $options = array()) {
public function __construct($root, CacheBackendInterface $cache, $twig_extension_hash, \Twig_LoaderInterface $loader = NULL, $options = array()) {
$this->cache_object = $cache;
$this->templateCacheFilenamePrefix = $twig_extension_hash;
// Ensure that twig.engine is loaded, given that it is needed to render a
// template because functions like TwigExtension::escapeFilter() are called.
......@@ -88,6 +110,26 @@ public function updateCompiledTemplate($cache_filename, $name) {
$this->cache_object->set($cid, REQUEST_TIME);
}
/**
* {@inheritdoc}
*/
public function getCacheFilename($name) {
// We override the cache filename in order to avoid issues with not using
// shared filesystems. The Twig templates for example rely on available Twig
// extensions, so we use the twig extension hash which varies by extensions
// and their mtime.
// @see \Drupal\Core\DependencyInjection\Compiler\TwigExtensionPass
if (!$this->cache) {
return FALSE;
}
$class = substr($this->getTemplateClass($name), strlen($this->templateClassPrefix));
// The first part is what is invalidated.
return $this->templateCacheFilenamePrefix . '_' . basename($name) . '_' . $class;
}
/**
* Implements Twig_Environment::loadTemplate().
*
......
......@@ -83,5 +83,25 @@ public function testTemplateNotFoundException() {
}
}
/**
* Ensures that cacheFilename() varies by extensions + deployment identifier.
*/
public function testCacheFilename() {
/** @var \Drupal\Core\Template\TwigEnvironment $environment */
// Note: Later we refetch the twig service in order to bypass its internal
// static cache.
$environment = \Drupal::service('twig');
$original_filename = $environment->getCacheFilename('core/modules/system/templates/container.html.twig');
\Drupal::getContainer()->set('twig', NULL);
\Drupal::service('module_installer')->install(['twig_extension_test']);
$environment = \Drupal::service('twig');
$new_extension_filename = $environment->getCacheFilename('core/modules/system/templates/container.html.twig');
\Drupal::getContainer()->set('twig', NULL);
$this->assertNotEqual($new_extension_filename, $original_filename);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment