Merge branch '3.4'

* 3.4:
  A DI tag for resettable services.
  Add default templates directory and option to configure it
  Feature #23583  Add current and fallback locales in WDT / Profiler
This commit is contained in:
Fabien Potencier 2017-09-14 19:22:10 -07:00
commit b05fce2453
20 changed files with 372 additions and 3 deletions

View File

@ -33,6 +33,7 @@ use Symfony\Component\HttpKernel\DependencyInjection\AddCacheWarmerPass;
use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass; use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass;
use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass; use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass;
use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass; use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass;
use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass;
use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass; use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass;
use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass; use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass;
use Symfony\Component\Serializer\DependencyInjection\SerializerPass; use Symfony\Component\Serializer\DependencyInjection\SerializerPass;
@ -106,6 +107,7 @@ class FrameworkBundle extends Bundle
$container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING);
$this->addCompilerPassIfExists($container, FormPass::class); $this->addCompilerPassIfExists($container, FormPass::class);
$container->addCompilerPass(new WorkflowGuardListenerPass()); $container->addCompilerPass(new WorkflowGuardListenerPass());
$container->addCompilerPass(new ResettableServicePass());
if ($container->getParameter('kernel.debug')) { if ($container->getParameter('kernel.debug')) {
$container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); $container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);

View File

@ -60,5 +60,11 @@
<service id="Symfony\Component\Config\Resource\SelfCheckingResourceChecker"> <service id="Symfony\Component\Config\Resource\SelfCheckingResourceChecker">
<tag name="config_cache.resource_checker" priority="-990" /> <tag name="config_cache.resource_checker" priority="-990" />
</service> </service>
<service id="Symfony\Component\HttpKernel\EventListener\ServiceResetListener">
<argument /> <!-- ResettableServicePass will inject an iterator of initialized services here ($serviceId => $serviceInstance) -->
<argument type="collection" /> <!-- ResettableServicePass will inject an array of reset methods here ($serviceId => $method) -->
<tag name="kernel.event_subscriber" />
</service>
</services> </services>
</container> </container>

View File

@ -11,6 +11,7 @@ CHANGELOG
* deprecated `Symfony\Bundle\TwigBundle\Command\DebugCommand`, use `Symfony\Bridge\Twig\Command\DebugCommand` instead * deprecated `Symfony\Bundle\TwigBundle\Command\DebugCommand`, use `Symfony\Bridge\Twig\Command\DebugCommand` instead
* deprecated relying on the `ContainerAwareInterface` implementation for `Symfony\Bundle\TwigBundle\Command\LintCommand` * deprecated relying on the `ContainerAwareInterface` implementation for `Symfony\Bundle\TwigBundle\Command\LintCommand`
* added option to configure default path templates (via `default_path`)
3.3.0 3.3.0
----- -----

View File

@ -130,6 +130,10 @@ class Configuration implements ConfigurationInterface
->booleanNode('strict_variables')->end() ->booleanNode('strict_variables')->end()
->scalarNode('auto_reload')->end() ->scalarNode('auto_reload')->end()
->integerNode('optimizations')->min(-1)->end() ->integerNode('optimizations')->min(-1)->end()
->scalarNode('default_path')
->info('The default path used to load templates')
->defaultValue('%kernel.project_dir%/templates')
->end()
->arrayNode('paths') ->arrayNode('paths')
->normalizeKeys(false) ->normalizeKeys(false)
->useAttributeAsKey('paths') ->useAttributeAsKey('paths')

View File

@ -109,7 +109,7 @@ class TwigExtension extends Extension
$container->getDefinition('twig.cache_warmer')->replaceArgument(2, $config['paths']); $container->getDefinition('twig.cache_warmer')->replaceArgument(2, $config['paths']);
$container->getDefinition('twig.template_iterator')->replaceArgument(2, $config['paths']); $container->getDefinition('twig.template_iterator')->replaceArgument(2, $config['paths']);
$bundleHierarchy = $this->getBundleHierarchy($container); $bundleHierarchy = $this->getBundleHierarchy($container, $config);
foreach ($bundleHierarchy as $name => $bundle) { foreach ($bundleHierarchy as $name => $bundle) {
$namespace = $this->normalizeBundleName($name); $namespace = $this->normalizeBundleName($name);
@ -130,6 +130,11 @@ class TwigExtension extends Extension
} }
$container->addResource(new FileExistenceResource($dir)); $container->addResource(new FileExistenceResource($dir));
if (file_exists($dir = $container->getParameterBag()->resolveValue($config['default_path']))) {
$twigFilesystemLoaderDefinition->addMethodCall('addPath', array($dir));
}
$container->addResource(new FileExistenceResource($dir));
if (!empty($config['globals'])) { if (!empty($config['globals'])) {
$def = $container->getDefinition('twig'); $def = $container->getDefinition('twig');
foreach ($config['globals'] as $key => $global) { foreach ($config['globals'] as $key => $global) {
@ -161,7 +166,7 @@ class TwigExtension extends Extension
$container->registerForAutoconfiguration(RuntimeExtensionInterface::class)->addTag('twig.runtime'); $container->registerForAutoconfiguration(RuntimeExtensionInterface::class)->addTag('twig.runtime');
} }
private function getBundleHierarchy(ContainerBuilder $container) private function getBundleHierarchy(ContainerBuilder $container, array $config)
{ {
$bundleHierarchy = array(); $bundleHierarchy = array();
@ -179,6 +184,11 @@ class TwigExtension extends Extension
} }
$container->addResource(new FileExistenceResource($dir)); $container->addResource(new FileExistenceResource($dir));
if (file_exists($dir = $container->getParameterBag()->resolveValue($config['default_path']).'/bundles/'.$name)) {
$bundleHierarchy[$name]['paths'][] = $dir;
}
$container->addResource(new FileExistenceResource($dir));
if (file_exists($dir = $bundle['path'].'/Resources/views')) { if (file_exists($dir = $bundle['path'].'/Resources/views')) {
$bundleHierarchy[$name]['paths'][] = $dir; $bundleHierarchy[$name]['paths'][] = $dir;
} }

View File

@ -26,6 +26,7 @@
<xsd:attribute name="debug" type="xsd:string" /> <xsd:attribute name="debug" type="xsd:string" />
<xsd:attribute name="strict-variables" type="xsd:string" /> <xsd:attribute name="strict-variables" type="xsd:string" />
<xsd:attribute name="exception-controller" type="xsd:string" /> <xsd:attribute name="exception-controller" type="xsd:string" />
<xsd:attribute name="default-path" type="xsd:string" />
</xsd:complexType> </xsd:complexType>
<xsd:complexType name="date"> <xsd:complexType name="date">

View File

@ -17,6 +17,7 @@ $container->loadFromExtension('twig', array(
'charset' => 'ISO-8859-1', 'charset' => 'ISO-8859-1',
'debug' => true, 'debug' => true,
'strict_variables' => true, 'strict_variables' => true,
'default_path' => '%kernel.root_dir%/templates',
'paths' => array( 'paths' => array(
'path1', 'path1',
'path2', 'path2',

View File

@ -6,7 +6,7 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/twig http://symfony.com/schema/dic/twig/twig-1.0.xsd"> http://symfony.com/schema/dic/twig http://symfony.com/schema/dic/twig/twig-1.0.xsd">
<twig:config auto-reload="true" autoescape="true" base-template-class="stdClass" cache="/tmp" charset="ISO-8859-1" debug="true" strict-variables="true"> <twig:config auto-reload="true" autoescape="true" base-template-class="stdClass" cache="/tmp" charset="ISO-8859-1" debug="true" strict-variables="true" default-path="%kernel.root_dir%/templates">
<twig:form-theme>MyBundle::form.html.twig</twig:form-theme> <twig:form-theme>MyBundle::form.html.twig</twig:form-theme>
<twig:global key="foo" id="bar" type="service" /> <twig:global key="foo" id="bar" type="service" />
<twig:global key="baz">@@qux</twig:global> <twig:global key="baz">@@qux</twig:global>

View File

@ -13,6 +13,7 @@ twig:
charset: ISO-8859-1 charset: ISO-8859-1
debug: true debug: true
strict_variables: true strict_variables: true
default_path: '%kernel.root_dir%/templates'
paths: paths:
path1: '' path1: ''
path2: '' path2: ''

View File

@ -196,6 +196,7 @@ class TwigExtensionTest extends TestCase
array(__DIR__.'/Fixtures/Bundle/ChildChildTwigBundle/Resources/views', 'Twig'), array(__DIR__.'/Fixtures/Bundle/ChildChildTwigBundle/Resources/views', 'Twig'),
array(__DIR__.'/Fixtures/Bundle/ChildTwigBundle/Resources/views', 'Twig'), array(__DIR__.'/Fixtures/Bundle/ChildTwigBundle/Resources/views', 'Twig'),
array(__DIR__.'/Fixtures/Resources/TwigBundle/views', 'Twig'), array(__DIR__.'/Fixtures/Resources/TwigBundle/views', 'Twig'),
array(__DIR__.'/Fixtures/templates/bundles/TwigBundle', 'Twig'),
array(realpath(__DIR__.'/../..').'/Resources/views', 'Twig'), array(realpath(__DIR__.'/../..').'/Resources/views', 'Twig'),
array(__DIR__.'/Fixtures/Bundle/ChildChildChildChildTwigBundle/Resources/views', 'ChildTwig'), array(__DIR__.'/Fixtures/Bundle/ChildChildChildChildTwigBundle/Resources/views', 'ChildTwig'),
array(__DIR__.'/Fixtures/Bundle/ChildChildChildTwigBundle/Resources/views', 'ChildTwig'), array(__DIR__.'/Fixtures/Bundle/ChildChildChildTwigBundle/Resources/views', 'ChildTwig'),
@ -205,6 +206,7 @@ class TwigExtensionTest extends TestCase
array(__DIR__.'/Fixtures/Bundle/ChildChildChildTwigBundle/Resources/views', 'ChildChildTwig'), array(__DIR__.'/Fixtures/Bundle/ChildChildChildTwigBundle/Resources/views', 'ChildChildTwig'),
array(__DIR__.'/Fixtures/Bundle/ChildChildTwigBundle/Resources/views', 'ChildChildTwig'), array(__DIR__.'/Fixtures/Bundle/ChildChildTwigBundle/Resources/views', 'ChildChildTwig'),
array(__DIR__.'/Fixtures/Resources/views'), array(__DIR__.'/Fixtures/Resources/views'),
array(__DIR__.'/Fixtures/templates'),
), $paths); ), $paths);
} }

View File

@ -12,6 +12,12 @@
{% endset %} {% endset %}
{% set text %} {% set text %}
<div class="sf-toolbar-info-piece">
<b>Locale</b>
<span class="sf-toolbar-status">
{{ collector.locale|default('-') }}
</span>
</div>
<div class="sf-toolbar-info-piece"> <div class="sf-toolbar-info-piece">
<b>Missing messages</b> <b>Missing messages</b>
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.countMissings ? 'red' }}"> <span class="sf-toolbar-status sf-toolbar-status-{{ collector.countMissings ? 'red' }}">
@ -61,6 +67,20 @@
{% endblock %} {% endblock %}
{% block panelContent %} {% block panelContent %}
<h2>Translation Locales</h2>
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.locale|default('-') }}</span>
<span class="label">Locale</span>
</div>
<div class="metric">
<span class="value">{{ collector.fallbackLocales|join(', ')|default('-') }}</span>
<span class="label">Fallback locales</span>
</div>
</div>
<h2>Translation Metrics</h2> <h2>Translation Metrics</h2>
<div class="metrics"> <div class="metrics">

View File

@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DependencyInjection;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\EventListener\ServiceResetListener;
/**
* @author Alexander M. Turek <me@derrabus.de>
*/
class ResettableServicePass implements CompilerPassInterface
{
private $tagName;
/**
* @param string $tagName
*/
public function __construct($tagName = 'kernel.reset')
{
$this->tagName = $tagName;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->has(ServiceResetListener::class)) {
return;
}
$services = $methods = array();
foreach ($container->findTaggedServiceIds($this->tagName, true) as $id => $tags) {
$services[$id] = new Reference($id, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE);
$attributes = $tags[0];
if (!isset($attributes['method'])) {
throw new RuntimeException(sprintf('Tag %s requires the "method" attribute to be set.', $this->tagName));
}
$methods[$id] = $attributes['method'];
}
if (empty($services)) {
$container->removeDefinition(ServiceResetListener::class);
return;
}
$container->findDefinition(ServiceResetListener::class)
->replaceArgument(0, new IteratorArgument($services))
->replaceArgument(1, $methods);
}
}

View File

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Clean up services between requests.
*
* @author Alexander M. Turek <me@derrabus.de>
*/
class ServiceResetListener implements EventSubscriberInterface
{
private $services;
private $resetMethods;
public function __construct(\Traversable $services, array $resetMethods)
{
$this->services = $services;
$this->resetMethods = $resetMethods;
}
public function onKernelTerminate()
{
foreach ($this->services as $id => $service) {
$method = $this->resetMethods[$id];
$service->$method();
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
KernelEvents::TERMINATE => array('onKernelTerminate', -2048),
);
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace Symfony\Component\HttpKernel\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass;
use Symfony\Component\HttpKernel\EventListener\ServiceResetListener;
use Symfony\Component\HttpKernel\Tests\Fixtures\ClearableService;
use Symfony\Component\HttpKernel\Tests\Fixtures\ResettableService;
class ResettableServicePassTest extends TestCase
{
public function testCompilerPass()
{
$container = new ContainerBuilder();
$container->register('one', ResettableService::class)
->addTag('kernel.reset', array('method' => 'reset'));
$container->register('two', ClearableService::class)
->addTag('kernel.reset', array('method' => 'clear'));
$container->register(ServiceResetListener::class)
->setArguments(array(null, array()));
$container->addCompilerPass(new ResettableServicePass('kernel.reset'));
$container->compile();
$definition = $container->getDefinition(ServiceResetListener::class);
$this->assertEquals(
array(
new IteratorArgument(array(
'one' => new Reference('one', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
'two' => new Reference('two', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
)),
array(
'one' => 'reset',
'two' => 'clear',
),
),
$definition->getArguments()
);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage Tag kernel.reset requires the "method" attribute to be set.
*/
public function testMissingMethod()
{
$container = new ContainerBuilder();
$container->register(ResettableService::class)
->addTag('kernel.reset');
$container->register(ServiceResetListener::class)
->setArguments(array(null, array()));
$container->addCompilerPass(new ResettableServicePass('kernel.reset'));
$container->compile();
}
public function testCompilerPassWithoutResetters()
{
$container = new ContainerBuilder();
$container->register(ServiceResetListener::class)
->setArguments(array(null, array()));
$container->addCompilerPass(new ResettableServicePass());
$container->compile();
$this->assertFalse($container->has(ServiceResetListener::class));
}
public function testCompilerPassWithoutListener()
{
$container = new ContainerBuilder();
$container->addCompilerPass(new ResettableServicePass());
$container->compile();
$this->assertFalse($container->has(ServiceResetListener::class));
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace Symfony\Component\HttpKernel\Tests\EventListener;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\EventListener\ServiceResetListener;
use Symfony\Component\HttpKernel\Tests\Fixtures\ClearableService;
use Symfony\Component\HttpKernel\Tests\Fixtures\ResettableService;
class ServiceResetListenerTest extends TestCase
{
protected function setUp()
{
ResettableService::$counter = 0;
ClearableService::$counter = 0;
}
public function testResetServicesNoOp()
{
$container = $this->buildContainer();
$container->get('reset_subscriber')->onKernelTerminate();
$this->assertEquals(0, ResettableService::$counter);
$this->assertEquals(0, ClearableService::$counter);
}
public function testResetServicesPartially()
{
$container = $this->buildContainer();
$container->get('one');
$container->get('reset_subscriber')->onKernelTerminate();
$this->assertEquals(1, ResettableService::$counter);
$this->assertEquals(0, ClearableService::$counter);
}
public function testResetServicesTwice()
{
$container = $this->buildContainer();
$container->get('one');
$container->get('reset_subscriber')->onKernelTerminate();
$container->get('two');
$container->get('reset_subscriber')->onKernelTerminate();
$this->assertEquals(2, ResettableService::$counter);
$this->assertEquals(1, ClearableService::$counter);
}
/**
* @return ContainerBuilder
*/
private function buildContainer()
{
$container = new ContainerBuilder();
$container->register('one', ResettableService::class);
$container->register('two', ClearableService::class);
$container->register('reset_subscriber', ServiceResetListener::class)
->addArgument(new IteratorArgument(array(
'one' => new Reference('one', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
'two' => new Reference('two', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
)))
->addArgument(array(
'one' => 'reset',
'two' => 'clear',
));
$container->compile();
return $container;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Symfony\Component\HttpKernel\Tests\Fixtures;
class ClearableService
{
public static $counter = 0;
public function clear()
{
++self::$counter;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Symfony\Component\HttpKernel\Tests\Fixtures;
class ResettableService
{
public static $counter = 0;
public function reset()
{
++self::$counter;
}
}

View File

@ -45,6 +45,9 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto
$this->data = $this->computeCount($messages); $this->data = $this->computeCount($messages);
$this->data['messages'] = $messages; $this->data['messages'] = $messages;
$this->data['locale'] = $this->translator->getLocale();
$this->data['fallback_locales'] = $this->translator->getFallbackLocales();
$this->data = $this->cloneVar($this->data); $this->data = $this->cloneVar($this->data);
} }
@ -87,6 +90,16 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto
return isset($this->data[DataCollectorTranslator::MESSAGE_DEFINED]) ? $this->data[DataCollectorTranslator::MESSAGE_DEFINED] : 0; return isset($this->data[DataCollectorTranslator::MESSAGE_DEFINED]) ? $this->data[DataCollectorTranslator::MESSAGE_DEFINED] : 0;
} }
public function getLocale()
{
return !empty($this->data['locale']) ? $this->data['locale'] : null;
}
public function getFallbackLocales()
{
return (isset($this->data['fallback_locales']) && count($this->data['fallback_locales']) > 0) ? $this->data['fallback_locales'] : array();
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */