From be5a208c391a06858d1692b0b8f07a643a5d3dca Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 10 Jan 2015 11:33:00 +0100 Subject: [PATCH 1/3] decoupled global variables system in Twig from the Templating one --- .../Templating/GlobalVariables.php | 2 +- src/Symfony/Bundle/TwigBundle/AppVariable.php | 160 ++++++++++++++++++ .../TwigBundle/Resources/config/twig.xml | 10 +- .../DependencyInjection/TwigExtensionTest.php | 2 +- src/Symfony/Bundle/TwigBundle/composer.json | 2 +- 5 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Bundle/TwigBundle/AppVariable.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.php b/src/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.php index 51fe53f2e0..c2a2c7888c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.php @@ -18,7 +18,7 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\SecurityContext; /** - * GlobalVariables is the entry point for Symfony global variables in Twig templates. + * GlobalVariables is the entry point for Symfony global variables in PHP templates. * * @author Fabien Potencier */ diff --git a/src/Symfony/Bundle/TwigBundle/AppVariable.php b/src/Symfony/Bundle/TwigBundle/AppVariable.php new file mode 100644 index 0000000000..f40cba5cd1 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/AppVariable.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\SecurityContextInterface; + +/** + * Exposes some Symfony parameters and services as an "app" global variable. + * + * @author Fabien Potencier + */ +class AppVariable +{ + private $security; + private $tokenStorage; + private $requestStack; + private $environment; + private $debug; + + /** + * @deprecated since version 2.7, to be removed in 3.0. + */ + public function setSecurity(SecurityContextInterface $security) + { + $this->security = $security; + } + + public function setTokenStorage(TokenStorageInterface $tokenStorage) + { + $this->tokenStorage = $tokenStorage; + } + + public function setRequestStack(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + } + + public function setEnvironment($environment) + { + $this->environment = $environment; + } + + public function setDebug($debug) + { + $this->debug = (bool) $debug; + } + + /** + * Returns the security context service. + * + * @deprecated since version 2.6, to be removed in 3.0. + * + * @return SecurityContext|null The security context + */ + public function getSecurity() + { + trigger_error('The "app.security" variable is deprecated since version 2.6 and will be removed in 3.0.', E_USER_DEPRECATED); + + if (null === $this->security) { + throw new \RuntimeException('The "app.security" variable is not available.'); + } + + return $this->security; + } + + /** + * Returns the current user. + * + * @return mixed + * + * @see TokenInterface::getUser() + */ + public function getUser() + { + if (null === $this->tokenStorage) { + throw new \RuntimeException('The "app.user" variable is not available.'); + } + + if (!$token = $this->tokenStorage->getToken()) { + return; + } + + $user = $token->getUser(); + if (is_object($user)) { + return $user; + } + } + + /** + * Returns the current request. + * + * @return Request|null The HTTP request object + */ + public function getRequest() + { + if (null === $this->requestStack) { + throw new \RuntimeException('The "app.request" variable is not available.'); + } + + return $this->requestStack->getCurrentRequest(); + } + + /** + * Returns the current session. + * + * @return Session|null The session + */ + public function getSession() + { + if (null === $this->requestStack) { + throw new \RuntimeException('The "app.session" variable is not available.'); + } + + if ($request = $this->getRequest()) { + return $request->getSession(); + } + } + + /** + * Returns the current app environment. + * + * @return string The current environment string (e.g 'dev') + */ + public function getEnvironment() + { + if (null === $this->environment) { + throw new \RuntimeException('The "app.environment" variable is not available.'); + } + + return $this->environment; + } + + /** + * Returns the current app debug mode. + * + * @return bool The current debug mode + */ + public function getDebug() + { + if (null === $this->debug) { + throw new \RuntimeException('The "app.debug" variable is not available.'); + } + + return $this->debug; + } +} diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index 40dbaa9fbb..b0a7223287 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -34,10 +34,18 @@ %twig.options% app - + + + %kernel.environment% + %kernel.debug% + + + + + diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php index 05d450db73..9709059caf 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php @@ -99,7 +99,7 @@ class TwigExtensionTest extends TestCase // Globals $calls = $container->getDefinition('twig')->getMethodCalls(); $this->assertEquals('app', $calls[0][1][0], '->load() registers services as Twig globals'); - $this->assertEquals(new Reference('templating.globals'), $calls[0][1][1]); + $this->assertEquals(new Reference('twig.app_variable'), $calls[0][1][1]); $this->assertEquals('foo', $calls[1][1][0], '->load() registers services as Twig globals'); $this->assertEquals(new Reference('bar'), $calls[1][1][1], '->load() registers services as Twig globals'); $this->assertEquals('baz', $calls[2][1][0], '->load() registers variables as Twig globals'); diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index b1e51bd726..7a141e0644 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -28,7 +28,7 @@ "symfony/config": "~2.2|~3.0.0", "symfony/routing": "~2.1|~3.0.0", "symfony/templating": "~2.1|~3.0.0", - "symfony/framework-bundle": "~2.1|~3.0.0" + "symfony/framework-bundle": "~2.7|~3.0.0" }, "autoload": { "psr-0": { "Symfony\\Bundle\\TwigBundle\\": "" } From 0d537c4e4d3da375f67b601f0ecc9f93c9ab4388 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 10 Jan 2015 11:34:10 +0100 Subject: [PATCH 2/3] decoupled Twig from the Templating system --- .../FrameworkExtension.php | 68 +++++++++++-------- .../Resources/config/old_assets.xml | 37 ++++++++++ .../Resources/config/templating.xml | 11 +++ .../Resources/config/templating_php.xml | 33 --------- .../Compiler/ExtensionPass.php | 19 ++++++ .../DependencyInjection/TwigExtension.php | 3 - .../TwigBundle/Resources/config/twig.xml | 7 +- 7 files changed, 110 insertions(+), 68 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/config/old_assets.xml diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index fdcd1be7fe..0adf3cb6f8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -458,7 +458,6 @@ class FrameworkExtension extends Extension private function registerTemplatingConfiguration(array $config, $ide, ContainerBuilder $container, XmlFileLoader $loader) { $loader->load('templating.xml'); - $loader->load('templating_php.xml'); $links = array( 'textmate' => 'txmt://open?url=file://%%f&line=%%l', @@ -468,12 +467,26 @@ class FrameworkExtension extends Extension ); $container->setParameter('templating.helper.code.file_link_format', isset($links[$ide]) ? $links[$ide] : $ide); - $container->setParameter('templating.helper.form.resources', $config['form']['resources']); $container->setParameter('fragment.renderer.hinclude.global_template', $config['hinclude_default_template']); - if ($container->getParameter('kernel.debug')) { - $loader->load('templating_debug.xml'); + $loader->load('old_assets.xml'); + // create package definitions and add them to the assets helper + $defaultPackage = $this->createTemplatingPackageDefinition($container, $config['assets_base_urls']['http'], $config['assets_base_urls']['ssl'], $config['assets_version'], $config['assets_version_format']); + $container->setDefinition('templating.asset.default_package', $defaultPackage); + $namedPackages = array(); + foreach ($config['packages'] as $name => $package) { + $namedPackage = $this->createTemplatingPackageDefinition($container, $package['base_urls']['http'], $package['base_urls']['ssl'], $package['version'], $package['version_format'], $name); + $container->setDefinition('templating.asset.package.'.$name, $namedPackage); + $namedPackages[$name] = new Reference('templating.asset.package.'.$name); + } + + $container->getDefinition('templating.helper.assets')->setArguments(array( + new Reference('templating.asset.default_package'), + $namedPackages, + )); + + if ($container->getParameter('kernel.debug')) { $logger = new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE); $container->getDefinition('templating.loader.cache') @@ -482,25 +495,8 @@ class FrameworkExtension extends Extension $container->getDefinition('templating.loader.chain') ->addTag('monolog.logger', array('channel' => 'templating')) ->addMethodCall('setLogger', array($logger)); - - $container->setDefinition('templating.engine.php', $container->findDefinition('debug.templating.engine.php')); - $container->setAlias('debug.templating.engine.php', 'templating.engine.php'); } - // create package definitions and add them to the assets helper - $defaultPackage = $this->createPackageDefinition($container, $config['assets_base_urls']['http'], $config['assets_base_urls']['ssl'], $config['assets_version'], $config['assets_version_format']); - $container->setDefinition('templating.asset.default_package', $defaultPackage); - $namedPackages = array(); - foreach ($config['packages'] as $name => $package) { - $namedPackage = $this->createPackageDefinition($container, $package['base_urls']['http'], $package['base_urls']['ssl'], $package['version'], $package['version_format'], $name); - $container->setDefinition('templating.asset.package.'.$name, $namedPackage); - $namedPackages[$name] = new Reference('templating.asset.package.'.$name); - } - $container->getDefinition('templating.helper.assets')->setArguments(array( - new Reference('templating.asset.default_package'), - $namedPackages, - )); - if (!empty($config['loaders'])) { $loaders = array_map(function ($loader) { return new Reference($loader); }, $config['loaders']); @@ -530,14 +526,6 @@ class FrameworkExtension extends Extension $container->findDefinition('templating.locator')->getClass(), )); - if (in_array('php', $config['engines'], true)) { - $this->addClassesToCompile(array( - 'Symfony\\Component\\Templating\\Storage\\FileStorage', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\PhpEngine', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\FilesystemLoader', - )); - } - $container->setParameter('templating.engines', $config['engines']); $engines = array_map(function ($engine) { return new Reference('templating.engine.'.$engine); }, $config['engines']); @@ -550,12 +538,32 @@ class FrameworkExtension extends Extension } $container->setAlias('templating', 'templating.engine.delegating'); } + + // configure the PHP engine if needed + if (in_array('php', $config['engines'], true)) { + $loader->load('templating_php.xml'); + + $container->setParameter('templating.helper.form.resources', $config['form']['resources']); + + if ($container->getParameter('kernel.debug')) { + $loader->load('templating_debug.xml'); + + $container->setDefinition('templating.engine.php', $container->findDefinition('debug.templating.engine.php')); + $container->setAlias('debug.templating.engine.php', 'templating.engine.php'); + } + + $this->addClassesToCompile(array( + 'Symfony\\Component\\Templating\\Storage\\FileStorage', + 'Symfony\\Bundle\\FrameworkBundle\\Templating\\PhpEngine', + 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\FilesystemLoader', + )); + } } /** * Returns a definition for an asset package. */ - private function createPackageDefinition(ContainerBuilder $container, array $httpUrls, array $sslUrls, $version, $format, $name = null) + private function createTemplatingPackageDefinition(ContainerBuilder $container, array $httpUrls, array $sslUrls, $version, $format, $name = null) { if (!$httpUrls) { $package = new DefinitionDecorator('templating.asset.path_package'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/old_assets.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/old_assets.xml new file mode 100644 index 0000000000..90d935906d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/old_assets.xml @@ -0,0 +1,37 @@ + + + + + + Symfony\Bundle\FrameworkBundle\Templating\Asset\PathPackage + Symfony\Component\Templating\Asset\UrlPackage + Symfony\Bundle\FrameworkBundle\Templating\Asset\PackageFactory + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml index 59da78fc41..efec555b87 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml @@ -14,6 +14,7 @@ Symfony\Component\Templating\Loader\CacheLoader Symfony\Component\Templating\Loader\ChainLoader Symfony\Bundle\FrameworkBundle\CacheWarmer\TemplateFinder + Symfony\Component\Templating\Helper\CoreAssetsHelper @@ -58,5 +59,15 @@ + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml index e0a9234d02..5b4f8073fe 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml @@ -7,7 +7,6 @@ Symfony\Bundle\FrameworkBundle\Templating\PhpEngine Symfony\Component\Templating\Helper\SlotsHelper - Symfony\Component\Templating\Helper\CoreAssetsHelper Symfony\Bundle\FrameworkBundle\Templating\Helper\ActionsHelper Symfony\Bundle\FrameworkBundle\Templating\Helper\RouterHelper Symfony\Bundle\FrameworkBundle\Templating\Helper\RequestHelper @@ -19,9 +18,6 @@ Symfony\Component\Form\Extension\Templating\TemplatingRendererEngine Symfony\Component\Form\FormRenderer Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables - Symfony\Bundle\FrameworkBundle\Templating\Asset\PathPackage - Symfony\Component\Templating\Asset\UrlPackage - Symfony\Bundle\FrameworkBundle\Templating\Asset\PackageFactory @@ -37,35 +33,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php index c4bd1d372a..953a9dd9dd 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -46,5 +46,24 @@ class ExtensionPass implements CompilerPassInterface if ($container->has('request_stack')) { $container->getDefinition('twig.extension.httpfoundation')->addTag('twig.extension'); } + + if ($container->hasParameter('templating.helper.code.file_link_format')) { + $container->getDefinition('twig.extension.code')->replaceArgument(0, $container->getParameter('templating.helper.code.file_link_format')); + } + + if ($container->has('templating')) { + $container->getDefinition('twig.cache_warmer')->addTag('kernel.cache_warmer'); + + if ($container->getParameter('kernel.debug')) { + $container->setDefinition('templating.engine.twig', $container->findDefinition('debug.templating.engine.twig')); + $container->setAlias('debug.templating.engine.twig', 'templating.engine.twig'); + } + } else { + $loader = $container->getDefinition('twig.loader.native_filesystem'); + $loader->addTag('twig.loader'); + $loader->setMethodCalls($container->getDefinition('twig.loader.filesystem')->getMethodCalls()); + + $container->setDefinition('twig.loader.filesystem', $loader); + } } } diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php index 9883daeded..c9595ff219 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -103,9 +103,6 @@ class TwigExtension extends Extension if ($container->getParameter('kernel.debug')) { $loader->load('debug.xml'); - - $container->setDefinition('templating.engine.twig', $container->findDefinition('debug.templating.engine.twig')); - $container->setAlias('debug.templating.engine.twig', 'templating.engine.twig'); } if (isset($config['autoescape_service']) && isset($config['autoescape_service_method'])) { diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index b0a7223287..41f9c2d361 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -47,11 +47,14 @@ - + + + + @@ -84,7 +87,7 @@ - %templating.helper.code.file_link_format% + %kernel.root_dir% %kernel.charset% From 18d4c4163c763bb8e0d19c8a190c3c6ef95e8412 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 10 Jan 2015 13:28:00 +0100 Subject: [PATCH 3/3] [TwigBundle] added some tests --- .../Functional/NoTemplatingEntryTest.php | 78 +++++++++++++++++++ .../Resources/views/index.html.twig | 1 + src/Symfony/Bundle/TwigBundle/composer.json | 2 +- 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Bundle/TwigBundle/Tests/Functional/NoTemplatingEntryTest.php create mode 100644 src/Symfony/Bundle/TwigBundle/Tests/Functional/Resources/views/index.html.twig diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Functional/NoTemplatingEntryTest.php b/src/Symfony/Bundle/TwigBundle/Tests/Functional/NoTemplatingEntryTest.php new file mode 100644 index 0000000000..068a10526c --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/Functional/NoTemplatingEntryTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\Tests; + +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\TwigBundle\TwigBundle; + +class NoTemplatingEntryTest extends \PHPUnit_Framework_TestCase +{ + public function test() + { + $kernel = new NoTemplatingEntryKernel('dev', true); + $kernel->boot(); + + $container = $kernel->getContainer(); + $content = $container->get('twig')->render('index.html.twig'); + $this->assertContains('{ a: b }', $content); + } + + protected function setUp() + { + $this->deleteTempDir(); + } + + protected function tearDown() + { + $this->deleteTempDir(); + } + + protected function deleteTempDir() + { + if (!file_exists($dir = sys_get_temp_dir().'/'.Kernel::VERSION.'/NoTemplatingEntryKernel')) { + return; + } + + $fs = new Filesystem(); + $fs->remove($dir); + } +} + +class NoTemplatingEntryKernel extends Kernel +{ + public function registerBundles() + { + return array(new FrameworkBundle(), new TwigBundle()); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + $loader->load(function ($container) { + $container->loadFromExtension('framework', array( + 'secret' => '$ecret', + )); + }); + } + + public function getCacheDir() + { + return sys_get_temp_dir().'/'.Kernel::VERSION.'/NoTemplatingEntryKernel/cache/'.$this->environment; + } + + public function getLogDir() + { + return sys_get_temp_dir().'/'.Kernel::VERSION.'/NoTemplatingEntryKernel/logs'; + } +} diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Functional/Resources/views/index.html.twig b/src/Symfony/Bundle/TwigBundle/Tests/Functional/Resources/views/index.html.twig new file mode 100644 index 0000000000..ddc4eb4404 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/Functional/Resources/views/index.html.twig @@ -0,0 +1 @@ +{{ {a: 'b'}|yaml_encode }} diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index 7a141e0644..6d5e4dbd75 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=5.3.9", - "symfony/twig-bridge": "~2.6|~3.0.0", + "symfony/twig-bridge": "~2.7|~3.0.0", "symfony/http-foundation": "~2.5|~3.0.0", "symfony/http-kernel": "~2.3.24|~2.5.9|~2.6,>=2.6.2|~3.0.0" },