From 87143b3dd3b22fa3b6026a1c0532d16bcd63244c Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 20 May 2010 19:11:09 +0200 Subject: [PATCH] [TwigBundle] added the Twig bundle (proof of concept) --- src/Symfony/Framework/TwigBundle/Bundle.php | 41 ++++++++ .../DependencyInjection/TwigExtension.php | 58 +++++++++++ .../Framework/TwigBundle/Environment.php | 33 +++++++ .../TwigBundle/Extension/Helpers.php | 52 ++++++++++ .../Framework/TwigBundle/Loader/Loader.php | 96 +++++++++++++++++++ .../Framework/TwigBundle/Node/HelperNode.php | 87 +++++++++++++++++ .../TwigBundle/Renderer/Renderer.php | 46 +++++++++ .../config/schema/dic/twig/twig-1.0.xsd | 18 ++++ .../TwigBundle/Resources/config/twig.xml | 44 +++++++++ .../TokenParser/RenderTokenParser.php | 49 ++++++++++ .../TokenParser/RouteTokenParser.php | 49 ++++++++++ .../TokenParser/StylesheetTokenParser.php | 49 ++++++++++ .../TokenParser/StylesheetsTokenParser.php | 38 ++++++++ 13 files changed, 660 insertions(+) create mode 100644 src/Symfony/Framework/TwigBundle/Bundle.php create mode 100644 src/Symfony/Framework/TwigBundle/DependencyInjection/TwigExtension.php create mode 100644 src/Symfony/Framework/TwigBundle/Environment.php create mode 100644 src/Symfony/Framework/TwigBundle/Extension/Helpers.php create mode 100644 src/Symfony/Framework/TwigBundle/Loader/Loader.php create mode 100644 src/Symfony/Framework/TwigBundle/Node/HelperNode.php create mode 100644 src/Symfony/Framework/TwigBundle/Renderer/Renderer.php create mode 100644 src/Symfony/Framework/TwigBundle/Resources/config/schema/dic/twig/twig-1.0.xsd create mode 100644 src/Symfony/Framework/TwigBundle/Resources/config/twig.xml create mode 100644 src/Symfony/Framework/TwigBundle/TokenParser/RenderTokenParser.php create mode 100644 src/Symfony/Framework/TwigBundle/TokenParser/RouteTokenParser.php create mode 100644 src/Symfony/Framework/TwigBundle/TokenParser/StylesheetTokenParser.php create mode 100644 src/Symfony/Framework/TwigBundle/TokenParser/StylesheetsTokenParser.php diff --git a/src/Symfony/Framework/TwigBundle/Bundle.php b/src/Symfony/Framework/TwigBundle/Bundle.php new file mode 100644 index 0000000000..c027290593 --- /dev/null +++ b/src/Symfony/Framework/TwigBundle/Bundle.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Bundle. + * + * @package Symfony + * @subpackage Framework_TwigBundle + * @author Fabien Potencier + */ +class Bundle extends BaseBundle +{ + /** + * Customizes the Container instance. + * + * @param Symfony\Components\DependencyInjection\ContainerInterface $container A ContainerInterface instance + * + * @return Symfony\Components\DependencyInjection\BuilderConfiguration A BuilderConfiguration instance + */ + public function buildContainer(ContainerInterface $container) + { + Loader::registerExtension(new TwigExtension()); + } +} diff --git a/src/Symfony/Framework/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Framework/TwigBundle/DependencyInjection/TwigExtension.php new file mode 100644 index 0000000000..19b7b483dc --- /dev/null +++ b/src/Symfony/Framework/TwigBundle/DependencyInjection/TwigExtension.php @@ -0,0 +1,58 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * TwigExtension. + * + * @package Symfony + * @subpackage Framework_TwigBundle + * @author Fabien Potencier + */ +class TwigExtension extends LoaderExtension +{ + public function configLoad($config) + { + $configuration = new BuilderConfiguration(); + + $loader = new XmlFileLoader(__DIR__.'/../Resources/config'); + $configuration->merge($loader->load('twig.xml')); + + $configuration->setParameter('twig_options', array_replace($configuration->getParameter('twig_options'), $config)); + + return $configuration; + } + + /** + * Returns the base path for the XSD files. + * + * @return string The XSD base path + */ + public function getXsdValidationBasePath() + { + return __DIR__.'/../Resources/config/'; + } + + public function getNamespace() + { + return 'http://www.symfony-project.org/schema/dic/twig'; + } + + public function getAlias() + { + return 'twig'; + } +} diff --git a/src/Symfony/Framework/TwigBundle/Environment.php b/src/Symfony/Framework/TwigBundle/Environment.php new file mode 100644 index 0000000000..505c61da3d --- /dev/null +++ b/src/Symfony/Framework/TwigBundle/Environment.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * TwigExtension. + * + * @package Symfony + * @subpackage Framework_TwigBundle + * @author Fabien Potencier + */ +class Environment extends \Twig_Environment +{ + public function __construct(ContainerInterface $container, \Twig_LoaderInterface $loader = null, $options = array()) + { + parent::__construct($loader, $options); + + foreach ($container->findAnnotatedServiceIds('twig.extension') as $id => $attributes) { + $this->addExtension($container->getService($id)); + } + } +} diff --git a/src/Symfony/Framework/TwigBundle/Extension/Helpers.php b/src/Symfony/Framework/TwigBundle/Extension/Helpers.php new file mode 100644 index 0000000000..e4c9cce8ff --- /dev/null +++ b/src/Symfony/Framework/TwigBundle/Extension/Helpers.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * + * @package Symfony + * @subpackage Framework_TwigBundle + * @author Fabien Potencier + */ +class Helpers extends \Twig_Extension +{ + /** + * Returns the token parser instance to add to the existing list. + * + * @return array An array of Twig_TokenParser instances + */ + public function getTokenParsers() + { + return array( + new StylesheetTokenParser(), + new StylesheetsTokenParser(), + new RouteTokenParser(), + new RenderTokenParser(), + ); + } + + /** + * Returns the name of the extension. + * + * @return string The extension name + */ + public function getName() + { + return 'symfony.helpers'; + } +} diff --git a/src/Symfony/Framework/TwigBundle/Loader/Loader.php b/src/Symfony/Framework/TwigBundle/Loader/Loader.php new file mode 100644 index 0000000000..5ecd9fcdfe --- /dev/null +++ b/src/Symfony/Framework/TwigBundle/Loader/Loader.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * + * @package Symfony + * @subpackage Framework_TwigBundle + * @author Fabien Potencier + */ +class Loader implements \Twig_LoaderInterface +{ + protected $engine; + + public function setEngine(Engine $engine) + { + $this->engine = $engine; + } + + /** + * Gets the source code of a template, given its name. + * + * @param string $name string The name of the template to load + * + * @return string The template source code + */ + public function getSource($name) + { + if ($name instanceof Storage) { + return $name->getContent(); + } + + list($name, $options) = $this->engine->splitTemplateName($name); + + $template = $this->engine->getLoader()->load($name, $options); + + if (false === $template) { + throw new \InvalidArgumentException(sprintf('The template "%s" does not exist (renderer: %s).', $name, $options['renderer'])); + } + + return $template->getContent(); + } + + /** + * Gets the cache key to use for the cache for a given template name. + * + * @param string $name string The name of the template to load + * + * @return string The cache key + */ + public function getCacheKey($name) + { + if ($name instanceof Storage) { + return (string) $name; + } + + list($name, $options) = $this->engine->splitTemplateName($name); + + return $name.'_'.serialize($options); + } + + /** + * Returns true if the template is still fresh. + * + * @param string $name The template name + * @param timestamp $time The last modification time of the cached template + */ + public function isFresh($name, $time) + { + if ($name instanceof Storage) { + if ($name instanceof FileStorage) + { + return filemtime((string) $name) < $time; + } + + return false; + } + + list($name, $options) = $this->engine->splitTemplateName($name); + + return $this->engine->getLoader()->isFresh($name, $options, $time); + } +} diff --git a/src/Symfony/Framework/TwigBundle/Node/HelperNode.php b/src/Symfony/Framework/TwigBundle/Node/HelperNode.php new file mode 100644 index 0000000000..f4486061c6 --- /dev/null +++ b/src/Symfony/Framework/TwigBundle/Node/HelperNode.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * + * @package Symfony + * @subpackage Framework_TwigBundle + * @author Fabien Potencier + */ +class HelperNode extends \Twig_Node implements \Twig_NodeListInterface +{ + protected $name; + protected $method; + protected $arguments; + protected $echo; + + public function __construct($name, $method, \Twig_NodeList $arguments, $echo, $lineno, $tag = null) + { + parent::__construct($lineno, $tag); + $this->name = $name; + $this->method = $method; + $this->arguments = $arguments; + $this->echo = $echo; + } + + public function __toString() + { + return get_class($this).'('.$this->arguments.')'; + } + + public function getNodes() + { + return array($this->arguments); + } + + public function setNodes(array $nodes) + { + $this->arguments = $nodes[0]; + } + + public function compile($compiler) + { + $compiler->addDebugInfo($this); + + if ($this->echo) { + $compiler->raw('echo '); + } + + $compiler->write('$context[\'_view\']->'.$this->name.'->'.$this->method.'('); + + $count = count($this->arguments->getNodes()); + foreach ($this->arguments->getNodes() as $i => $node) { + $compiler->subcompile($node); + + if ($i !== $count - 1) { + $compiler->raw(', '); + } + } + + $compiler->raw(");\n"); + } + + public function getName() + { + return $this->name; + } + + public function getMethod() + { + return $this->method; + } + + public function getArguments() + { + return $this->arguments; + } +} diff --git a/src/Symfony/Framework/TwigBundle/Renderer/Renderer.php b/src/Symfony/Framework/TwigBundle/Renderer/Renderer.php new file mode 100644 index 0000000000..17b26f8913 --- /dev/null +++ b/src/Symfony/Framework/TwigBundle/Renderer/Renderer.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * + * @package Symfony + * @subpackage Framework_TwigBundle + * @author Fabien Potencier + */ +class Renderer extends BaseRenderer +{ + protected $environment; + + public function __construct(\Twig_Environment $environment) + { + $this->environment = $environment; + } + + /** + * Evaluates a template. + * + * @param Storage $template The template to render + * @param array $parameters An array of parameters to pass to the template + * + * @return string|false The evaluated template, or false if the renderer is unable to render the template + */ + public function evaluate(Storage $template, array $parameters = array()) + { + $parameters['_view'] = $this->engine; + + return $this->environment->loadTemplate($template)->render($parameters); + } +} diff --git a/src/Symfony/Framework/TwigBundle/Resources/config/schema/dic/twig/twig-1.0.xsd b/src/Symfony/Framework/TwigBundle/Resources/config/schema/dic/twig/twig-1.0.xsd new file mode 100644 index 0000000000..68fe823855 --- /dev/null +++ b/src/Symfony/Framework/TwigBundle/Resources/config/schema/dic/twig/twig-1.0.xsd @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/src/Symfony/Framework/TwigBundle/Resources/config/twig.xml b/src/Symfony/Framework/TwigBundle/Resources/config/twig.xml new file mode 100644 index 0000000000..563b7d1259 --- /dev/null +++ b/src/Symfony/Framework/TwigBundle/Resources/config/twig.xml @@ -0,0 +1,44 @@ + + + + + + Symfony\Framework\TwigBundle\Environment + + %kernel.charset% + %kernel.debug% + %kernel.cache_dir%/twig + + Symfony\Framework\TwigBundle\Loader\Loader + Symfony\Framework\TwigBundle\Renderer\Renderer + + + + + + + %twig_options% + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Framework/TwigBundle/TokenParser/RenderTokenParser.php b/src/Symfony/Framework/TwigBundle/TokenParser/RenderTokenParser.php new file mode 100644 index 0000000000..31f34ccf27 --- /dev/null +++ b/src/Symfony/Framework/TwigBundle/TokenParser/RenderTokenParser.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Wrapper for the actions helper render() method. + * + * {% render 'BlogBundle:Post:list' with ['path': ['limit': 2], 'standalone': false, 'alt': 'BlogBundle:Post:error'] %} + * + * @package Symfony + * @subpackage Framework_TwigBundle + * @author Fabien Potencier + */ +class RenderTokenParser extends \Twig_TokenParser +{ + public function parse(\Twig_Token $token) + { + $nodes = array(); + + $lineno = $token->getLine(); + $nodes[] = $this->parser->getExpressionParser()->parseExpression(); + + if ($this->parser->getStream()->test(\Twig_Token::NAME_TYPE, 'with')) + { + $this->parser->getStream()->expect(\Twig_Token::NAME_TYPE, 'with'); + $nodes[] = $this->parser->getExpressionParser()->parseExpression(); + } + + $this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE); + + return new HelperNode('actions', 'render', new \Twig_NodeList($nodes), true, $lineno, $this->getTag()); + } + + public function getTag() + { + return 'render'; + } +} diff --git a/src/Symfony/Framework/TwigBundle/TokenParser/RouteTokenParser.php b/src/Symfony/Framework/TwigBundle/TokenParser/RouteTokenParser.php new file mode 100644 index 0000000000..9696446ee2 --- /dev/null +++ b/src/Symfony/Framework/TwigBundle/TokenParser/RouteTokenParser.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Wrapper for the route helper generate() method. + * + * {% route blog_post with ['id': post.id] %} + * + * @package Symfony + * @subpackage Framework_TwigBundle + * @author Fabien Potencier + */ +class RouteTokenParser extends \Twig_TokenParser +{ + public function parse(\Twig_Token $token) + { + $nodes = array(); + + $lineno = $token->getLine(); + $nodes[] = $this->parser->getExpressionParser()->parseExpression(); + + if ($this->parser->getStream()->test(\Twig_Token::NAME_TYPE, 'with')) + { + $this->parser->getStream()->expect(\Twig_Token::NAME_TYPE, 'with'); + $nodes[] = $this->parser->getExpressionParser()->parseExpression(); + } + + $this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE); + + return new HelperNode('router', 'generate', new \Twig_NodeList($nodes), true, $lineno, $this->getTag()); + } + + public function getTag() + { + return 'route'; + } +} diff --git a/src/Symfony/Framework/TwigBundle/TokenParser/StylesheetTokenParser.php b/src/Symfony/Framework/TwigBundle/TokenParser/StylesheetTokenParser.php new file mode 100644 index 0000000000..a3ae0891d4 --- /dev/null +++ b/src/Symfony/Framework/TwigBundle/TokenParser/StylesheetTokenParser.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Wrapper for the stylesheets helper add() method. + * + * {% stylesheet 'bundles/blog/css/blog.css' with ['media': 'screen'] %} + * + * @package Symfony + * @subpackage Framework_TwigBundle + * @author Fabien Potencier + */ +class StylesheetTokenParser extends \Twig_TokenParser +{ + public function parse(\Twig_Token $token) + { + $nodes = array(); + + $lineno = $token->getLine(); + $nodes[] = $this->parser->getExpressionParser()->parseExpression(); + + if ($this->parser->getStream()->test(\Twig_Token::NAME_TYPE, 'with')) + { + $this->parser->getStream()->expect(\Twig_Token::NAME_TYPE, 'with'); + $nodes[] = $this->parser->getExpressionParser()->parseExpression(); + } + + $this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE); + + return new HelperNode('stylesheets', 'add', new \Twig_NodeList($nodes), false, $lineno, $this->getTag()); + } + + public function getTag() + { + return 'stylesheet'; + } +} diff --git a/src/Symfony/Framework/TwigBundle/TokenParser/StylesheetsTokenParser.php b/src/Symfony/Framework/TwigBundle/TokenParser/StylesheetsTokenParser.php new file mode 100644 index 0000000000..24e0dec4c9 --- /dev/null +++ b/src/Symfony/Framework/TwigBundle/TokenParser/StylesheetsTokenParser.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Wrapper for the stylesheets helper output() method. + * + * {% stylesheets %} + * + * @package Symfony + * @subpackage Framework_TwigBundle + * @author Fabien Potencier + */ +class StylesheetsTokenParser extends \Twig_TokenParser +{ + public function parse(\Twig_Token $token) + { + $this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE); + + return new HelperNode('stylesheets', 'render', new \Twig_NodeList(array()), true, $token->getLine(), $this->getTag()); + } + + public function getTag() + { + return 'stylesheets'; + } +}