diff --git a/UPGRADE-2.1.md b/UPGRADE-2.1.md index b949f534f493e..8d44288fb9e2c 100644 --- a/UPGRADE-2.1.md +++ b/UPGRADE-2.1.md @@ -1053,6 +1053,21 @@ $registry->addType($registry->resolveType(new MyFormType())); ``` + * The method `renderBlock()` of the helper for the PHP Templating component was + deprecated and will be removed in Symfony 2.3. You should use `block()` instead. + + Before: + + ``` + renderBlock('widget_attributes') ?> + ``` + + After: + + ``` + block('widget_attributes') ?> + ``` + ### Validator * The methods `setMessage()`, `getMessageTemplate()` and diff --git a/src/Symfony/Bridge/Twig/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php index f77965f689f24..f07a8a486b7c3 100644 --- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php @@ -12,11 +12,9 @@ namespace Symfony\Bridge\Twig\Extension; use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; -use Symfony\Component\Form\FormView; -use Symfony\Component\Form\Exception\FormException; +use Symfony\Bridge\Twig\Form\TwigRendererInterface; +use Symfony\Component\Form\FormViewInterface; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; -use Symfony\Component\Form\Extension\Core\View\ChoiceView; -use Symfony\Component\Form\Util\FormUtil; /** * FormExtension extends Twig with form capabilities. @@ -26,21 +24,17 @@ */ class FormExtension extends \Twig_Extension { - protected $csrfProvider; - protected $resources; - protected $blocks; - protected $environment; - protected $themes; - protected $varStack; - protected $template; + /** + * This property is public so that it can be accessed directly from compiled + * templates without having to call a getter, which slightly decreases performance. + * + * @var \Symfony\Component\Form\FormRendererInterface + */ + public $renderer; - public function __construct(CsrfProviderInterface $csrfProvider = null, array $resources = array()) + public function __construct(TwigRendererInterface $renderer) { - $this->csrfProvider = $csrfProvider; - $this->themes = new \SplObjectStorage(); - $this->varStack = array(); - $this->blocks = new \SplObjectStorage(); - $this->resources = $resources; + $this->renderer = $renderer; } /** @@ -48,25 +42,11 @@ public function __construct(CsrfProviderInterface $csrfProvider = null, array $r */ public function initRuntime(\Twig_Environment $environment) { - $this->environment = $environment; - } - - /** - * Sets a theme for a given view. - * - * @param FormView $view A FormView instance - * @param array|string $resources An array of resource names|a resource name - */ - public function setTheme(FormView $view, $resources) - { - $this->themes->attach($view, (array) $resources); - $this->blocks = new \SplObjectStorage(); + $this->renderer->setEnvironment($environment); } /** - * Returns the token parser instance to add to the existing list. - * - * @return array An array of Twig_TokenParser instances + * {@inheritdoc} */ public function getTokenParsers() { @@ -76,305 +56,39 @@ public function getTokenParsers() ); } + /** + * {@inheritdoc} + */ public function getFunctions() { return array( - 'form_enctype' => new \Twig_Function_Method($this, 'renderEnctype', array('is_safe' => array('html'))), - 'form_widget' => new \Twig_Function_Method($this, 'renderWidget', array('is_safe' => array('html'))), - 'form_errors' => new \Twig_Function_Method($this, 'renderErrors', array('is_safe' => array('html'))), - 'form_label' => new \Twig_Function_Method($this, 'renderLabel', array('is_safe' => array('html'))), - 'form_row' => new \Twig_Function_Method($this, 'renderRow', array('is_safe' => array('html'))), - 'form_rest' => new \Twig_Function_Method($this, 'renderRest', array('is_safe' => array('html'))), - 'csrf_token' => new \Twig_Function_Method($this, 'getCsrfToken'), - '_form_is_choice_group' => new \Twig_Function_Method($this, 'isChoiceGroup', array('is_safe' => array('html'))), - '_form_is_choice_selected' => new \Twig_Function_Method($this, 'isChoiceSelected', array('is_safe' => array('html'))), + 'form_enctype' => new \Twig_Function_Method($this, 'renderer->renderEnctype', array('is_safe' => array('html'))), + 'form_widget' => new \Twig_Function_Method($this, 'renderer->renderWidget', array('is_safe' => array('html'))), + 'form_errors' => new \Twig_Function_Method($this, 'renderer->renderErrors', array('is_safe' => array('html'))), + 'form_label' => new \Twig_Function_Method($this, 'renderer->renderLabel', array('is_safe' => array('html'))), + 'form_row' => new \Twig_Function_Method($this, 'renderer->renderRow', array('is_safe' => array('html'))), + 'form_rest' => new \Twig_Function_Method($this, 'renderer->renderRest', array('is_safe' => array('html'))), + 'csrf_token' => new \Twig_Function_Method($this, 'renderer->renderCsrfToken'), + '_form_is_choice_group' => new \Twig_Function_Method($this, 'renderer->isChoiceGroup', array('is_safe' => array('html'))), + '_form_is_choice_selected' => new \Twig_Function_Method($this, 'renderer->isChoiceSelected', array('is_safe' => array('html'))), ); } + /** + * {@inheritdoc} + */ public function getFilters() { return array( - 'humanize' => new \Twig_Filter_Function(__NAMESPACE__.'\humanize'), + 'humanize' => new \Twig_Filter_Method($this, 'renderer->humanize'), ); } - public function isChoiceGroup($label) - { - return FormUtil::isChoiceGroup($label); - } - - public function isChoiceSelected(FormView $view, ChoiceView $choice) - { - return FormUtil::isChoiceSelected($choice->getValue(), $view->getVar('value')); - } - - /** - * Renders the HTML enctype in the form tag, if necessary - * - * Example usage in Twig templates: - * - *
- * - * @param FormView $view The view for which to render the encoding type - * - * @return string The html markup - */ - public function renderEnctype(FormView $view) - { - return $this->render($view, 'enctype'); - } - /** - * Renders a row for the view. - * - * @param FormView $view The view to render as a row - * @param array $variables An array of variables - * - * @return string The html markup - */ - public function renderRow(FormView $view, array $variables = array()) - { - return $this->render($view, 'row', $variables); - } - - /** - * Renders views which have not already been rendered. - * - * @param FormView $view The parent view - * @param array $variables An array of variables - * - * @return string The html markup - */ - public function renderRest(FormView $view, array $variables = array()) - { - return $this->render($view, 'rest', $variables); - } - - /** - * Renders the HTML for a given view - * - * Example usage in Twig: - * - * {{ form_widget(view) }} - * - * You can pass options during the call: - * - * {{ form_widget(view, {'attr': {'class': 'foo'}}) }} - * - * {{ form_widget(view, {'separator': '+++++'}) }} - * - * @param FormView $view The view to render - * @param array $variables Additional variables passed to the template - * - * @return string The html markup - */ - public function renderWidget(FormView $view, array $variables = array()) - { - return $this->render($view, 'widget', $variables); - } - - /** - * Renders the errors of the given view - * - * @param FormView $view The view to render the errors for - * - * @return string The html markup - */ - public function renderErrors(FormView $view) - { - return $this->render($view, 'errors'); - } - - /** - * Renders the label of the given view - * - * @param FormView $view The view to render the label for - * @param string $label Label name - * @param array $variables Additional variables passed to the template - * - * @return string The html markup - */ - public function renderLabel(FormView $view, $label = null, array $variables = array()) - { - if ($label !== null) { - $variables += array('label' => $label); - } - - return $this->render($view, 'label', $variables); - } - - /** - * Renders a template. - * - * 1. This function first looks for a block named "__
", - * 2. if such a block is not found the function will look for a block named - * "_
", - * 3. the type name is recursively replaced by the parent type name until a - * corresponding block is found - * - * @param FormView $view The form view - * @param string $section The section to render (i.e. 'row', 'widget', 'label', ...) - * @param array $variables Additional variables - * - * @return string The html markup - * - * @throws FormException if no template block exists to render the given section of the view - */ - protected function render(FormView $view, $section, array $variables = array()) - { - $mainTemplate = in_array($section, array('widget', 'row')); - - if ($mainTemplate && $view->isRendered()) { - return ''; - } - - if (null === $this->template) { - $this->template = reset($this->resources); - if (!$this->template instanceof \Twig_Template) { - $this->template = $this->environment->loadTemplate($this->template); - } - } - - $custom = '_'.$view->getVar('id'); - $rendering = $custom.$section; - $blocks = $this->getBlocks($view); - - if (isset($this->varStack[$rendering])) { - $typeIndex = $this->varStack[$rendering]['typeIndex'] - 1; - $types = $this->varStack[$rendering]['types']; - $this->varStack[$rendering]['variables'] = array_replace_recursive($this->varStack[$rendering]['variables'], $variables); - } else { - $types = $view->getVar('types'); - $types[] = $view->getVar('full_block_name'); - $typeIndex = count($types) - 1; - $this->varStack[$rendering] = array( - 'variables' => array_replace_recursive($view->getVars(), $variables), - 'types' => $types, - ); - } - - do { - $types[$typeIndex] .= '_'.$section; - - if (isset($blocks[$types[$typeIndex]])) { - $this->varStack[$rendering]['typeIndex'] = $typeIndex; - - $context = $this->environment->mergeGlobals($this->varStack[$rendering]['variables']); - - // we do not call renderBlock here to avoid too many nested level calls (XDebug limits the level to 100 by default) - ob_start(); - $this->template->displayBlock($types[$typeIndex], $context, $blocks); - $html = ob_get_clean(); - - if ($mainTemplate) { - $view->setRendered(); - } - - unset($this->varStack[$rendering]); - - return $html; - } - } while (--$typeIndex >= 0); - - throw new FormException(sprintf( - 'Unable to render the form as none of the following blocks exist: "%s".', - implode('", "', array_reverse($types)) - )); - } - - /** - * Returns a CSRF token. - * - * Use this helper for CSRF protection without the overhead of creating a - * form. - * - * - * - * - * - * Check the token in your action using the same intention. - * - * - * $csrfProvider = $this->get('form.csrf_provider'); - * if (!$csrfProvider->isCsrfTokenValid('rm_user_'.$user->getId(), $token)) { - * throw new \RuntimeException('CSRF attack detected.'); - * } - * - * - * @param string $intention The intention of the protected action - * - * @return string A CSRF token - */ - public function getCsrfToken($intention) - { - if (!$this->csrfProvider instanceof CsrfProviderInterface) { - throw new \BadMethodCallException('CSRF token can only be generated if a CsrfProviderInterface is injected in the constructor.'); - } - - return $this->csrfProvider->generateCsrfToken($intention); - } - - /** - * Returns the name of the extension. - * - * @return string The extension name + * {@inheritdoc} */ public function getName() { return 'form'; } - - /** - * Returns the blocks used to render the view. - * - * Templates are looked for in the resources in the following order: - * * resources from the themes (and its parents) - * * resources from the themes of parent views (up to the root view) - * * default resources - * - * @param FormView $view The view - * - * @return array An array of Twig_TemplateInterface instances - */ - protected function getBlocks(FormView $view) - { - if (!$this->blocks->contains($view)) { - $rootView = !$view->hasParent(); - - $templates = $rootView ? $this->resources : array(); - - if (isset($this->themes[$view])) { - $templates = array_merge($templates, $this->themes[$view]); - } - - $blocks = array(); - - foreach ($templates as $template) { - if (!$template instanceof \Twig_Template) { - $template = $this->environment->loadTemplate($template); - } - - $templateBlocks = array(); - do { - $templateBlocks = array_merge($template->getBlocks(), $templateBlocks); - } while (false !== $template = $template->getParent(array())); - $blocks = array_merge($blocks, $templateBlocks); - } - - if (!$rootView) { - $blocks = array_merge($this->getBlocks($view->getParent()), $blocks); - } - - $this->blocks->attach($view, $blocks); - } else { - $blocks = $this->blocks[$view]; - } - - return $blocks; - } -} - -function humanize($text) -{ - return ucfirst(trim(strtolower(preg_replace('/[_\s]+/', ' ', $text)))); } diff --git a/src/Symfony/Bridge/Twig/Form/TwigRenderer.php b/src/Symfony/Bridge/Twig/Form/TwigRenderer.php new file mode 100644 index 0000000000000..72798d103fa1f --- /dev/null +++ b/src/Symfony/Bridge/Twig/Form/TwigRenderer.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Form; + +use Symfony\Component\Form\FormRenderer; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; + +/** + * @author Bernhard Schussek + */ +class TwigRenderer extends FormRenderer implements TwigRendererInterface +{ + /** + * @var TwigRendererEngineInterface + */ + private $engine; + + public function __construct(TwigRendererEngineInterface $engine, CsrfProviderInterface $csrfProvider = null) + { + parent::__construct($engine, $csrfProvider); + + $this->engine = $engine; + } + + /** + * {@inheritdoc} + */ + public function setEnvironment(\Twig_Environment $environment) + { + $this->engine->setEnvironment($environment); + } +} diff --git a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php new file mode 100644 index 0000000000000..22d3688f4520d --- /dev/null +++ b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Form; + +use Symfony\Component\Form\AbstractRendererEngine; +use Symfony\Component\Form\FormViewInterface; + +/** + * @author Bernhard Schussek + */ +class TwigRendererEngine extends AbstractRendererEngine implements TwigRendererEngineInterface +{ + /** + * @var \Twig_Environment + */ + private $environment; + + /** + * @var \Twig_Template + */ + private $template; + + /** + * {@inheritdoc} + */ + public function setEnvironment(\Twig_Environment $environment) + { + $this->environment = $environment; + } + + /** + * {@inheritdoc} + */ + public function renderBlock(FormViewInterface $view, $resource, $block, array $variables = array()) + { + $cacheKey = $view->getVar(self::CACHE_KEY_VAR); + + $context = $this->environment->mergeGlobals($variables); + + ob_start(); + + // By contract,This method can only be called after getting the resource + // (which is passed to the method). Getting a resource for the first time + // (with an empty cache) is guaranteed to invoke loadResourcesFromTheme(), + // where the property $template is initialized. + + // We do not call renderBlock here to avoid too many nested level calls + // (XDebug limits the level to 100 by default) + $this->template->displayBlock($block, $context, $this->resources[$cacheKey]); + + return ob_get_clean(); + } + + /** + * Loads the cache with the resource for a given block name. + * + * This implementation eagerly loads all blocks of the themes assigned to the given view + * and all of its ancestors views. This is necessary, because Twig receives the + * list of blocks later. At that point, all blocks must already be loaded, for the + * case that the function "block()" is used in the Twig template. + * + * @see getResourceForBlock() + * + * @param string $cacheKey The cache key of the form view. + * @param FormViewInterface $view The form view for finding the applying themes. + * @param string $block The name of the block to load. + * + * @return Boolean True if the resource could be loaded, false otherwise. + */ + protected function loadResourceForBlock($cacheKey, FormViewInterface $view, $block) + { + // The caller guarantees that $this->resources[$cacheKey][$block] is + // not set, but it doesn't have to check whether $this->resources[$cacheKey] + // is set. If $this->resources[$cacheKey] is set, all themes for this + // $cacheKey are already loaded (due to the eager population, see doc comment). + if (isset($this->resources[$cacheKey])) { + // As said in the previous, the caller guarantees that + // $this->resources[$cacheKey][$block] is not set. Since the themes are + // already loaded, it can only be a non-existing block. + $this->resources[$cacheKey][$block] = false; + + return false; + } + + // Recursively try to find the block in the themes assigned to $view, + // then of its parent view, then of the parent view of the parent and so on. + // When the root view is reached in this recursion, also the default + // themes are taken into account. + + // Check each theme whether it contains the searched block + if (isset($this->themes[$cacheKey])) { + for ($i = count($this->themes[$cacheKey]) - 1; $i >= 0; --$i) { + $this->loadResourcesFromTheme($cacheKey, $this->themes[$cacheKey][$i]); + // CONTINUE LOADING (see doc comment) + } + } + + // Check the default themes once we reach the root view without success + if (!$view->hasParent()) { + for ($i = count($this->defaultThemes) - 1; $i >= 0; --$i) { + $this->loadResourcesFromTheme($cacheKey, $this->defaultThemes[$i]); + // CONTINUE LOADING (see doc comment) + } + } + + // Proceed with the themes of the parent view + if ($view->hasParent()) { + $parentCacheKey = $view->getParent()->getVar(self::CACHE_KEY_VAR); + + if (!isset($this->resources[$parentCacheKey])) { + $this->loadResourceForBlock($parentCacheKey, $view->getParent(), $block); + } + + // EAGER CACHE POPULATION (see doc comment) + foreach ($this->resources[$parentCacheKey] as $blockName => $resource) { + if (!isset($this->resources[$cacheKey][$blockName])) { + $this->resources[$cacheKey][$blockName] = $resource; + } + } + } + + // Even though we loaded the themes, it can happen that none of them + // contains the searched block + if (!isset($this->resources[$cacheKey][$block])) { + // Cache that we didn't find anything to speed up further accesses + $this->resources[$cacheKey][$block] = false; + } + + return false !== $this->resources[$cacheKey][$block]; + } + + /** + * Loads the resources for all blocks in a theme. + * + * @param string $cacheKey The cache key for storing the resource. + * @param mixed $theme The theme to load the block from. This parameter + * is passed by reference, because it might be necessary + * to initialize the theme first. Any changes made to + * this variable will be kept and be available upon + * further calls to this method using the same theme. + */ + protected function loadResourcesFromTheme($cacheKey, &$theme) + { + if (!$theme instanceof \Twig_Template) { + /* @var \Twig_Template $theme */ + $theme = $this->environment->loadTemplate($theme); + } + + if (null === $this->template) { + // Store the first \Twig_Template instance that we find so that + // we can call displayBlock() later on. It doesn't matter *which* + // template we use for that, since we pass the used blocks manually + // anyway. + $this->template = $theme; + } + + // Use a separate variable for the inheritance traversal, because + // theme is a reference and we don't want to change it. + $currentTheme = $theme; + + // The do loop takes care of template inheritance. + // Add blocks from all templates in the inheritance tree, but avoid + // overriding blocks already set. + do { + foreach ($currentTheme->getBlocks() as $block => $blockData) { + if (!isset($this->resources[$cacheKey][$block])) { + // The resource given back is the key to the bucket that + // contains this block. + $this->resources[$cacheKey][$block] = $blockData; + } + } + } while (false !== $currentTheme = $currentTheme->getParent(array())); + } +} diff --git a/src/Symfony/Bridge/Twig/Form/TwigRendererEngineInterface.php b/src/Symfony/Bridge/Twig/Form/TwigRendererEngineInterface.php new file mode 100644 index 0000000000000..ef764a248f339 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Form/TwigRendererEngineInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Form; + +use Symfony\Component\Form\FormRendererEngineInterface; + +/** + * @author Bernhard Schussek + */ +interface TwigRendererEngineInterface extends FormRendererEngineInterface +{ + /** + * Sets Twig's environment. + * + * @param \Twig_Environment $environment + */ + public function setEnvironment(\Twig_Environment $environment); +} diff --git a/src/Symfony/Bridge/Twig/Form/TwigRendererInterface.php b/src/Symfony/Bridge/Twig/Form/TwigRendererInterface.php new file mode 100644 index 0000000000000..4682f520008ac --- /dev/null +++ b/src/Symfony/Bridge/Twig/Form/TwigRendererInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Form; + +use Symfony\Component\Form\FormRendererInterface; + +/** + * @author Bernhard Schussek + */ +interface TwigRendererInterface extends FormRendererInterface +{ + /** + * Sets Twig's environment. + * + * @param \Twig_Environment $environment + */ + public function setEnvironment(\Twig_Environment $environment); +} diff --git a/src/Symfony/Bridge/Twig/Node/FormThemeNode.php b/src/Symfony/Bridge/Twig/Node/FormThemeNode.php index c6fe132113c11..3528a2dd214ee 100644 --- a/src/Symfony/Bridge/Twig/Node/FormThemeNode.php +++ b/src/Symfony/Bridge/Twig/Node/FormThemeNode.php @@ -30,7 +30,7 @@ public function compile(\Twig_Compiler $compiler) { $compiler ->addDebugInfo($this) - ->write('echo $this->env->getExtension(\'form\')->setTheme(') + ->write('echo $this->env->getExtension(\'form\')->renderer->setTheme(') ->subcompile($this->getNode('form')) ->raw(', ') ->subcompile($this->getNode('resources')) diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php index c2f8ed19e4a32..f057a515cc6dc 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php @@ -12,6 +12,8 @@ namespace Symfony\Bridge\Twig\Tests\Extension; use Symfony\Bridge\Twig\Extension\FormExtension; +use Symfony\Bridge\Twig\Form\TwigRenderer; +use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader; @@ -20,6 +22,9 @@ class FormExtensionDivLayoutTest extends AbstractDivLayoutTest { + /** + * @var FormExtension + */ protected $extension; protected function setUp() @@ -42,20 +47,23 @@ protected function setUp() parent::setUp(); - $loader = new StubFilesystemLoader(array( - __DIR__.'/../../../../../../src/Symfony/Bridge/Twig/Resources/views/Form', - __DIR__, - )); - - $this->extension = new FormExtension($this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'), array( + $rendererEngine = new TwigRendererEngine(array( 'form_div_layout.html.twig', 'custom_widgets.html.twig', )); + $renderer = new TwigRenderer($rendererEngine, $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface')); + + $this->extension = new FormExtension($renderer); + + $loader = new StubFilesystemLoader(array( + __DIR__.'/../../Resources/views/Form', + __DIR__, + )); $environment = new \Twig_Environment($loader, array('strict_variables' => true)); - $environment->addExtension($this->extension); $environment->addExtension(new TranslationExtension(new StubTranslator())); $environment->addGlobal('global', ''); + $environment->addExtension($this->extension); $this->extension->initRuntime($environment); } @@ -99,37 +107,37 @@ public function testThemeBlockInheritanceUsingExtend() protected function renderEnctype(FormView $view) { - return (string) $this->extension->renderEnctype($view); + return (string) $this->extension->renderer->renderEnctype($view); } protected function renderLabel(FormView $view, $label = null, array $vars = array()) { - return (string) $this->extension->renderLabel($view, $label, $vars); + return (string) $this->extension->renderer->renderLabel($view, $label, $vars); } protected function renderErrors(FormView $view) { - return (string) $this->extension->renderErrors($view); + return (string) $this->extension->renderer->renderErrors($view); } protected function renderWidget(FormView $view, array $vars = array()) { - return (string) $this->extension->renderWidget($view, $vars); + return (string) $this->extension->renderer->renderWidget($view, $vars); } protected function renderRow(FormView $view, array $vars = array()) { - return (string) $this->extension->renderRow($view, $vars); + return (string) $this->extension->renderer->renderRow($view, $vars); } protected function renderRest(FormView $view, array $vars = array()) { - return (string) $this->extension->renderRest($view, $vars); + return (string) $this->extension->renderer->renderRest($view, $vars); } protected function setTheme(FormView $view, array $themes) { - $this->extension->setTheme($view, $themes); + $this->extension->renderer->setTheme($view, $themes); } public static function themeBlockInheritanceProvider() diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php index dc979b9fed55f..effca091adc22 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php @@ -12,6 +12,8 @@ namespace Symfony\Bridge\Twig\Tests\Extension; use Symfony\Component\Form\FormView; +use Symfony\Bridge\Twig\Form\TwigRenderer; +use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bridge\Twig\Extension\FormExtension; use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Component\Form\Tests\AbstractTableLayoutTest; @@ -20,6 +22,9 @@ class FormExtensionTableLayoutTest extends AbstractTableLayoutTest { + /** + * @var FormExtension + */ protected $extension; protected function setUp() @@ -42,20 +47,23 @@ protected function setUp() parent::setUp(); - $loader = new StubFilesystemLoader(array( - __DIR__.'/../../../../../../src/Symfony/Bridge/Twig/Resources/views/Form', - __DIR__, - )); - - $this->extension = new FormExtension($this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'), array( + $rendererEngine = new TwigRendererEngine(array( 'form_table_layout.html.twig', 'custom_widgets.html.twig', )); + $renderer = new TwigRenderer($rendererEngine, $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface')); + + $this->extension = new FormExtension($renderer); + + $loader = new StubFilesystemLoader(array( + __DIR__.'/../../Resources/views/Form', + __DIR__, + )); $environment = new \Twig_Environment($loader, array('strict_variables' => true)); - $environment->addExtension($this->extension); $environment->addExtension(new TranslationExtension(new StubTranslator())); $environment->addGlobal('global', ''); + $environment->addExtension($this->extension); $this->extension->initRuntime($environment); } @@ -69,36 +77,36 @@ protected function tearDown() protected function renderEnctype(FormView $view) { - return (string) $this->extension->renderEnctype($view); + return (string) $this->extension->renderer->renderEnctype($view); } protected function renderLabel(FormView $view, $label = null, array $vars = array()) { - return (string) $this->extension->renderLabel($view, $label, $vars); + return (string) $this->extension->renderer->renderLabel($view, $label, $vars); } protected function renderErrors(FormView $view) { - return (string) $this->extension->renderErrors($view); + return (string) $this->extension->renderer->renderErrors($view); } protected function renderWidget(FormView $view, array $vars = array()) { - return (string) $this->extension->renderWidget($view, $vars); + return (string) $this->extension->renderer->renderWidget($view, $vars); } protected function renderRow(FormView $view, array $vars = array()) { - return (string) $this->extension->renderRow($view, $vars); + return (string) $this->extension->renderer->renderRow($view, $vars); } protected function renderRest(FormView $view, array $vars = array()) { - return (string) $this->extension->renderRest($view, $vars); + return (string) $this->extension->renderer->renderRest($view, $vars); } protected function setTheme(FormView $view, array $themes) { - $this->extension->setTheme($view, $themes); + $this->extension->renderer->setTheme($view, $themes); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml index 3d133603467bb..ea1795455f80b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml @@ -15,6 +15,8 @@ Symfony\Bundle\FrameworkBundle\Templating\Helper\CodeHelper Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper + 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 @@ -96,11 +98,19 @@ + + + + - %templating.helper.form.resources% + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/attributes.html.php index 632b311cdf896..621cf9c8711bf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/attributes.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/attributes.html.php @@ -1 +1 @@ -renderBlock('widget_attributes') ?> +block('widget_attributes') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/checkbox_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/checkbox_widget.html.php index fff427db11119..2efc537192b31 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/checkbox_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/checkbox_widget.html.php @@ -1,5 +1,5 @@ renderBlock('widget_attributes') ?> + block('widget_attributes') ?> value="escape($value) ?>" checked="checked" /> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_options.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_options.html.php index eae0d92827c5c..9beb0ad9c6505 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_options.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_options.html.php @@ -1 +1 @@ -renderBlock('choice_widget_options') ?> +block('choice_widget_options') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget.html.php index 99db4acfae15b..66e23ef6f8b6d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget.html.php @@ -1,5 +1,5 @@ -renderBlock('choice_widget_expanded') ?> +block('choice_widget_expanded') ?> -renderBlock('choice_widget_collapsed') ?> +block('choice_widget_collapsed') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_collapsed.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_collapsed.html.php index b0c601ea8f9a4..dd3655b733aba 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_collapsed.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_collapsed.html.php @@ -1,13 +1,13 @@ diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_expanded.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_expanded.html.php index 58de38d646988..b4b9a0f6d3469 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_expanded.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_expanded.html.php @@ -1,4 +1,4 @@ -
renderBlock('widget_container_attributes') ?>> +
block('widget_container_attributes') ?>> widget($child) ?> label($child) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/container_attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/container_attributes.html.php index 3edfa44eef897..588028ba74c0e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/container_attributes.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/container_attributes.html.php @@ -1 +1 @@ -renderBlock('widget_container_attributes') ?> +block('widget_container_attributes') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/date_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/date_widget.html.php index 439443c7b0af1..7e087bee067c2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/date_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/date_widget.html.php @@ -1,7 +1,7 @@ - renderBlock('form_widget_simple'); ?> + block('form_widget_simple'); ?> -
renderBlock('widget_container_attributes') ?>> +
block('widget_container_attributes') ?>> widget($form['year']), $view['form']->widget($form['month']), diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/datetime_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/datetime_widget.html.php index 6864874db0d3e..3685c08bfcf73 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/datetime_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/datetime_widget.html.php @@ -1,7 +1,7 @@ - renderBlock('form_widget_simple'); ?> + block('form_widget_simple'); ?> -
renderBlock('widget_container_attributes') ?>> +
block('widget_container_attributes') ?>> widget($form['date']).' '.$view['form']->widget($form['time']) ?>
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/email_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/email_widget.html.php index 0ef4d9dcdeefe..782de8c421e26 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/email_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/email_widget.html.php @@ -1 +1 @@ -renderBlock('form_widget_simple', array('type' => isset($type) ? $type : 'email')) ?> +block('form_widget_simple', array('type' => isset($type) ? $type : 'email')) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_enctype.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_enctype.html.php index baab129372768..1f56ba0e7a7b5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_enctype.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_enctype.html.php @@ -1 +1 @@ -renderBlock('form_enctype') ?> +block('form_enctype') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_errors.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_errors.html.php index d4cbe1b721ed5..4542230327041 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_errors.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_errors.html.php @@ -1 +1 @@ -renderBlock('form_errors') ?> +block('form_errors') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_label.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_label.html.php index 71d055cf6140b..bd9fd27b4f402 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_label.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_label.html.php @@ -1 +1 @@ -renderBlock('form_label') ?> +block('form_label') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rest.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rest.html.php index fe9bae0c5587e..8341168aca2d4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rest.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rest.html.php @@ -1 +1 @@ -renderBlock('form_rest') ?> +block('form_rest') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_row.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_row.html.php index 8077ff3162749..10c495266d8e7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_row.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_row.html.php @@ -1 +1 @@ -renderBlock('form_row') ?> +block('form_row') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rows.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rows.html.php index b9a07bc4666bc..62b69ab4115ba 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rows.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rows.html.php @@ -1 +1 @@ -renderBlock('form_rows') ?> +block('form_rows') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_widget.html.php index 2621130b142de..627555cff6fb2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_widget.html.php @@ -1 +1 @@ -renderBlock('form_widget_simple') ?> +block('form_widget_simple') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget.html.php index 0e634184e3751..4695c1cc59c62 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget.html.php @@ -1,5 +1,5 @@ -renderBlock('form_widget_compound')?> +block('form_widget_compound')?> -renderBlock('form_widget_simple')?> +block('form_widget_simple')?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_compound.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_compound.html.php index 9321a69fa106d..6124d2f35946c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_compound.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_compound.html.php @@ -1,4 +1,4 @@ -
renderBlock('widget_container_attributes') ?>> +
block('widget_container_attributes') ?>> hasParent() && $errors): ?> @@ -6,6 +6,6 @@ - renderBlock('form_rows') ?> + block('form_rows') ?> rest($form) ?>
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_simple.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_simple.html.php index 29649cfbd6065..203f7a4266266 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_simple.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_simple.html.php @@ -1,5 +1,5 @@ value="escape($value) ?>" - renderBlock('widget_attributes') ?> + block('widget_attributes') ?> /> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/hidden_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/hidden_widget.html.php index 7dc71ae228c6b..120ebd1c5fa7a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/hidden_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/hidden_widget.html.php @@ -1 +1 @@ -renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "hidden")) ?> +block('form_widget_simple', array('type' => isset($type) ? $type : "hidden")) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/integer_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/integer_widget.html.php index 14000a5ee53b9..aa34a0f42d32c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/integer_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/integer_widget.html.php @@ -1 +1 @@ -renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "number")) ?> +block('form_widget_simple', array('type' => isset($type) ? $type : "number")) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/money_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/money_widget.html.php index 6c84a871aa0ea..f829637b99264 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/money_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/money_widget.html.php @@ -1 +1 @@ -renderBlock('form_widget_simple'), $money_pattern) ?> +block('form_widget_simple'), $money_pattern) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/number_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/number_widget.html.php index c3344d8bd72de..6fa4aa4220606 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/number_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/number_widget.html.php @@ -1 +1 @@ -renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "text")) ?> +block('form_widget_simple', array('type' => isset($type) ? $type : "text")) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/password_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/password_widget.html.php index ab0bcafae5aec..463b39e55dd8c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/password_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/password_widget.html.php @@ -1 +1 @@ -renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "password")) ?> +block('form_widget_simple', array('type' => isset($type) ? $type : "password")) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/percent_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/percent_widget.html.php index 38b51c091d737..36bc2a11d5b40 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/percent_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/percent_widget.html.php @@ -1 +1 @@ -renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "text")) ?> % +block('form_widget_simple', array('type' => isset($type) ? $type : "text")) ?> % diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/radio_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/radio_widget.html.php index 3ecad14ca2ec8..024ca8c278976 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/radio_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/radio_widget.html.php @@ -1,5 +1,5 @@ renderBlock('widget_attributes') ?> + block('widget_attributes') ?> value="escape($value) ?>" checked="checked" /> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/repeated_row.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/repeated_row.html.php index b9a07bc4666bc..62b69ab4115ba 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/repeated_row.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/repeated_row.html.php @@ -1 +1 @@ -renderBlock('form_rows') ?> +block('form_rows') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/search_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/search_widget.html.php index e071e95ec09fe..04cd5a571ede0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/search_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/search_widget.html.php @@ -1 +1 @@ -renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "search")) ?> +block('form_widget_simple', array('type' => isset($type) ? $type : "search")) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/textarea_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/textarea_widget.html.php index a82744b8e0684..5973706b3da61 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/textarea_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/textarea_widget.html.php @@ -1 +1 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/time_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/time_widget.html.php index fb7f94c3eb8c2..9483561d0f8e4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/time_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/time_widget.html.php @@ -1,7 +1,7 @@ - renderBlock('form_widget_simple'); ?> + block('form_widget_simple'); ?> -
renderBlock('widget_container_attributes') ?>> +
block('widget_container_attributes') ?>> renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "url")) ?> +block('form_widget_simple', array('type' => isset($type) ? $type : "url")) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget_compound.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget_compound.html.php index b022830603059..d4d0d08ee4c6a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget_compound.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/form_widget_compound.html.php @@ -1,7 +1,7 @@ -renderBlock('widget_container_attributes') ?>> +
block('widget_container_attributes') ?>> hasParent()): ?> errors($form) ?> - renderBlock('form_rows') ?> + block('form_rows') ?> rest($form) ?>
diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php index a2b5afc1d5a40..b82f6efb11129 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php @@ -12,8 +12,9 @@ namespace Symfony\Bundle\FrameworkBundle\Templating\Helper; use Symfony\Component\Templating\Helper\Helper; +use Symfony\Component\Form\FormRendererInterface; +use Symfony\Component\Form\FormViewInterface; use Symfony\Component\Templating\EngineInterface; -use Symfony\Component\Form\FormView; use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\Extension\Core\View\ChoiceView; @@ -27,46 +28,35 @@ */ class FormHelper extends Helper { - protected $engine; - - protected $csrfProvider; - - protected $varStack; - - protected $context; - - protected $resources; - - protected $themes; + /** + * @var FormRendererInterface + */ + private $renderer; - protected $templates; + /** + * @param FormRendererInterface $renderer + */ + public function __construct(FormRendererInterface $renderer) + { + $this->renderer = $renderer; + } /** - * Constructor. - * - * @param EngineInterface $engine The templating engine - * @param CsrfProviderInterface $csrfProvider The CSRF provider - * @param array $resources An array of theme names + * {@inheritdoc} */ - public function __construct(EngineInterface $engine, CsrfProviderInterface $csrfProvider = null, array $resources = array()) + public function getName() { - $this->engine = $engine; - $this->csrfProvider = $csrfProvider; - $this->resources = $resources; - $this->varStack = array(); - $this->context = array(); - $this->templates = array(); - $this->themes = array(); + return 'form'; } public function isChoiceGroup($label) { - return FormUtil::isChoiceGroup($label); + return $this->renderer->isChoiceGroup($label); } - public function isChoiceSelected(FormView $view, ChoiceView $choice) + public function isChoiceSelected(FormViewInterface $view, ChoiceView $choice) { - return FormUtil::isChoiceSelected($choice->getValue(), $view->getVar('value')); + return $this->renderer->isChoiceSelected($view, $choice); } /** @@ -74,13 +64,12 @@ public function isChoiceSelected(FormView $view, ChoiceView $choice) * * The theme format is ":". * - * @param FormView $view A FormView instance + * @param FormViewInterface $view A FormViewInterface instance * @param string|array $themes A theme or an array of theme */ - public function setTheme(FormView $view, $themes) + public function setTheme(FormViewInterface $view, $themes) { - $this->themes[$view->getVar('id')] = (array) $themes; - $this->templates = array(); + $this->renderer->setTheme($view, $themes); } /** @@ -90,13 +79,13 @@ public function setTheme(FormView $view, $themes) * * enctype() ?>> * - * @param FormView $view The view for which to render the encoding type + * @param FormViewInterface $view The view for which to render the encoding type * - * @return string The html markup + * @return string The HTML markup */ - public function enctype(FormView $view) + public function enctype(FormViewInterface $view) { - return $this->renderSection($view, 'enctype'); + return $this->renderer->renderEnctype($view); } /** @@ -112,70 +101,95 @@ public function enctype(FormView $view) * * widget(array('separator' => '+++++)) ?> * - * @param FormView $view The view for which to render the widget - * @param array $variables Additional variables passed to the template + * @param FormViewInterface $view The view for which to render the widget + * @param array $variables Additional variables passed to the template * - * @return string The html markup + * @return string The HTML markup */ - public function widget(FormView $view, array $variables = array()) + public function widget(FormViewInterface $view, array $variables = array()) { - return $this->renderSection($view, 'widget', $variables); + return $this->renderer->renderWidget($view, $variables); } /** * Renders the entire form field "row". * - * @param FormView $view The view for which to render the row - * @param array $variables Additional variables passed to the template + * @param FormViewInterface $view The view for which to render the row + * @param array $variables Additional variables passed to the template * - * @return string The html markup + * @return string The HTML markup */ - public function row(FormView $view, array $variables = array()) + public function row(FormViewInterface $view, array $variables = array()) { - return $this->renderSection($view, 'row', $variables); + return $this->renderer->renderRow($view, $variables); } /** * Renders the label of the given view. * - * @param FormView $view The view for which to render the label - * @param string $label The label - * @param array $variables Additional variables passed to the template + * @param FormViewInterface $view The view for which to render the label + * @param string $label The label + * @param array $variables Additional variables passed to the template * - * @return string The html markup + * @return string The HTML markup */ - public function label(FormView $view, $label = null, array $variables = array()) + public function label(FormViewInterface $view, $label = null, array $variables = array()) { - if ($label !== null) { - $variables += array('label' => $label); - } - - return $this->renderSection($view, 'label', $variables); + return $this->renderer->renderLabel($view, $label, $variables); } /** * Renders the errors of the given view. * - * @param FormView $view The view to render the errors for + * @param FormViewInterface $view The view to render the errors for * - * @return string The html markup + * @return string The HTML markup */ - public function errors(FormView $view) + public function errors(FormViewInterface $view) { - return $this->renderSection($view, 'errors'); + return $this->renderer->renderErrors($view); } /** * Renders views which have not already been rendered. * - * @param FormView $view The parent view - * @param array $variables An array of variables + * @param FormViewInterface $view The parent view + * @param array $variables An array of variables * - * @return string The html markup + * @return string The HTML markup */ - public function rest(FormView $view, array $variables = array()) + public function rest(FormViewInterface $view, array $variables = array()) { - return $this->renderSection($view, 'rest', $variables); + return $this->renderer->renderRest($view, $variables); + } + + /** + * Alias of {@link block()} + * + * @param string $block The name of the block to render. + * @param array $variables The variable to pass to the template. + * + * @return string The HTML markup + * + * @deprecated Deprecated since version 2.1, to be removed in 2.3. Use + * {@link block()} instead. + */ + public function renderBlock($block, array $variables = array()) + { + return $this->block($block, $variables); + } + + /** + * Renders a block of the template. + * + * @param string $block The name of the block to render. + * @param array $variables The variable to pass to the template. + * + * @return string The HTML markup + */ + public function block($block, array $variables = array()) + { + return $this->renderer->renderBlock($block, $variables); } /** @@ -200,166 +214,16 @@ public function rest(FormView $view, array $variables = array()) * @param string $intention The intention of the protected action * * @return string A CSRF token - */ - public function csrfToken($intention) - { - if (!$this->csrfProvider instanceof CsrfProviderInterface) { - throw new \BadMethodCallException('CSRF token can only be generated if a CsrfProviderInterface is injected in the constructor.'); - } - - return $this->csrfProvider->generateCsrfToken($intention); - } - - /** - * Renders a template. - * - * 1. This function first looks for a block named "__
", - * 2. if such a block is not found the function will look for a block named - * "_
", - * 3. the type name is recursively replaced by the parent type name until a - * corresponding block is found - * - * @param FormView $view The form view - * @param string $section The section to render (i.e. 'row', 'widget', 'label', ...) - * @param array $variables Additional variables - * - * @return string The html markup - * - * @throws FormException if no template block exists to render the given section of the view - */ - protected function renderSection(FormView $view, $section, array $variables = array()) - { - $mainTemplate = in_array($section, array('row', 'widget')); - if ($mainTemplate && $view->isRendered()) { - - return ''; - } - - $template = null; - - $custom = '_'.$view->getVar('id'); - $rendering = $custom.$section; - - if (isset($this->varStack[$rendering])) { - $typeIndex = $this->varStack[$rendering]['typeIndex'] - 1; - $types = $this->varStack[$rendering]['types']; - $variables = array_replace_recursive($this->varStack[$rendering]['variables'], $variables); - } else { - $types = $view->getVar('types'); - $types[] = $view->getVar('full_block_name'); - $typeIndex = count($types) - 1; - $variables = array_replace_recursive($view->getVars(), $variables); - $this->varStack[$rendering]['types'] = $types; - } - - $this->varStack[$rendering]['variables'] = $variables; - - do { - $types[$typeIndex] .= '_'.$section; - $template = $this->lookupTemplate($view, $types[$typeIndex]); - - if ($template) { - - $this->varStack[$rendering]['typeIndex'] = $typeIndex; - - $this->context[] = array( - 'variables' => $variables, - 'view' => $view, - ); - - $html = $this->engine->render($template, $variables); - - array_pop($this->context); - unset($this->varStack[$rendering]); - - if ($mainTemplate) { - $view->setRendered(); - } - - return trim($html); - } - } while (--$typeIndex >= 0); - - throw new FormException(sprintf( - 'Unable to render the form as none of the following blocks exist: "%s".', - implode('", "', array_reverse($types)) - )); - } - - /** - * Render a block from a form element. * - * @param string $name - * @param array $variables Additional variables (those would override the current context) - * - * @throws FormException if the block is not found - * @throws FormException if the method is called out of a form element (no context) + * @throws \BadMethodCallException When no CSRF provider was injected in the constructor. */ - public function renderBlock($name, $variables = array()) + public function csrfToken($intention) { - if (0 == count($this->context)) { - throw new FormException(sprintf('This method should only be called while rendering a form element.', $name)); - } - - $context = end($this->context); - - $template = $this->lookupTemplate($context['view'], $name); - - if (false === $template) { - throw new FormException(sprintf('No block "%s" found while rendering the form.', $name)); - } - - $variables = array_replace_recursive($context['variables'], $variables); - - return trim($this->engine->render($template, $variables)); + return $this->renderer->renderCsrfToken($intention); } public function humanize($text) { - return ucfirst(trim(strtolower(preg_replace('/[_\s]+/', ' ', $text)))); - } - - public function getName() - { - return 'form'; - } - - /** - * Returns the name of the template to use to render the block - * - * @param FormView $view The form view - * @param string $block The name of the block - * - * @return string|Boolean The template logical name or false when no template is found - */ - protected function lookupTemplate(FormView $view, $block) - { - $file = $block.'.html.php'; - $id = $view->getVar('id'); - - if (!isset($this->templates[$id][$block])) { - $template = false; - - $themes = $view->hasParent() ? array() : $this->resources; - - if (isset($this->themes[$id])) { - $themes = array_merge($themes, $this->themes[$id]); - } - - for ($i = count($themes) - 1; $i >= 0; --$i) { - if ($this->engine->exists($templateName = $themes[$i].':'.$file)) { - $template = $templateName; - break; - } - } - - if (false === $template && $view->hasParent()) { - $template = $this->lookupTemplate($view->getParent(), $block); - } - - $this->templates[$id][$block] = $template; - } - - return $this->templates[$id][$block]; + return $this->renderer->humanize($text); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php index 7d3a6f1b79706..cc386971f9870 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php @@ -15,9 +15,11 @@ use Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper; use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTemplateNameParser; use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTranslator; -use Symfony\Component\Form\FormView; use Symfony\Component\Templating\PhpEngine; use Symfony\Component\Templating\Loader\FilesystemLoader; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\FormRenderer; +use Symfony\Component\Form\Extension\Templating\TemplatingRendererEngine; use Symfony\Component\Form\Tests\AbstractDivLayoutTest; class FormHelperDivLayoutTest extends AbstractDivLayoutTest @@ -34,8 +36,10 @@ protected function setUp() $loader = new FilesystemLoader(array()); $engine = new PhpEngine($templateNameParser, $loader); $engine->addGlobal('global', ''); + $rendererEngine = new TemplatingRendererEngine($engine, array('FrameworkBundle:Form')); + $renderer = new FormRenderer($rendererEngine, $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface')); - $this->helper = new FormHelper($engine, $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'), array('FrameworkBundle:Form')); + $this->helper = new FormHelper($renderer); $engine->setHelpers(array( $this->helper, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php index 21a26459726a8..ad8b74eba9359 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php @@ -16,9 +16,11 @@ use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTemplateNameParser; use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTranslator; use Symfony\Component\Form\FormView; +use Symfony\Component\Form\FormRenderer; +use Symfony\Component\Form\Extension\Templating\TemplatingRendererEngine; +use Symfony\Component\Form\Tests\AbstractTableLayoutTest; use Symfony\Component\Templating\PhpEngine; use Symfony\Component\Templating\Loader\FilesystemLoader; -use Symfony\Component\Form\Tests\AbstractTableLayoutTest; class FormHelperTableLayoutTest extends AbstractTableLayoutTest { @@ -34,11 +36,13 @@ protected function setUp() $loader = new FilesystemLoader(array()); $engine = new PhpEngine($templateNameParser, $loader); $engine->addGlobal('global', ''); - - $this->helper = new FormHelper($engine, $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'), array( + $rendererEngine = new TemplatingRendererEngine($engine, array( 'FrameworkBundle:Form', 'FrameworkBundle:FormTable' )); + $renderer = new FormRenderer($rendererEngine, $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface')); + + $this->helper = new FormHelper($renderer); $engine->setHelpers(array( $this->helper, diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index 3d4a6971222f6..eae0a49bb573a 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -16,6 +16,8 @@ Symfony\Bridge\Twig\Extension\RoutingExtension Symfony\Bridge\Twig\Extension\YamlExtension Symfony\Bridge\Twig\Extension\FormExtension + Symfony\Bridge\Twig\Form\TwigRendererEngine + Symfony\Bridge\Twig\Form\TwigRenderer Symfony\Bridge\Twig\Translation\TwigExtractor Symfony\Component\HttpKernel\EventListener\ExceptionListener @@ -75,10 +77,18 @@ - + + + + %twig.form.resources% + + + + + diff --git a/src/Symfony/Component/Form/AbstractRendererEngine.php b/src/Symfony/Component/Form/AbstractRendererEngine.php new file mode 100644 index 0000000000000..f3b403689bc03 --- /dev/null +++ b/src/Symfony/Component/Form/AbstractRendererEngine.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * Default implementation of {@link FormRendererEngineInterface}. + * + * @author Bernhard Schussek + */ +abstract class AbstractRendererEngine implements FormRendererEngineInterface +{ + /** + * The variable in {@link FormViewInterface} used as cache key. + */ + const CACHE_KEY_VAR = 'full_block_name'; + + /** + * @var array + */ + protected $defaultThemes; + + /** + * @var array + */ + protected $themes = array(); + + /** + * @var array + */ + protected $resources = array(); + + /** + * @var array + */ + private $resourceHierarchyLevels = array(); + + /** + * Creates a new renderer engine. + * + * @param array $defaultThemes The default themes. The type of these + * themes is open to the implementation. + */ + public function __construct(array $defaultThemes = array()) + { + $this->defaultThemes = $defaultThemes; + } + + /** + * {@inheritdoc} + */ + public function setTheme(FormViewInterface $view, $themes) + { + $cacheKey = $view->getVar(self::CACHE_KEY_VAR); + + // Do not cast, as casting turns objects into arrays of properties + $this->themes[$cacheKey] = is_array($themes) ? $themes : array($themes); + + // Unset instead of resetting to an empty array, in order to allow + // implementations (like TwigRendererEngine) to check whether $cacheKey + // is set at all. + unset($this->resources[$cacheKey]); + unset($this->resourceHierarchyLevels[$cacheKey]); + } + + /** + * {@inheritdoc} + */ + public function getResourceForBlock(FormViewInterface $view, $block) + { + $cacheKey = $view->getVar(self::CACHE_KEY_VAR); + + if (!isset($this->resources[$cacheKey][$block])) { + $this->loadResourceForBlock($cacheKey, $view, $block); + } + + return $this->resources[$cacheKey][$block]; + } + + /** + * {@inheritdoc} + */ + public function getResourceForBlockHierarchy(FormViewInterface $view, array $blockHierarchy, $hierarchyLevel) + { + $cacheKey = $view->getVar(self::CACHE_KEY_VAR); + $block = $blockHierarchy[$hierarchyLevel]; + + if (!isset($this->resources[$cacheKey][$block])) { + $this->loadResourceForBlockHierarchy($cacheKey, $view, $blockHierarchy, $hierarchyLevel); + } + + return $this->resources[$cacheKey][$block]; + } + + /** + * {@inheritdoc} + */ + public function getResourceHierarchyLevel(FormViewInterface $view, array $blockHierarchy, $hierarchyLevel) + { + $cacheKey = $view->getVar(self::CACHE_KEY_VAR); + $block = $blockHierarchy[$hierarchyLevel]; + + if (!isset($this->resources[$cacheKey][$block])) { + $this->loadResourceForBlockHierarchy($cacheKey, $view, $blockHierarchy, $hierarchyLevel); + } + + // If $block was previously rendered loaded with loadTemplateForBlock(), the template + // is cached but the hierarchy level is not. In this case, we know that the block + // exists at this very hierarchy level, so we can just set it. + if (!isset($this->resourceHierarchyLevels[$cacheKey][$block])) { + $this->resourceHierarchyLevels[$cacheKey][$block] = $hierarchyLevel; + } + + return $this->resourceHierarchyLevels[$cacheKey][$block]; + } + + /** + * Loads the cache with the resource for a given block name. + * + * @see getResourceForBlock() + * + * @param string $cacheKey The cache key of the form view. + * @param FormViewInterface $view The form view for finding the applying themes. + * @param string $block The name of the block to load. + * + * @return Boolean True if the resource could be loaded, false otherwise. + */ + abstract protected function loadResourceForBlock($cacheKey, FormViewInterface $view, $block); + + /** + * Loads the cache with the resource for a specific level of a block hierarchy. + * + * @see getResourceForBlockHierarchy() + * + * @param string $cacheKey The cache key used for storing the + * resource. + * @param FormViewInterface $view The form view for finding the applying + * themes. + * @param array $blockHierarchy The block hierarchy, with the most + * specific block name at the end. + * @param integer $hierarchyLevel The level in the block hierarchy that + * should be loaded. + * + * @return Boolean True if the resource could be loaded, false otherwise. + */ + private function loadResourceForBlockHierarchy($cacheKey, FormViewInterface $view, array $blockHierarchy, $hierarchyLevel) + { + $block = $blockHierarchy[$hierarchyLevel]; + + // Try to find a template for that block + if ($this->loadResourceForBlock($cacheKey, $view, $block)) { + // If loadTemplateForBlock() returns true, it was able to populate the + // cache. The only missing thing is to set the hierarchy level at which + // the template was found. + $this->resourceHierarchyLevels[$cacheKey][$block] = $hierarchyLevel; + + return true; + } + + if ($hierarchyLevel > 0) { + $parentLevel = $hierarchyLevel - 1; + $parentBlock = $blockHierarchy[$parentLevel]; + + // The next two if statements contain slightly duplicated code. This is by intention + // and tries to avoid execution of unnecessary checks in order to increase performance. + + if (isset($this->resources[$cacheKey][$parentBlock])) { + // It may happen that the parent block is already loaded, but its level is not. + // In this case, the parent block must have been loaded by loadResourceForBlock(), + // which does not check the hierarchy of the block. Subsequently the block must have + // been found directly on the parent level. + if (!isset($this->resourceHierarchyLevels[$cacheKey][$parentBlock])) { + $this->resourceHierarchyLevels[$cacheKey][$parentBlock] = $parentLevel; + } + + // Cache the shortcuts for further accesses + $this->resources[$cacheKey][$block] = $this->resources[$cacheKey][$parentBlock]; + $this->resourceHierarchyLevels[$cacheKey][$block] = $this->resourceHierarchyLevels[$cacheKey][$parentBlock]; + + return true; + } + + if ($this->loadResourceForBlockHierarchy($cacheKey, $view, $blockHierarchy, $parentLevel)) { + // Cache the shortcuts for further accesses + $this->resources[$cacheKey][$block] = $this->resources[$cacheKey][$parentBlock]; + $this->resourceHierarchyLevels[$cacheKey][$block] = $this->resourceHierarchyLevels[$cacheKey][$parentBlock]; + + return true; + } + } + + // Cache the result for further accesses + $this->resources[$cacheKey][$block] = false; + $this->resourceHierarchyLevels[$cacheKey][$block] = false; + + return false; + } +} diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 03290fb8b5d4d..279a2badbf611 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -31,7 +31,6 @@ CHANGELOG * ArrayToChoicesTransformer to ChoicesToValuesTransformer * ScalarToChoiceTransformer to ChoiceToValueTransformer to be consistent with the naming in ChoiceListInterface. - * [BC BREAK] removed FormUtil::toArrayKey() and FormUtil::toArrayKeys(). They were merged into ChoiceList and have no public equivalent anymore. * choice fields now throw a FormException if neither the "choices" nor the "choice_list" option is set @@ -141,7 +140,7 @@ CHANGELOG * deprecated `hasChildren` in Form and FormBuilder in favor of `count` * FormBuilder now implements \IteratorAggregate * [BC BREAK] compound forms now always need a data mapper - * FormBuilder now maintains the order when explicitely adding form builders as children + * FormBuilder now maintains the order when explicitly adding form builders as children * ChoiceType now doesn't add the empty value anymore if the choices already contain an empty element * DateType, TimeType and DateTimeType now show empty values again if not required * [BC BREAK] fixed rendering of errors for DateType, BirthdayType and similar ones @@ -172,4 +171,11 @@ CHANGELOG * ChoiceType now caches its created choice lists to improve performance * [BC BREAK] Rows of a collection field cannot be themed individually anymore. All rows in the collection field now have the same block names, which contains "entry" where it previously contained the row index. - * [BC BREAK] When registering a type through the DI extension, the tag alias has to match the actual type name. \ No newline at end of file + * [BC BREAK] When registering a type through the DI extension, the tag alias has to match the actual type name. + * added FormRendererInterface, FormRendererEngineInterface and implementations of these interfaces + * [BC BREAK] removed the following methods from FormUtil: + * `toArrayKey` + * `toArrayKeys` + * `isChoiceGroup` + * `isChoiceSelected` + * added method `block` to FormHelper and deprecated `renderBlock` instead diff --git a/src/Symfony/Component/Form/Extension/Templating/TemplatingRendererEngine.php b/src/Symfony/Component/Form/Extension/Templating/TemplatingRendererEngine.php new file mode 100644 index 0000000000000..00f76e106dc4b --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Templating/TemplatingRendererEngine.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Templating; + +use Symfony\Component\Form\AbstractRendererEngine; +use Symfony\Component\Form\FormViewInterface; +use Symfony\Component\Templating\EngineInterface; + +/** + * @author Bernhard Schussek + */ +class TemplatingRendererEngine extends AbstractRendererEngine +{ + /** + * @var EngineInterface + */ + private $engine; + + public function __construct(EngineInterface $engine, array $defaultThemes = array()) + { + parent::__construct($defaultThemes); + + $this->engine = $engine; + } + + /** + * {@inheritdoc} + */ + public function renderBlock(FormViewInterface $view, $resource, $block, array $variables = array()) + { + return trim($this->engine->render($resource, $variables)); + } + + /** + * Loads the cache with the resource for a given block name. + * + * This implementation tries to load as few blocks as possible, since each block + * is represented by a template on the file system. + * + * @see getResourceForBlock() + * + * @param string $cacheKey The cache key of the form view. + * @param FormViewInterface $view The form view for finding the applying themes. + * @param string $block The name of the block to load. + * + * @return Boolean True if the resource could be loaded, false otherwise. + */ + protected function loadResourceForBlock($cacheKey, FormViewInterface $view, $block) + { + // Recursively try to find the block in the themes assigned to $view, + // then of its parent form, then of the parent form of the parent and so on. + // When the root form is reached in this recursion, also the default + // themes are taken into account. + + // Check each theme whether it contains the searched block + if (isset($this->themes[$cacheKey])) { + for ($i = count($this->themes[$cacheKey]) - 1; $i >= 0; --$i) { + if ($this->loadResourceFromTheme($cacheKey, $block, $this->themes[$cacheKey][$i])) { + return true; + } + } + } + + // Check the default themes once we reach the root form without success + if (!$view->hasParent()) { + for ($i = count($this->defaultThemes) - 1; $i >= 0; --$i) { + if ($this->loadResourceFromTheme($cacheKey, $block, $this->defaultThemes[$i])) { + return true; + } + } + } + + // If we did not find anything in the themes of the current view, proceed + // with the themes of the parent view + if ($view->hasParent()) { + $parentCacheKey = $view->getParent()->getVar(self::CACHE_KEY_VAR); + + if (!isset($this->resources[$parentCacheKey][$block])) { + $this->loadResourceForBlock($parentCacheKey, $view->getParent(), $block); + } + + // If a template exists in the parent themes, cache that template + // for the current theme as well to speed up further accesses + if ($this->resources[$parentCacheKey][$block]) { + $this->resources[$cacheKey][$block] = $this->resources[$parentCacheKey][$block]; + + return true; + } + } + + // Cache that we didn't find anything to speed up further accesses + $this->resources[$cacheKey][$block] = false; + + return false; + } + + /** + * Tries to load the resource for a block from a theme. + * + * @param string $cacheKey The cache key for storing the resource. + * @param string $block The name of the block to load a resource for. + * @param mixed $theme The theme to load the block from. + * + * @return Boolean True if the resource could be loaded, false otherwise. + */ + protected function loadResourceFromTheme($cacheKey, $block, $theme) + { + if ($this->engine->exists($templateName = $theme . ':' . $block . '.html.php')) { + $this->resources[$cacheKey][$block] = $templateName; + + return true; + } + + return false; + } +} diff --git a/src/Symfony/Component/Form/FormRenderer.php b/src/Symfony/Component/Form/FormRenderer.php new file mode 100644 index 0000000000000..738c01f17b740 --- /dev/null +++ b/src/Symfony/Component/Form/FormRenderer.php @@ -0,0 +1,350 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Extension\Core\View\ChoiceView; +use Symfony\Component\Form\Exception\FormException; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; + +/** + * Renders a form into HTML using a rendering engine. + * + * @author Bernhard Schussek + */ +class FormRenderer implements FormRendererInterface +{ + /** + * @var FormRendererEngineInterface + */ + private $engine; + + /** + * @var CsrfProviderInterface + */ + private $csrfProvider; + + /** + * @var array + */ + private $blockHierarchyMap = array(); + + /** + * @var array + */ + private $hierarchyLevelMap = array(); + + /** + * @var array + */ + private $variableMap = array(); + + /** + * @var array + */ + private $stack = array(); + + public function __construct(FormRendererEngineInterface $engine, CsrfProviderInterface $csrfProvider = null) + { + $this->engine = $engine; + $this->csrfProvider = $csrfProvider; + } + + /** + * {@inheritdoc} + */ + public function getEngine() + { + return $this->engine; + } + + /** + * {@inheritdoc} + */ + public function setTheme(FormViewInterface $view, $themes) + { + $this->engine->setTheme($view, $themes); + } + + /** + * {@inheritdoc} + */ + public function renderEnctype(FormViewInterface $view) + { + return $this->renderSection($view, 'enctype'); + } + + /** + * {@inheritdoc} + */ + public function renderRow(FormViewInterface $view, array $variables = array()) + { + return $this->renderSection($view, 'row', $variables); + } + + /** + * {@inheritdoc} + */ + public function renderRest(FormViewInterface $view, array $variables = array()) + { + return $this->renderSection($view, 'rest', $variables); + } + + /** + * {@inheritdoc} + */ + public function renderWidget(FormViewInterface $view, array $variables = array()) + { + return $this->renderSection($view, 'widget', $variables); + } + + /** + * {@inheritdoc} + */ + public function renderErrors(FormViewInterface $view) + { + return $this->renderSection($view, 'errors'); + } + + /** + * {@inheritdoc} + */ + public function renderLabel(FormViewInterface $view, $label = null, array $variables = array()) + { + if ($label !== null) { + $variables += array('label' => $label); + } + + return $this->renderSection($view, 'label', $variables); + } + + /** + * {@inheritdoc} + */ + public function renderCsrfToken($intention) + { + if (null === $this->csrfProvider) { + throw new \BadMethodCallException('CSRF token can only be generated if a CsrfProviderInterface is injected in the constructor.'); + } + + return $this->csrfProvider->generateCsrfToken($intention); + } + + /** + * {@inheritdoc} + */ + public function renderBlock($block, array $variables = array()) + { + if (0 == count($this->stack)) { + throw new FormException('This method should only be called while rendering a form element.'); + } + + list($view, $scopeVariables) = end($this->stack); + + $resource = $this->engine->getResourceForBlock($view, $block); + + if (!$resource) { + throw new FormException(sprintf('No block "%s" found while rendering the form.', $block)); + } + + // Merge the passed with the existing attributes + if (isset($variables['attr']) && isset($scopeVariables['attr'])) { + $variables['attr'] = array_replace($scopeVariables['attr'], $variables['attr']); + } + + // Merge the passed with the exist *label* attributes + if (isset($variables['label_attr']) && isset($scopeVariables['label_attr'])) { + $variables['label_attr'] = array_replace($scopeVariables['label_attr'], $variables['label_attr']); + } + + // Do not use array_replace_recursive(), otherwise array variables + // cannot be overwritten + $variables = array_replace($scopeVariables, $variables); + + return $this->engine->renderBlock($view, $resource, $block, $variables); + } + + /** + * {@inheritdoc} + */ + public function isChoiceGroup($choice) + { + return is_array($choice) || $choice instanceof \Traversable; + } + + /** + * {@inheritdoc} + */ + public function isChoiceSelected(FormViewInterface $view, ChoiceView $choice) + { + $value = $view->getVar('value'); + $choiceValue = $choice->getValue(); + + if (is_array($value)) { + return false !== array_search($choiceValue, $value, true); + } + + return $choiceValue === $value; + } + + /** + * {@inheritdoc} + */ + public function humanize($text) + { + return ucfirst(trim(strtolower(preg_replace('/[_\s]+/', ' ', $text)))); + } + + /** + * Renders the given section of a form view. + * + * @param FormViewInterface $view The form view. + * @param string $section The name of the section to render. + * @param array $variables The variables to pass to the template. + * + * @return string The HTML markup. + * + * @throws Exception\FormException If no fitting template was found. + */ + protected function renderSection(FormViewInterface $view, $section, array $variables = array()) + { + $renderOnlyOnce = in_array($section, array('row', 'widget')); + + if ($renderOnlyOnce && $view->isRendered()) { + return ''; + } + + // The cache key for storing the variables and types + $mapKey = $uniqueBlockName = $view->getVar('full_block_name') . '_' . $section; + + // In templates, we have to deal with two kinds of block hierarchies: + // + // +---------+ +---------+ + // | Theme B | -------> | Theme A | + // +---------+ +---------+ + // + // form_widget -------> form_widget + // ^ + // | + // choice_widget -----> choice_widget + // + // The first kind of hierarchy is the theme hierarchy. This allows to + // override the block "choice_widget" from Theme A in the extending + // Theme B. This kind of inheritance needs to be supported by the + // template engine and, for example, offers "parent()" or similar + // functions to fall back from the custom to the parent implementation. + // + // The second kind of hierarchy is the form type hierarchy. This allows + // to implement a custom "choice_widget" block (no matter in which theme), + // or to fallback to the block of the parent type, which would be + // "form_widget" in this example (again, no matter in which theme). + // If the designer wants to explicitly fallback to "form_widget" in his + // custom "choice_widget", for example because he only wants to wrap + // a
around the original implementation, he can simply call the + // widget() function again to render the block for the parent type. + // + // The second kind is implemented in the following blocks. + if (!isset($this->blockHierarchyMap[$mapKey])) { + // INITIAL CALL + // Calculate the hierarchy of template blocks and start on + // the bottom level of the hierarchy (= "__
" block) + $blockHierarchy = array(); + foreach ($view->getVar('types') as $type) { + $blockHierarchy[] = $type . '_' . $section; + } + $blockHierarchy[] = $uniqueBlockName; + $hierarchyLevel = count($blockHierarchy) - 1; + + // The default variable scope contains all view variables, merged with + // the variables passed explicitly to the helper + $scopeVariables = $view->getVars(); + } else { + // RECURSIVE CALL + // If a block recursively calls renderSection() again, resume rendering + // using the parent type in the hierarchy. + $blockHierarchy = $this->blockHierarchyMap[$mapKey]; + $hierarchyLevel = $this->hierarchyLevelMap[$mapKey] - 1; + + // Reuse the current scope and merge it with the explicitly passed variables + $scopeVariables = $this->variableMap[$mapKey]; + } + + // Load the resource where this block can be found + $resource = $this->engine->getResourceForBlockHierarchy($view, $blockHierarchy, $hierarchyLevel); + + // Update the current hierarchy level to the one at which the resource was + // found. For example, if looking for "choice_widget", but only a resource + // is found for its parent "form_widget", then the level is updated here + // to the parent level. + $hierarchyLevel = $this->engine->getResourceHierarchyLevel($view, $blockHierarchy, $hierarchyLevel); + + // The actually existing block name in $resource + $block = $blockHierarchy[$hierarchyLevel]; + + // Escape if no resource exists for this block + if (!$resource) { + throw new FormException(sprintf( + 'Unable to render the form as none of the following blocks exist: "%s".', + implode('", "', array_reverse($blockHierarchy)) + )); + } + + // Merge the passed with the existing attributes + if (isset($variables['attr']) && isset($scopeVariables['attr'])) { + $variables['attr'] = array_replace($scopeVariables['attr'], $variables['attr']); + } + + // Merge the passed with the exist *label* attributes + if (isset($variables['label_attr']) && isset($scopeVariables['label_attr'])) { + $variables['label_attr'] = array_replace($scopeVariables['label_attr'], $variables['label_attr']); + } + + // Do not use array_replace_recursive(), otherwise array variables + // cannot be overwritten + $variables = array_replace($scopeVariables, $variables); + + // In order to make recursive calls possible, we need to store the block hierarchy, + // the current level of the hierarchy and the variables so that this method can + // resume rendering one level higher of the hierarchy when it is called recursively. + // + // We need to store these values in maps (associative arrays) because within a + // call to widget() another call to widget() can be made, but for a different view + // object. These nested calls should not override each other. + $this->blockHierarchyMap[$mapKey] = $blockHierarchy; + $this->hierarchyLevelMap[$mapKey] = $hierarchyLevel; + $this->variableMap[$mapKey] = $variables; + + // We also need to store the view and the variables so that we can render custom + // blocks with renderBlock() using the same themes and variables as in the outer + // block. + // + // A stack is sufficient for this purpose, because renderBlock() always accesses + // the immediate next outer scope, which is always stored at the end of the stack. + $this->stack[] = array($view, $variables); + + // Do the rendering + $html = $this->engine->renderBlock($view, $resource, $block, $variables); + + // Clear the stack + array_pop($this->stack); + + // Clear the maps + unset($this->blockHierarchyMap[$mapKey]); + unset($this->hierarchyLevelMap[$mapKey]); + unset($this->variableMap[$mapKey]); + + if ($renderOnlyOnce) { + $view->setRendered(); + } + + return $html; + } +} diff --git a/src/Symfony/Component/Form/FormRendererEngineInterface.php b/src/Symfony/Component/Form/FormRendererEngineInterface.php new file mode 100644 index 0000000000000..aca8f75fb603e --- /dev/null +++ b/src/Symfony/Component/Form/FormRendererEngineInterface.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * Adapter for rendering form templates with a specific templating engine. + * + * @author Bernhard Schussek + */ +interface FormRendererEngineInterface +{ + /** + * Sets the theme(s) to be used for rendering a view and its children. + * + * @param FormViewInterface $view The view to assign the theme(s) to. + * @param mixed $themes The theme(s). The type of these themes + * is open to the implementation. + */ + public function setTheme(FormViewInterface $view, $themes); + + /** + * Returns the resource for a block name. + * + * The resource is first searched in the themes attached to $view, then + * in the themes of its parent view and so on, until a resource was found. + * + * The type of the resource is decided by the implementation. The resource + * is later passed to {@link renderBlock()} by the rendering algorithm. + * + * @param FormViewInterface $view The view for determining the used themes. + * First the themes attached directly to the + * view with {@link setTheme()} are considered, + * then the ones of its parent etc. + * @param string $block The name of the block to render. + * + * @return mixed The renderer resource or false, if none was found. + */ + public function getResourceForBlock(FormViewInterface $view, $block); + + /** + * Returns the resource for a block hierarchy. + * + * A block hierarchy is an array which starts with the root of the hierarchy + * and continues with the child of that root, the child of that child etc. + * The following is an example for a block hierarchy: + * + * + * form_widget + * text_widget + * url_widget + * + * + * In this example, "url_widget" is the most specific block, while the other + * blocks are its ancestors in the hierarchy. + * + * The second parameter $hierarchyLevel determines the level of the hierarchy + * that should be rendered. For example, if $hierarchyLevel is 2 for the + * above hierarchy, the engine will first look for the block "url_widget", + * then, if that does not exist, for the block "text_widget" etc. + * + * The type of the resource is decided by the implementation. The resource + * is later passed to {@link renderBlock()} by the rendering algorithm. + * + * @param FormViewInterface $view The view for determining the used + * themes. First the themes attached + * directly to the view with + * {@link setTheme()} are considered, + * then the ones of its parent etc. + * @param array $blockHierarchy The block name hierarchy, with + * the root block at the beginning. + * @param integer $hierarchyLevel The level in the hierarchy at + * which to start looking. Level 0 + * indicates the root block, i.e. + * the first element of $blockHierarchy. + * + * @return mixed The renderer resource or false, if none was found. + */ + public function getResourceForBlockHierarchy(FormViewInterface $view, array $blockHierarchy, $hierarchyLevel); + + /** + * Returns the hierarchy level at which a resource can be found. + * + * A block hierarchy is an array which starts with the root of the hierarchy + * and continues with the child of that root, the child of that child etc. + * The following is an example for a block hierarchy: + * + * + * form_widget + * text_widget + * url_widget + * + * + * The second parameter $hierarchyLevel determines the level of the hierarchy + * that should be rendered. + * + * If we call this method with the hierarchy level 2, the engine will first + * look for a resource for block "url_widget". If such a resource exists, + * the method returns 2. Otherwise it tries to find a resource for block + * "text_widget" (at level 1) and, again, returns 1 if a resource was found. + * The method continues to look for resources until the root level was + * reached and nothing was found. In this case false is returned. + * + * The type of the resource is decided by the implementation. The resource + * is later passed to {@link renderBlock()} by the rendering algorithm. + * + * @param FormViewInterface $view The view for determining the used + * themes. First the themes attached + * directly to the view with + * {@link setTheme()} are considered, + * then the ones of its parent etc. + * @param array $blockHierarchy The block name hierarchy, with + * the root block at the beginning. + * @param integer $hierarchyLevel The level in the hierarchy at + * which to start looking. Level 0 + * indicates the root block, i.e. + * the first element of $blockHierarchy. + * + * @return integer|Boolean The hierarchy level or false, if no resource was found. + */ + public function getResourceHierarchyLevel(FormViewInterface $view, array $blockHierarchy, $hierarchyLevel); + + /** + * Renders a block in the given renderer resource. + * + * The resource can be obtained by calling {@link getResourceForBlock()} + * or {@link getResourceForBlockHierarchy()}. The type of the resource is + * decided by the implementation. + * + * @param FormViewInterface $view The view to render. + * @param mixed $resource The renderer resource. + * @param string $block The name of the block to render. + * @param array $variables The variables to pass to the template. + * + * @return string The HTML markup. + */ + public function renderBlock(FormViewInterface $view, $resource, $block, array $variables = array()); +} diff --git a/src/Symfony/Component/Form/FormRendererInterface.php b/src/Symfony/Component/Form/FormRendererInterface.php new file mode 100644 index 0000000000000..1da338a3fd8b1 --- /dev/null +++ b/src/Symfony/Component/Form/FormRendererInterface.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Extension\Core\View\ChoiceView; + +/** + * Renders a form into HTML. + * + * @author Bernhard Schussek + */ +interface FormRendererInterface +{ + /** + * Returns the engine used by this renderer. + * + * @return FormRendererEngineInterface The renderer engine. + */ + public function getEngine(); + + /** + * Sets the theme(s) to be used for rendering a view and its children. + * + * @param FormViewInterface $view The view to assign the theme(s) to. + * @param mixed $themes The theme(s). The type of these themes + * is open to the implementation. + */ + public function setTheme(FormViewInterface $view, $themes); + + /** + * Renders the HTML enctype in the form tag, if necessary. + * + * Example usage templates: + * + * renderEnctype($form) ?>> + * + * @param FormViewInterface $view The view for which to render the encoding type + * + * @return string The HTML markup + */ + public function renderEnctype(FormViewInterface $view); + + /** + * Renders the entire row for a form field. + * + * A row typically contains the label, errors and widget of a field. + * + * @param FormViewInterface $view The view for which to render the row + * @param array $variables Additional variables passed to the template + * + * @return string The HTML markup + */ + public function renderRow(FormViewInterface $view, array $variables = array()); + + /** + * Renders views which have not already been rendered. + * + * @param FormViewInterface $view The parent view + * @param array $variables An array of variables + * + * @return string The HTML markup + */ + public function renderRest(FormViewInterface $view, array $variables = array()); + + /** + * Renders the HTML for a given view. + * + * Example usage: + * + * renderWidget($form) ?> + * + * You can pass options during the call: + * + * renderWidget($form, array('attr' => array('class' => 'foo'))) ?> + * + * renderWidget($form, array('separator' => '+++++)) ?> + * + * @param FormViewInterface $view The view for which to render the widget + * @param array $variables Additional variables passed to the template + * + * @return string The HTML markup + */ + public function renderWidget(FormViewInterface $view, array $variables = array()); + + /** + * Renders the errors of the given view. + * + * @param FormViewInterface $view The view to render the errors for + * + * @return string The HTML markup + */ + public function renderErrors(FormViewInterface $view); + + /** + * Renders the label of the given view. + * + * @param FormViewInterface $view The view for which to render the label + * @param string $label The label + * @param array $variables Additional variables passed to the template + * + * @return string The HTML markup + */ + public function renderLabel(FormViewInterface $view, $label = null, array $variables = array()); + + /** + * Renders a named block of the form theme. + * + * @param string $block The name of the block. + * @param array $variables The variables to pass to the template. + * + * @return string The HTML markup + */ + public function renderBlock($block, array $variables = array()); + + /** + * Renders a CSRF token. + * + * Use this helper for CSRF protection without the overhead of creating a + * form. + * + * + * + * + * + * Check the token in your action using the same intention. + * + * + * $csrfProvider = $this->get('form.csrf_provider'); + * if (!$csrfProvider->isCsrfTokenValid('rm_user_'.$user->getId(), $token)) { + * throw new \RuntimeException('CSRF attack detected.'); + * } + * + * + * @param string $intention The intention of the protected action + * + * @return string A CSRF token + */ + public function renderCsrfToken($intention); + + /** + * Returns whether the given choice is a group. + * + * @param mixed $choice A choice + * + * @return Boolean Whether the choice is a group + */ + public function isChoiceGroup($choice); + + /** + * Returns whether the given choice is selected. + * + * @param FormViewInterface $view The view of the choice field + * @param ChoiceView $choice The choice to check + * + * @return Boolean Whether the choice is selected + */ + public function isChoiceSelected(FormViewInterface $view, ChoiceView $choice); + + /** + * Makes a technical name human readable. + * + * Sequences of underscores are replaced by single spaces. The first letter + * of the resulting string is capitalized, while all other letters are + * turned to lowercase. + * + * @param string $text The text to humanize. + * + * @return string The humanized text. + */ + public function humanize($text); +} diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php index 01c8d96968900..bb97cd64a67fb 100644 --- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php @@ -545,7 +545,7 @@ public function testThemeInheritance($parentTheme, $childTheme) ] /following-sibling::div [ - ./label + ./label[.="child"] /following-sibling::div [ ./div diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php index 3ed8b5b3a0afa..a7d5f8b563b54 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php @@ -226,7 +226,7 @@ public function testSubmit_differentPattern() // Bug fix public function testInitializeWithDateTime() { - // Throws an exception if "data_class" option is not explicitely set + // Throws an exception if "data_class" option is not explicitly set // to null in the type $this->factory->create('datetime', new \DateTime()); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php index cd9921759750b..b618902b04da1 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php @@ -558,7 +558,7 @@ public function testPassWidgetToView() // Bug fix public function testInitializeWithDateTime() { - // Throws an exception if "data_class" option is not explicitely set + // Throws an exception if "data_class" option is not explicitly set // to null in the type $this->factory->create('date', new \DateTime()); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php index 8e6118123cfeb..4aed58dbcb8b9 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php @@ -403,7 +403,7 @@ public function testIsPartiallyFilled_returnsTrueIfChoiceAndSecondsEmpty() // Bug fix public function testInitializeWithDateTime() { - // Throws an exception if "data_class" option is not explicitely set + // Throws an exception if "data_class" option is not explicitly set // to null in the type $this->factory->create('time', new \DateTime()); } diff --git a/src/Symfony/Component/Form/Tests/FormRendererTest.php b/src/Symfony/Component/Form/Tests/FormRendererTest.php new file mode 100644 index 0000000000000..bf212c90495fa --- /dev/null +++ b/src/Symfony/Component/Form/Tests/FormRendererTest.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * @author Bernhard Schussek + */ +use Symfony\Component\Form\Extension\Core\View\ChoiceView; + +class FormRendererTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $engine; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $csrfProvider; + + /** + * @var FormRenderer + */ + private $renderer; + + protected function setUp() + { + $this->engine = $this->getMock('Symfony\Component\Form\FormRendererEngineInterface'); + $this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'); + $this->renderer = new FormRenderer($this->engine, $this->csrfProvider); + } + + public function isChoiceGroupProvider() + { + return array( + array(false, 0), + array(false, '0'), + array(false, '1'), + array(false, 1), + array(false, ''), + array(false, null), + array(false, true), + + array(true, array()), + ); + } + + /** + * @dataProvider isChoiceGroupProvider + */ + public function testIsChoiceGroup($expected, $value) + { + $this->assertSame($expected, $this->renderer->isChoiceGroup($value)); + } + + public function testIsChoiceGroupPart2() + { + $this->assertTrue($this->renderer->isChoiceGroup(new \SplFixedArray(1))); + } + + public function isChoiceSelectedProvider() + { + // The commented cases should not be necessary anymore, because the + // choice lists should assure that both values passed here are always + // strings + return array( +// array(true, 0, 0), + array(true, '0', '0'), + array(true, '1', '1'), +// array(true, false, 0), +// array(true, true, 1), + array(true, '', ''), +// array(true, null, ''), + array(true, '1.23', '1.23'), + array(true, 'foo', 'foo'), + array(true, 'foo10', 'foo10'), + array(true, 'foo', array(1, 'foo', 'foo10')), + + array(false, 10, array(1, 'foo', 'foo10')), + array(false, 0, array(1, 'foo', 'foo10')), + ); + } + + /** + * @dataProvider isChoiceSelectedProvider + */ + public function testIsChoiceSelected($expected, $choice, $value) + { + $view = new FormView('name'); + $view->setVar('value', $value); + $choice = new ChoiceView($choice, $choice . ' label'); + + $this->assertSame($expected, $this->renderer->isChoiceSelected($view, $choice)); + } +} diff --git a/src/Symfony/Component/Form/Tests/SimpleFormTest.php b/src/Symfony/Component/Form/Tests/SimpleFormTest.php index cf6e478dc4ccb..dcc2438a18bd3 100644 --- a/src/Symfony/Component/Form/Tests/SimpleFormTest.php +++ b/src/Symfony/Component/Form/Tests/SimpleFormTest.php @@ -364,7 +364,7 @@ public function testSetDataConvertsScalarToStringIfOnlyModelTransformer() /* * NULL remains NULL in app and norm format to remove the need to treat - * empty values and NULL explicitely in the application + * empty values and NULL explicitly in the application */ public function testSetDataConvertsNullToStringIfNoTransformer() { diff --git a/src/Symfony/Component/Form/Tests/Util/FormUtilTest.php b/src/Symfony/Component/Form/Tests/Util/FormUtilTest.php index f5d24cfb92878..3943e7ac28cd8 100644 --- a/src/Symfony/Component/Form/Tests/Util/FormUtilTest.php +++ b/src/Symfony/Component/Form/Tests/Util/FormUtilTest.php @@ -15,65 +15,6 @@ class FormUtilTest extends \PHPUnit_Framework_TestCase { - public function isChoiceGroupProvider() - { - return array( - array(false, 0), - array(false, '0'), - array(false, '1'), - array(false, 1), - array(false, ''), - array(false, null), - array(false, true), - - array(true, array()), - ); - } - - /** - * @dataProvider isChoiceGroupProvider - */ - public function testIsChoiceGroup($expected, $value) - { - $this->assertSame($expected, FormUtil::isChoiceGroup($value)); - } - - public function testIsChoiceGroupPart2() - { - $this->assertTrue(FormUtil::isChoiceGroup(new \SplFixedArray(1))); - } - - public function isChoiceSelectedProvider() - { - // The commented cases should not be necessary anymore, because the - // choice lists should assure that both values passed here are always - // strings - return array( -// array(true, 0, 0), - array(true, '0', '0'), - array(true, '1', '1'), -// array(true, false, 0), -// array(true, true, 1), - array(true, '', ''), -// array(true, null, ''), - array(true, '1.23', '1.23'), - array(true, 'foo', 'foo'), - array(true, 'foo10', 'foo10'), - array(true, 'foo', array(1, 'foo', 'foo10')), - - array(false, 10, array(1, 'foo', 'foo10')), - array(false, 0, array(1, 'foo', 'foo10')), - ); - } - - /** - * @dataProvider isChoiceSelectedProvider - */ - public function testIsChoiceSelected($expected, $choice, $value) - { - $this->assertSame($expected, FormUtil::isChoiceSelected($choice, $value)); - } - public function singularifyProvider() { // see http://english-zone.com/spelling/plurals.html diff --git a/src/Symfony/Component/Form/Util/FormUtil.php b/src/Symfony/Component/Form/Util/FormUtil.php index cf7d4d792ca67..614361c49b369 100644 --- a/src/Symfony/Component/Form/Util/FormUtil.php +++ b/src/Symfony/Component/Form/Util/FormUtil.php @@ -191,35 +191,6 @@ public static function singularify($plural) return $plural; } - /** - * Returns whether the given choice is a group. - * - * @param mixed $choice A choice - * - * @return Boolean Whether the choice is a group - */ - public static function isChoiceGroup($choice) - { - return is_array($choice) || $choice instanceof \Traversable; - } - - /** - * Returns whether the given choice is selected. - * - * @param mixed $choice The choice - * @param mixed $value the value - * - * @return Boolean Whether the choice is selected - */ - public static function isChoiceSelected($choice, $value) - { - if (is_array($value)) { - return false !== array_search($choice, $value, true); - } - - return $choice === $value; - } - /** * Returns whether the given data is empty. * diff --git a/src/Symfony/Component/Locale/Tests/Stub/StubIntlDateFormatterTest.php b/src/Symfony/Component/Locale/Tests/Stub/StubIntlDateFormatterTest.php index 2b3dd832c9612..fa902a133bdc0 100644 --- a/src/Symfony/Component/Locale/Tests/Stub/StubIntlDateFormatterTest.php +++ b/src/Symfony/Component/Locale/Tests/Stub/StubIntlDateFormatterTest.php @@ -491,7 +491,7 @@ public function testFormatWithDefaultTimezoneStubShouldUseTheTzEnvironmentVariab } /** - * It seems IntlDateFormatter caches the timezone id when not explicitely set via constructor or by the + * It seems IntlDateFormatter caches the timezone id when not explicitly set via constructor or by the * setTimeZoneId() method. Since testFormatWithDefaultTimezoneIntl() runs using the default environment * time zone, this test would use it too if not running in a separated process. *