core/lib/Drupal/Core/CoreServiceProvider.php | 2 +- .../Compiler/CacheabilitySafeguardsPass.php | 24 +++--- .../Compiler/CacheabilitySafeguardsPassTest.php | 85 ++++++++++++++++++++++ 3 files changed, 101 insertions(+), 10 deletions(-) diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index 6fab50d..2477d4b 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -102,7 +102,7 @@ public function register(ContainerBuilder $container) { $container->addCompilerPass(new DependencySerializationTraitPass()); - $container->addCompilerPass(new CacheabilitySafeguardsPass(), PassConfig::TYPE_BEFORE_REMOVING); + $container->addCompilerPass(new CacheabilitySafeguardsPass()); } /** diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/CacheabilitySafeguardsPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/CacheabilitySafeguardsPass.php index ac9a468..c53ba11 100644 --- a/core/lib/Drupal/Core/DependencyInjection/Compiler/CacheabilitySafeguardsPass.php +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/CacheabilitySafeguardsPass.php @@ -7,6 +7,8 @@ namespace Drupal\Core\DependencyInjection\Compiler; +use Drupal\Component\Utility\Random; +use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -28,19 +30,23 @@ public function process(ContainerBuilder $container) { $guard_down = $container->hasParameter('renderer.cacheability_safeguards') && $container->getParameter('renderer.cacheability_safeguards') === FALSE; foreach ($container->findTaggedServiceIds('cacheability_safeguard') as $id => $attributes) { - $decorated = $id . '.non_bubbling'; + $non_bubbling = $id . '.non_bubbling'; + // Regardless of whether the guard is up or down, make sure the + // non-bubbling service is available for injection, but does not have to + // be public. if ($guard_down) { - // Provide an aliases to the non-safeguarded (non-bubbling) service, to - // ensure the non-bubbling service is always available. - $container->setAlias($decorated, $id); + $container->setAlias($non_bubbling, new Alias($id, FALSE)); } else { - $container->setDefinition($decorated, $container->getDefinition($id)) - ->setPublic(FALSE); - $container->register($id) - ->setClass($attributes[0]['class']) - ->setArguments([new Reference($decorated), new Reference('renderer')]); + // Decorate the non-bubbling service with a non-bubbling implementation; + // use a random ID so that the bubbling service is only ever accessed + // via its original name. + $random = new Random(); + $container->register($id . '.' . strtolower($random->name()), $attributes[0]['class']) + ->setArguments([new Reference($non_bubbling), new Reference('renderer')]) + ->setPublic(false) + ->setDecoratedService($id, $non_bubbling); } } } diff --git a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/CacheabilitySafeguardsPassTest.php b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/CacheabilitySafeguardsPassTest.php new file mode 100644 index 0000000..9cd68e6 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/CacheabilitySafeguardsPassTest.php @@ -0,0 +1,85 @@ +setParameter('renderer.cacheability_safeguards', $safeguards_enabled); + } + $container->register('renderer', __NAMESPACE__ . '\Renderer'); + $definition = new Definition(__NAMESPACE__ . '\Llama'); + $definition->addTag( + 'cacheability_safeguard', + ['class' => __NAMESPACE__ . '\BubblingLlama'] + ); + $container->setDefinition('llama', $definition); + $container->setDefinition('llama_consumer', (new Definition(__NAMESPACE__ . '\LlamaConsumer'))->addArgument(new Reference('llama'))); + $container->setDefinition('advanced_llama_consumer', (new Definition(__NAMESPACE__ . '\LlamaConsumer'))->addArgument(new Reference('llama.non_bubbling'))); + + // Apply the CacheabilitySafeguardsPass compiler pass. + $handler_pass = new CacheabilitySafeguardsPass(); + $handler_pass->process($container); + // The CacheabilitySafeguardsPass creates decorated services, so we must + // apply the DecoratorServicePass also. + $decorator_pass = new DecoratorServicePass(); + $decorator_pass->process($container); + + $this->assertInstanceOf(__NAMESPACE__ . '\\' . $expected_llama_class, $container->get('llama')); + $this->assertInstanceOf(__NAMESPACE__ . '\\' . $expected_llama_class, $container->get('llama_consumer')->llama); + $this->assertInstanceOf(__NAMESPACE__ . '\\' . $expected_non_bubbling_llama_class, $container->get('advanced_llama_consumer')->llama); + } + + /** + * Provides test data for testCacheabilitySafeguards(). + * + * @return array + */ + public function cacheabilitySafeguardsProvider() { + return [ + 'default' => [NULL, 'BubblingLlama', 'Llama'], + 'enabled' => [TRUE, 'BubblingLlama', 'Llama'], + 'disabled' => [FALSE, 'Llama', 'Llama'], + ]; + } + +} + +class Llama {} +class BubblingLlama { + // This ensures that the expected arguments are received. + function __construct(Llama $llama, Renderer $renderer) {} +} + +class LlamaConsumer { + // Publicly expose the consumed llama so we can test it. + public $llama; + function __construct($llama) { + $this->llama = $llama; + } +} + +class Renderer {}