[Form] Extracted common parts of FormHelper and FormExtension into separate classes

This commit is contained in:
Bernhard Schussek 2012-07-15 18:58:13 +02:00
parent 216c539e41
commit 629093ed25
57 changed files with 1572 additions and 856 deletions

View File

@ -1053,6 +1053,21 @@
$registry->addType($registry->resolveType(new MyFormType())); $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:
```
<?php echo $view['form']->renderBlock('widget_attributes') ?>
```
After:
```
<?php echo $view['form']->block('widget_attributes') ?>
```
### Validator ### Validator
* The methods `setMessage()`, `getMessageTemplate()` and * The methods `setMessage()`, `getMessageTemplate()` and

View File

@ -12,11 +12,9 @@
namespace Symfony\Bridge\Twig\Extension; namespace Symfony\Bridge\Twig\Extension;
use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser;
use Symfony\Component\Form\FormView; use Symfony\Bridge\Twig\Form\TwigRendererInterface;
use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\FormViewInterface;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; 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. * FormExtension extends Twig with form capabilities.
@ -26,21 +24,17 @@ use Symfony\Component\Form\Util\FormUtil;
*/ */
class FormExtension extends \Twig_Extension class FormExtension extends \Twig_Extension
{ {
protected $csrfProvider; /**
protected $resources; * This property is public so that it can be accessed directly from compiled
protected $blocks; * templates without having to call a getter, which slightly decreases performance.
protected $environment; *
protected $themes; * @var \Symfony\Component\Form\FormRendererInterface
protected $varStack; */
protected $template; public $renderer;
public function __construct(CsrfProviderInterface $csrfProvider = null, array $resources = array()) public function __construct(TwigRendererInterface $renderer)
{ {
$this->csrfProvider = $csrfProvider; $this->renderer = $renderer;
$this->themes = new \SplObjectStorage();
$this->varStack = array();
$this->blocks = new \SplObjectStorage();
$this->resources = $resources;
} }
/** /**
@ -48,25 +42,11 @@ class FormExtension extends \Twig_Extension
*/ */
public function initRuntime(\Twig_Environment $environment) public function initRuntime(\Twig_Environment $environment)
{ {
$this->environment = $environment; $this->renderer->setEnvironment($environment);
} }
/** /**
* Sets a theme for a given view. * {@inheritdoc}
*
* @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();
}
/**
* Returns the token parser instance to add to the existing list.
*
* @return array An array of Twig_TokenParser instances
*/ */
public function getTokenParsers() public function getTokenParsers()
{ {
@ -76,305 +56,39 @@ class FormExtension extends \Twig_Extension
); );
} }
/**
* {@inheritdoc}
*/
public function getFunctions() public function getFunctions()
{ {
return array( return array(
'form_enctype' => new \Twig_Function_Method($this, 'renderEnctype', 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, 'renderWidget', 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, 'renderErrors', 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, 'renderLabel', 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, 'renderRow', 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, 'renderRest', 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, 'getCsrfToken'), 'csrf_token' => new \Twig_Function_Method($this, 'renderer->renderCsrfToken'),
'_form_is_choice_group' => new \Twig_Function_Method($this, 'isChoiceGroup', array('is_safe' => array('html'))), '_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, 'isChoiceSelected', 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() public function getFilters()
{ {
return array( 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 * {@inheritdoc}
*
* Example usage in Twig templates:
*
* <form action="..." method="post" {{ form_enctype(form) }}>
*
* @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 "_<view id>_<section>",
* 2. if such a block is not found the function will look for a block named
* "<type name>_<section>",
* 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.
*
* <code>
* <input type="hidden" name="token" value="{{ csrf_token('rm_user_' ~ user.id) }}">
* </code>
*
* Check the token in your action using the same intention.
*
* <code>
* $csrfProvider = $this->get('form.csrf_provider');
* if (!$csrfProvider->isCsrfTokenValid('rm_user_'.$user->getId(), $token)) {
* throw new \RuntimeException('CSRF attack detected.');
* }
* </code>
*
* @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
*/ */
public function getName() public function getName()
{ {
return 'form'; 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))));
} }

View File

@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\Form;
use Symfony\Component\Form\FormRenderer;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
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);
}
}

View File

@ -0,0 +1,160 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\Form;
use Symfony\Component\Form\AbstractRendererEngine;
use Symfony\Component\Form\FormViewInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
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)
{
// 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)
}
}
// 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])) {
$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;
}
}
}
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;
}
foreach ($theme->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;
}
}
}
}

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\Form;
use Symfony\Component\Form\FormRendererEngineInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface TwigRendererEngineInterface extends FormRendererEngineInterface
{
/**
* Sets Twig's environment.
*
* @param \Twig_Environment $environment
*/
public function setEnvironment(\Twig_Environment $environment);
}

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\Form;
use Symfony\Component\Form\FormRendererInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface TwigRendererInterface extends FormRendererInterface
{
/**
* Sets Twig's environment.
*
* @param \Twig_Environment $environment
*/
public function setEnvironment(\Twig_Environment $environment);
}

View File

@ -30,7 +30,7 @@ class FormThemeNode extends \Twig_Node
{ {
$compiler $compiler
->addDebugInfo($this) ->addDebugInfo($this)
->write('echo $this->env->getExtension(\'form\')->setTheme(') ->write('echo $this->env->getExtension(\'form\')->renderer->setTheme(')
->subcompile($this->getNode('form')) ->subcompile($this->getNode('form'))
->raw(', ') ->raw(', ')
->subcompile($this->getNode('resources')) ->subcompile($this->getNode('resources'))

View File

@ -12,6 +12,8 @@
namespace Symfony\Bridge\Twig\Tests\Extension; namespace Symfony\Bridge\Twig\Tests\Extension;
use Symfony\Bridge\Twig\Extension\FormExtension; 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\Extension\TranslationExtension;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader;
@ -20,6 +22,9 @@ use Symfony\Component\Form\Tests\AbstractDivLayoutTest;
class FormExtensionDivLayoutTest extends AbstractDivLayoutTest class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
{ {
/**
* @var FormExtension
*/
protected $extension; protected $extension;
protected function setUp() protected function setUp()
@ -42,20 +47,23 @@ class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
parent::setUp(); parent::setUp();
$loader = new StubFilesystemLoader(array( $rendererEngine = new TwigRendererEngine(array(
__DIR__.'/../../../../../../src/Symfony/Bridge/Twig/Resources/views/Form',
__DIR__,
));
$this->extension = new FormExtension($this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'), array(
'form_div_layout.html.twig', 'form_div_layout.html.twig',
'custom_widgets.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 = new \Twig_Environment($loader, array('strict_variables' => true));
$environment->addExtension($this->extension);
$environment->addExtension(new TranslationExtension(new StubTranslator())); $environment->addExtension(new TranslationExtension(new StubTranslator()));
$environment->addGlobal('global', ''); $environment->addGlobal('global', '');
$environment->addExtension($this->extension);
$this->extension->initRuntime($environment); $this->extension->initRuntime($environment);
} }
@ -99,37 +107,37 @@ class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
protected function renderEnctype(FormView $view) 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()) 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) 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()) 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()) 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()) 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) protected function setTheme(FormView $view, array $themes)
{ {
$this->extension->setTheme($view, $themes); $this->extension->renderer->setTheme($view, $themes);
} }
public static function themeBlockInheritanceProvider() public static function themeBlockInheritanceProvider()

View File

@ -12,6 +12,8 @@
namespace Symfony\Bridge\Twig\Tests\Extension; namespace Symfony\Bridge\Twig\Tests\Extension;
use Symfony\Component\Form\FormView; 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\FormExtension;
use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Component\Form\Tests\AbstractTableLayoutTest; use Symfony\Component\Form\Tests\AbstractTableLayoutTest;
@ -20,6 +22,9 @@ use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader;
class FormExtensionTableLayoutTest extends AbstractTableLayoutTest class FormExtensionTableLayoutTest extends AbstractTableLayoutTest
{ {
/**
* @var FormExtension
*/
protected $extension; protected $extension;
protected function setUp() protected function setUp()
@ -42,20 +47,23 @@ class FormExtensionTableLayoutTest extends AbstractTableLayoutTest
parent::setUp(); parent::setUp();
$loader = new StubFilesystemLoader(array( $rendererEngine = new TwigRendererEngine(array(
__DIR__.'/../../../../../../src/Symfony/Bridge/Twig/Resources/views/Form',
__DIR__,
));
$this->extension = new FormExtension($this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'), array(
'form_table_layout.html.twig', 'form_table_layout.html.twig',
'custom_widgets.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 = new \Twig_Environment($loader, array('strict_variables' => true));
$environment->addExtension($this->extension);
$environment->addExtension(new TranslationExtension(new StubTranslator())); $environment->addExtension(new TranslationExtension(new StubTranslator()));
$environment->addGlobal('global', ''); $environment->addGlobal('global', '');
$environment->addExtension($this->extension);
$this->extension->initRuntime($environment); $this->extension->initRuntime($environment);
} }
@ -69,36 +77,36 @@ class FormExtensionTableLayoutTest extends AbstractTableLayoutTest
protected function renderEnctype(FormView $view) 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()) 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) 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()) 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()) 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()) 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) protected function setTheme(FormView $view, array $themes)
{ {
$this->extension->setTheme($view, $themes); $this->extension->renderer->setTheme($view, $themes);
} }
} }

View File

@ -15,6 +15,8 @@
<parameter key="templating.helper.code.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\CodeHelper</parameter> <parameter key="templating.helper.code.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\CodeHelper</parameter>
<parameter key="templating.helper.translator.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper</parameter> <parameter key="templating.helper.translator.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper</parameter>
<parameter key="templating.helper.form.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper</parameter> <parameter key="templating.helper.form.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper</parameter>
<parameter key="templating.form.engine.class">Symfony\Component\Form\Extension\Templating\TemplatingRendererEngine</parameter>
<parameter key="templating.form.renderer.class">Symfony\Component\Form\FormRenderer</parameter>
<parameter key="templating.globals.class">Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables</parameter> <parameter key="templating.globals.class">Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables</parameter>
<parameter key="templating.asset.path_package.class">Symfony\Bundle\FrameworkBundle\Templating\Asset\PathPackage</parameter> <parameter key="templating.asset.path_package.class">Symfony\Bundle\FrameworkBundle\Templating\Asset\PathPackage</parameter>
<parameter key="templating.asset.url_package.class">Symfony\Component\Templating\Asset\UrlPackage</parameter> <parameter key="templating.asset.url_package.class">Symfony\Component\Templating\Asset\UrlPackage</parameter>
@ -96,11 +98,19 @@
<service id="templating.helper.form" class="%templating.helper.form.class%"> <service id="templating.helper.form" class="%templating.helper.form.class%">
<tag name="templating.helper" alias="form" /> <tag name="templating.helper" alias="form" />
<argument type="service" id="templating.form.renderer" />
</service>
<service id="templating.form.engine" class="%templating.form.engine.class%" public="false">
<argument type="service" id="templating.engine.php" /> <argument type="service" id="templating.engine.php" />
<argument type="service" id="form.csrf_provider" on-invalid="null" />
<argument>%templating.helper.form.resources%</argument> <argument>%templating.helper.form.resources%</argument>
</service> </service>
<service id="templating.form.renderer" class="%templating.form.renderer.class%" public="false">
<argument type="service" id="templating.form.engine" />
<argument type="service" id="form.csrf_provider" on-invalid="null" />
</service>
<service id="templating.globals" class="%templating.globals.class%"> <service id="templating.globals" class="%templating.globals.class%">
<argument type="service" id="service_container" /> <argument type="service" id="service_container" />
</service> </service>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('widget_attributes') ?> <?php echo $view['form']->block('widget_attributes') ?>

View File

@ -1,5 +1,5 @@
<input type="checkbox" <input type="checkbox"
<?php echo $view['form']->renderBlock('widget_attributes') ?> <?php echo $view['form']->block('widget_attributes') ?>
<?php if ($value): ?> value="<?php echo $view->escape($value) ?>"<?php endif ?> <?php if ($value): ?> value="<?php echo $view->escape($value) ?>"<?php endif ?>
<?php if ($checked): ?> checked="checked"<?php endif ?> <?php if ($checked): ?> checked="checked"<?php endif ?>
/> />

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('choice_widget_options') ?> <?php echo $view['form']->block('choice_widget_options') ?>

View File

@ -1,5 +1,5 @@
<?php if ($expanded): ?> <?php if ($expanded): ?>
<?php echo $view['form']->renderBlock('choice_widget_expanded') ?> <?php echo $view['form']->block('choice_widget_expanded') ?>
<?php else: ?> <?php else: ?>
<?php echo $view['form']->renderBlock('choice_widget_collapsed') ?> <?php echo $view['form']->block('choice_widget_collapsed') ?>
<?php endif ?> <?php endif ?>

View File

@ -1,13 +1,13 @@
<select <select
<?php echo $view['form']->renderBlock('widget_attributes') ?> <?php echo $view['form']->block('widget_attributes') ?>
<?php if ($multiple): ?> multiple="multiple"<?php endif ?> <?php if ($multiple): ?> multiple="multiple"<?php endif ?>
> >
<?php if (null !== $empty_value): ?><option value=""><?php echo $view->escape($view['translator']->trans($empty_value, array(), $translation_domain)) ?></option><?php endif; ?> <?php if (null !== $empty_value): ?><option value=""><?php echo $view->escape($view['translator']->trans($empty_value, array(), $translation_domain)) ?></option><?php endif; ?>
<?php if (count($preferred_choices) > 0): ?> <?php if (count($preferred_choices) > 0): ?>
<?php echo $view['form']->renderBlock('choice_widget_options', array('options' => $preferred_choices)) ?> <?php echo $view['form']->block('choice_widget_options', array('options' => $preferred_choices)) ?>
<?php if (count($choices) > 0 && null !== $separator): ?> <?php if (count($choices) > 0 && null !== $separator): ?>
<option disabled="disabled"><?php echo $separator ?></option> <option disabled="disabled"><?php echo $separator ?></option>
<?php endif ?> <?php endif ?>
<?php endif ?> <?php endif ?>
<?php echo $view['form']->renderBlock('choice_widget_options', array('options' => $choices)) ?> <?php echo $view['form']->block('choice_widget_options', array('options' => $choices)) ?>
</select> </select>

View File

@ -1,4 +1,4 @@
<div <?php echo $view['form']->renderBlock('widget_container_attributes') ?>> <div <?php echo $view['form']->block('widget_container_attributes') ?>>
<?php foreach ($form as $child): ?> <?php foreach ($form as $child): ?>
<?php echo $view['form']->widget($child) ?> <?php echo $view['form']->widget($child) ?>
<?php echo $view['form']->label($child) ?> <?php echo $view['form']->label($child) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('widget_container_attributes') ?> <?php echo $view['form']->block('widget_container_attributes') ?>

View File

@ -1,7 +1,7 @@
<?php if ($widget == 'single_text'): ?> <?php if ($widget == 'single_text'): ?>
<?php echo $view['form']->renderBlock('form_widget_simple'); ?> <?php echo $view['form']->block('form_widget_simple'); ?>
<?php else: ?> <?php else: ?>
<div <?php echo $view['form']->renderBlock('widget_container_attributes') ?>> <div <?php echo $view['form']->block('widget_container_attributes') ?>>
<?php echo str_replace(array('{{ year }}', '{{ month }}', '{{ day }}'), array( <?php echo str_replace(array('{{ year }}', '{{ month }}', '{{ day }}'), array(
$view['form']->widget($form['year']), $view['form']->widget($form['year']),
$view['form']->widget($form['month']), $view['form']->widget($form['month']),

View File

@ -1,7 +1,7 @@
<?php if ($widget == 'single_text'): ?> <?php if ($widget == 'single_text'): ?>
<?php echo $view['form']->renderBlock('form_widget_simple'); ?> <?php echo $view['form']->block('form_widget_simple'); ?>
<?php else: ?> <?php else: ?>
<div <?php echo $view['form']->renderBlock('widget_container_attributes') ?>> <div <?php echo $view['form']->block('widget_container_attributes') ?>>
<?php echo $view['form']->widget($form['date']).' '.$view['form']->widget($form['time']) ?> <?php echo $view['form']->widget($form['date']).' '.$view['form']->widget($form['time']) ?>
</div> </div>
<?php endif ?> <?php endif ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : 'email')) ?> <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : 'email')) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_enctype') ?> <?php echo $view['form']->block('form_enctype') ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_errors') ?> <?php echo $view['form']->block('form_errors') ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_label') ?> <?php echo $view['form']->block('form_label') ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_rest') ?> <?php echo $view['form']->block('form_rest') ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_row') ?> <?php echo $view['form']->block('form_row') ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_rows') ?> <?php echo $view['form']->block('form_rows') ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple') ?> <?php echo $view['form']->block('form_widget_simple') ?>

View File

@ -1,5 +1,5 @@
<?php if ($compound): ?> <?php if ($compound): ?>
<?php echo $view['form']->renderBlock('form_widget_compound')?> <?php echo $view['form']->block('form_widget_compound')?>
<?php else: ?> <?php else: ?>
<?php echo $view['form']->renderBlock('form_widget_simple')?> <?php echo $view['form']->block('form_widget_simple')?>
<?php endif ?> <?php endif ?>

View File

@ -1,4 +1,4 @@
<div <?php echo $view['form']->renderBlock('widget_container_attributes') ?>> <div <?php echo $view['form']->block('widget_container_attributes') ?>>
<?php if (!$form->hasParent() && $errors): ?> <?php if (!$form->hasParent() && $errors): ?>
<tr> <tr>
<td colspan="2"> <td colspan="2">
@ -6,6 +6,6 @@
</td> </td>
</tr> </tr>
<?php endif ?> <?php endif ?>
<?php echo $view['form']->renderBlock('form_rows') ?> <?php echo $view['form']->block('form_rows') ?>
<?php echo $view['form']->rest($form) ?> <?php echo $view['form']->rest($form) ?>
</div> </div>

View File

@ -1,5 +1,5 @@
<input <input
type="<?php echo isset($type) ? $view->escape($type) : 'text' ?>" type="<?php echo isset($type) ? $view->escape($type) : 'text' ?>"
<?php if (!empty($value)): ?>value="<?php echo $view->escape($value) ?>"<?php endif ?> <?php if (!empty($value)): ?>value="<?php echo $view->escape($value) ?>"<?php endif ?>
<?php echo $view['form']->renderBlock('widget_attributes') ?> <?php echo $view['form']->block('widget_attributes') ?>
/> />

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "hidden")) ?> <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : "hidden")) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "number")) ?> <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : "number")) ?>

View File

@ -1 +1 @@
<?php echo str_replace('{{ widget }}', $view['form']->renderBlock('form_widget_simple'), $money_pattern) ?> <?php echo str_replace('{{ widget }}', $view['form']->block('form_widget_simple'), $money_pattern) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "text")) ?> <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : "text")) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "password")) ?> <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : "password")) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "text")) ?> % <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : "text")) ?> %

View File

@ -1,5 +1,5 @@
<input type="radio" <input type="radio"
<?php echo $view['form']->renderBlock('widget_attributes') ?> <?php echo $view['form']->block('widget_attributes') ?>
value="<?php echo $view->escape($value) ?>" value="<?php echo $view->escape($value) ?>"
<?php if ($checked): ?> checked="checked"<?php endif ?> <?php if ($checked): ?> checked="checked"<?php endif ?>
/> />

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_rows') ?> <?php echo $view['form']->block('form_rows') ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "search")) ?> <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : "search")) ?>

View File

@ -1 +1 @@
<textarea <?php echo $view['form']->renderBlock('widget_attributes') ?>><?php echo $view->escape($value) ?></textarea> <textarea <?php echo $view['form']->block('widget_attributes') ?>><?php echo $view->escape($value) ?></textarea>

View File

@ -1,7 +1,7 @@
<?php if ($widget == 'single_text'): ?> <?php if ($widget == 'single_text'): ?>
<?php echo $view['form']->renderBlock('form_widget_simple'); ?> <?php echo $view['form']->block('form_widget_simple'); ?>
<?php else: ?> <?php else: ?>
<div <?php echo $view['form']->renderBlock('widget_container_attributes') ?>> <div <?php echo $view['form']->block('widget_container_attributes') ?>>
<?php <?php
// There should be no spaces between the colons and the widgets, that's why // There should be no spaces between the colons and the widgets, that's why
// this block is written in a single PHP tag // this block is written in a single PHP tag

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('form_widget_simple', array('type' => isset($type) ? $type : "url")) ?> <?php echo $view['form']->block('form_widget_simple', array('type' => isset($type) ? $type : "url")) ?>

View File

@ -1,7 +1,7 @@
<table <?php echo $view['form']->renderBlock('widget_container_attributes') ?>> <table <?php echo $view['form']->block('widget_container_attributes') ?>>
<?php if (!$form->hasParent()): ?> <?php if (!$form->hasParent()): ?>
<?php echo $view['form']->errors($form) ?> <?php echo $view['form']->errors($form) ?>
<?php endif ?> <?php endif ?>
<?php echo $view['form']->renderBlock('form_rows') ?> <?php echo $view['form']->block('form_rows') ?>
<?php echo $view['form']->rest($form) ?> <?php echo $view['form']->rest($form) ?>
</table> </table>

View File

@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Templating\Helper; namespace Symfony\Bundle\FrameworkBundle\Templating\Helper;
use Symfony\Component\Templating\Helper\Helper; use Symfony\Component\Templating\Helper\Helper;
use Symfony\Component\Form\FormRendererInterface;
use Symfony\Component\Form\FormViewInterface; use Symfony\Component\Form\FormViewInterface;
use Symfony\Component\Templating\EngineInterface; use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\Exception\FormException;
@ -28,77 +29,34 @@ use Symfony\Component\Form\Util\FormUtil;
class FormHelper extends Helper class FormHelper extends Helper
{ {
/** /**
* @var EngineInterface * @var FormRendererInterface
*/ */
private $engine; private $renderer;
/** /**
* @var CsrfProviderInterface * @param FormRendererInterface $renderer
*/ */
private $csrfProvider; public function __construct(FormRendererInterface $renderer)
/**
* @var array
*/
private $blockHierarchyMap = array();
/**
* @var array
*/
private $currentHierarchyLevelMap = array();
/**
* @var array
*/
private $variableMap = array();
/**
* @var array
*/
private $stack = array();
/**
* @var array
*/
private $defaultThemes;
/**
* @var array
*/
private $themes = array();
/**
* @var array
*/
private $templateCache = array();
/**
* @var array
*/
private $templateHierarchyLevelCache = array();
/**
* Constructor.
*
* @param EngineInterface $engine The templating engine
* @param CsrfProviderInterface $csrfProvider The CSRF provider
* @param array $defaultThemes An array of theme names
*/
public function __construct(EngineInterface $engine, CsrfProviderInterface $csrfProvider = null, array $defaultThemes = array())
{ {
$this->engine = $engine; $this->renderer = $renderer;
$this->csrfProvider = $csrfProvider; }
$this->defaultThemes = $defaultThemes;
/**
* {@inheritdoc}
*/
public function getName()
{
return 'form';
} }
public function isChoiceGroup($label) public function isChoiceGroup($label)
{ {
return FormUtil::isChoiceGroup($label); return $this->renderer->isChoiceGroup($label);
} }
public function isChoiceSelected(FormViewInterface $view, ChoiceView $choice) public function isChoiceSelected(FormViewInterface $view, ChoiceView $choice)
{ {
return FormUtil::isChoiceSelected($choice->getValue(), $view->getVar('value')); return $this->renderer->isChoiceSelected($view, $choice);
} }
/** /**
@ -111,9 +69,7 @@ class FormHelper extends Helper
*/ */
public function setTheme(FormViewInterface $view, $themes) public function setTheme(FormViewInterface $view, $themes)
{ {
$this->themes[$view->getVar('full_block_name')] = (array) $themes; $this->renderer->setTheme($view, $themes);
$this->templateCache = array();
$this->templateHierarchyLevelCache = array();
} }
/** /**
@ -129,7 +85,7 @@ class FormHelper extends Helper
*/ */
public function enctype(FormViewInterface $view) public function enctype(FormViewInterface $view)
{ {
return $this->renderSection($view, 'enctype'); return $this->renderer->renderEnctype($view);
} }
/** /**
@ -146,44 +102,40 @@ class FormHelper extends Helper
* <?php echo view['form']->widget(array('separator' => '+++++)) ?> * <?php echo view['form']->widget(array('separator' => '+++++)) ?>
* *
* @param FormViewInterface $view The view for which to render the widget * @param FormViewInterface $view The view for which to render the widget
* @param array $variables Additional variables passed to the template * @param array $variables Additional variables passed to the template
* *
* @return string The HTML markup * @return string The HTML markup
*/ */
public function widget(FormViewInterface $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". * Renders the entire form field "row".
* *
* @param FormViewInterface $view The view for which to render the row * @param FormViewInterface $view The view for which to render the row
* @param array $variables Additional variables passed to the template * @param array $variables Additional variables passed to the template
* *
* @return string The HTML markup * @return string The HTML markup
*/ */
public function row(FormViewInterface $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. * Renders the label of the given view.
* *
* @param FormViewInterface $view The view for which to render the label * @param FormViewInterface $view The view for which to render the label
* @param string $label The label * @param string $label The label
* @param array $variables Additional variables passed to the template * @param array $variables Additional variables passed to the template
* *
* @return string The HTML markup * @return string The HTML markup
*/ */
public function label(FormViewInterface $view, $label = null, array $variables = array()) public function label(FormViewInterface $view, $label = null, array $variables = array())
{ {
if ($label !== null) { return $this->renderer->renderLabel($view, $label, $variables);
$variables += array('label' => $label);
}
return $this->renderSection($view, 'label', $variables);
} }
/** /**
@ -195,20 +147,49 @@ class FormHelper extends Helper
*/ */
public function errors(FormViewInterface $view) public function errors(FormViewInterface $view)
{ {
return $this->renderSection($view, 'errors'); return $this->renderer->renderErrors($view);
} }
/** /**
* Renders views which have not already been rendered. * Renders views which have not already been rendered.
* *
* @param FormViewInterface $view The parent view * @param FormViewInterface $view The parent view
* @param array $variables An array of variables * @param array $variables An array of variables
* *
* @return string The HTML markup * @return string The HTML markup
*/ */
public function rest(FormViewInterface $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);
} }
/** /**
@ -238,304 +219,11 @@ class FormHelper extends Helper
*/ */
public function csrfToken($intention) public function csrfToken($intention)
{ {
if (!$this->csrfProvider instanceof CsrfProviderInterface) { return $this->renderer->renderCsrfToken($intention);
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 "_<view id>_<section>",
* 2. if such a block is not found the function will look for a block named
* "<type name>_<section>",
* 3. the type name is recursively replaced by the parent type name until a
* corresponding block is found
*
* @param FormViewInterface $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(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 = $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 explicitely fallback to "form_widget" in his
// custom "choice_widget", for example because he only wants to wrap
// a <div> 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 (= "_<id>_<section>" block)
$blockHierarchy = array_map(function ($type) use ($section) {
return $type . '_' . $section;
}, $view->getVar('types'));
$blockHierarchy[] = $view->getVar('full_block_name') . '_' . $section;
$currentHierarchyLevel = count($blockHierarchy) - 1;
// The default variable scope contains all view variables, merged with
// the variables passed explicitely to the helper
$variables = array_replace_recursive($view->getVars(), $variables);
} else {
// RECURSIVE CALL
// If a block recursively calls renderSection() again, resume rendering
// using the parent type in the hierarchy.
$blockHierarchy = $this->blockHierarchyMap[$mapKey];
$currentHierarchyLevel = $this->currentHierarchyLevelMap[$mapKey] - 1;
// Reuse the current scope and merge it with the explicitely passed variables
$variables = array_replace_recursive($this->variableMap[$mapKey], $variables);
}
$cacheKey = $view->getVar('full_block_name');
$block = $blockHierarchy[$currentHierarchyLevel];
// Populate the cache if the template for the block is not known yet
if (!isset($this->templateCache[$cacheKey][$block])) {
$this->loadTemplateForBlockHierarchy($view, $blockHierarchy, $currentHierarchyLevel);
}
// Escape if no template exists for this block
if (!$this->templateCache[$cacheKey][$block]) {
throw new FormException(sprintf(
'Unable to render the form as none of the following blocks exist: "%s".',
implode('", "', array_reverse($blockHierarchy))
));
}
// If $block was previously rendered manually with renderBlock(), the template
// is cached but the hierarchy level is not. In this case, we know that the block
// exists at this very hierarchy level (renderBlock() does not traverse the hierarchy)
// so we can just set it.
if (!isset($this->templateHierarchyLevelCache[$cacheKey][$block])) {
$this->templateHierarchyLevelCache[$cacheKey][$block] = $currentHierarchyLevel;
}
// 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->currentHierarchyLevelMap[$mapKey] = $this->templateHierarchyLevelCache[$cacheKey][$block];
$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->render($this->templateCache[$cacheKey][$block], $variables);
// Clear the stack
array_pop($this->stack);
// Clear the maps
unset($this->blockHierarchyMap[$mapKey]);
unset($this->currentHierarchyLevelMap[$mapKey]);
unset($this->variableMap[$mapKey]);
if ($renderOnlyOnce) {
$view->setRendered();
}
return trim($html);
}
public function renderBlock($block, $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);
$cacheKey = $view->getVar('full_block_name');
if (!isset($this->templateCache[$cacheKey][$block]) && !$this->loadTemplateForBlock($view, $block)) {
throw new FormException(sprintf('No block "%s" found while rendering the form.', $block));
}
$variables = array_replace_recursive($scopeVariables, $variables);
return trim($this->engine->render($this->templateCache[$cacheKey][$block], $variables));
} }
public function humanize($text) public function humanize($text)
{ {
return ucfirst(trim(strtolower(preg_replace('/[_\s]+/', ' ', $text)))); return $this->renderer->humanize($text);
}
public function getName()
{
return 'form';
}
/**
* Loads the cache with the template for a specific level of a block hierarchy.
*
* For example, the block hierarchy could be:
*
* <code>
* form_widget
* choice_widget
* entity_widget
* </code
*
* When loading this hierarchy at index 1, the method first tries to find the
* block "choice_widget" in any of the themes assigned to $view. If nothing is
* found, it then continues to look for "form_widget" and so on.
*
* This method both stores the template name and the level in the hierarchy at
* which the template was found in the cache. In the above example, if the
* template "MyBundle:choice_widget.html.php" was found at level 1, this template
* and the level "1" are stored. The stored level helps to resume rendering
* in recursive calls, where the parent block needs to be rendered (here the
* block "form_widget" at level 0).
*
* @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 $currentLevel The level in the block hierarchy that should be loaded.
*
* @return Boolean True if the cache could be populated successfully, false otherwise.
*/
private function loadTemplateForBlockHierarchy(FormViewInterface $view, array $blockHierarchy, $currentLevel)
{
$cacheKey = $view->getVar('full_block_name');
$block = $blockHierarchy[$currentLevel];
// Try to find a template for that block
if ($this->loadTemplateForBlock($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->templateHierarchyLevelCache[$cacheKey][$block] = $currentLevel;
return true;
}
if ($currentLevel > 0) {
$parentLevel = $currentLevel - 1;
$parentBlock = $blockHierarchy[$parentLevel];
if ($this->loadTemplateForBlockHierarchy($view, $blockHierarchy, $parentLevel)) {
// Cache the shortcuts for further accesses
$this->templateCache[$cacheKey][$block] = $this->templateCache[$cacheKey][$parentBlock];
$this->templateHierarchyLevelCache[$cacheKey][$block] = $this->templateHierarchyLevelCache[$cacheKey][$parentBlock];
return true;
}
}
// Cache the result for further accesses
$this->templateCache[$cacheKey][$block] = false;
$this->templateHierarchyLevelCache[$cacheKey][$block] = false;
return false;
}
/**
* Loads the cache with the template for a given block name.
*
* The template is first searched in all the themes assigned to $view. If nothing
* is found, the search is continued in the themes of the parent view. Once arrived
* at the root view, if still nothing has been found, the default themes stored
* in this class are searched.
*
* @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 cache could be populated successfully, false otherwise.
*/
private function loadTemplateForBlock(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.
$cacheKey = $view->getVar('full_block_name');
// Check the default themes once we reach the root form without success
$themes = $view->hasParent() ? array() : $this->defaultThemes;
// Add the themes that have been registered for that specific element
if (isset($this->themes[$cacheKey])) {
$themes = array_merge($themes, $this->themes[$cacheKey]);
}
// Check each theme whether it contains the searched block
for ($i = count($themes) - 1; $i >= 0; --$i) {
if ($this->engine->exists($templateName = $themes[$i] . ':' . $block . '.html.php')) {
$this->templateCache[$cacheKey][$block] = $templateName;
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('full_block_name');
if (!isset($this->templateCache[$parentCacheKey][$block])) {
$this->loadTemplateForBlock($view->getParent(), $block);
}
// If a template exists in the parent themes, cache that template name
// for the current theme as well to speed up further accesses
if ($this->templateCache[$parentCacheKey][$block]) {
$this->templateCache[$cacheKey][$block] = $this->templateCache[$parentCacheKey][$block];
return true;
}
}
// Cache that we didn't find anything to speed up further accesses
$this->templateCache[$cacheKey][$block] = false;
return false;
} }
} }

View File

@ -15,9 +15,11 @@ use Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper;
use Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper; use Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper;
use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTemplateNameParser; use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTemplateNameParser;
use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTranslator; use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTranslator;
use Symfony\Component\Form\FormView;
use Symfony\Component\Templating\PhpEngine; use Symfony\Component\Templating\PhpEngine;
use Symfony\Component\Templating\Loader\FilesystemLoader; 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; use Symfony\Component\Form\Tests\AbstractDivLayoutTest;
class FormHelperDivLayoutTest extends AbstractDivLayoutTest class FormHelperDivLayoutTest extends AbstractDivLayoutTest
@ -34,8 +36,10 @@ class FormHelperDivLayoutTest extends AbstractDivLayoutTest
$loader = new FilesystemLoader(array()); $loader = new FilesystemLoader(array());
$engine = new PhpEngine($templateNameParser, $loader); $engine = new PhpEngine($templateNameParser, $loader);
$engine->addGlobal('global', ''); $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( $engine->setHelpers(array(
$this->helper, $this->helper,

View File

@ -16,9 +16,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\StubTemplateNameParser;
use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTranslator; use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTranslator;
use Symfony\Component\Form\FormView; 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\PhpEngine;
use Symfony\Component\Templating\Loader\FilesystemLoader; use Symfony\Component\Templating\Loader\FilesystemLoader;
use Symfony\Component\Form\Tests\AbstractTableLayoutTest;
class FormHelperTableLayoutTest extends AbstractTableLayoutTest class FormHelperTableLayoutTest extends AbstractTableLayoutTest
{ {
@ -34,11 +36,13 @@ class FormHelperTableLayoutTest extends AbstractTableLayoutTest
$loader = new FilesystemLoader(array()); $loader = new FilesystemLoader(array());
$engine = new PhpEngine($templateNameParser, $loader); $engine = new PhpEngine($templateNameParser, $loader);
$engine->addGlobal('global', ''); $engine->addGlobal('global', '');
$rendererEngine = new TemplatingRendererEngine($engine, array(
$this->helper = new FormHelper($engine, $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'), array(
'FrameworkBundle:Form', 'FrameworkBundle:Form',
'FrameworkBundle:FormTable' 'FrameworkBundle:FormTable'
)); ));
$renderer = new FormRenderer($rendererEngine, $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'));
$this->helper = new FormHelper($renderer);
$engine->setHelpers(array( $engine->setHelpers(array(
$this->helper, $this->helper,

View File

@ -16,6 +16,8 @@
<parameter key="twig.extension.routing.class">Symfony\Bridge\Twig\Extension\RoutingExtension</parameter> <parameter key="twig.extension.routing.class">Symfony\Bridge\Twig\Extension\RoutingExtension</parameter>
<parameter key="twig.extension.yaml.class">Symfony\Bridge\Twig\Extension\YamlExtension</parameter> <parameter key="twig.extension.yaml.class">Symfony\Bridge\Twig\Extension\YamlExtension</parameter>
<parameter key="twig.extension.form.class">Symfony\Bridge\Twig\Extension\FormExtension</parameter> <parameter key="twig.extension.form.class">Symfony\Bridge\Twig\Extension\FormExtension</parameter>
<parameter key="twig.form.engine.class">Symfony\Bridge\Twig\Form\TwigRendererEngine</parameter>
<parameter key="twig.form.renderer.class">Symfony\Bridge\Twig\Form\TwigRenderer</parameter>
<parameter key="twig.translation.extractor.class">Symfony\Bridge\Twig\Translation\TwigExtractor</parameter> <parameter key="twig.translation.extractor.class">Symfony\Bridge\Twig\Translation\TwigExtractor</parameter>
<parameter key="twig.exception_listener.class">Symfony\Component\HttpKernel\EventListener\ExceptionListener</parameter> <parameter key="twig.exception_listener.class">Symfony\Component\HttpKernel\EventListener\ExceptionListener</parameter>
</parameters> </parameters>
@ -75,10 +77,18 @@
<service id="twig.extension.form" class="%twig.extension.form.class%" public="false"> <service id="twig.extension.form" class="%twig.extension.form.class%" public="false">
<tag name="twig.extension" /> <tag name="twig.extension" />
<argument type="service" id="form.csrf_provider" on-invalid="null" /> <argument type="service" id="twig.form.renderer" />
</service>
<service id="twig.form.engine" class="%twig.form.engine.class%" public="false">
<argument>%twig.form.resources%</argument> <argument>%twig.form.resources%</argument>
</service> </service>
<service id="twig.form.renderer" class="%twig.form.renderer.class%" public="false">
<argument type="service" id="twig.form.engine" />
<argument type="service" id="form.csrf_provider" on-invalid="null" />
</service>
<service id="twig.translation.extractor" class="%twig.translation.extractor.class%"> <service id="twig.translation.extractor" class="%twig.translation.extractor.class%">
<argument type="service" id="twig" /> <argument type="service" id="twig" />
<tag name="translation.extractor" alias="twig" /> <tag name="translation.extractor" alias="twig" />

View File

@ -0,0 +1,202 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form;
/**
* Default implementation of {@link FormRendererEngineInterface}.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
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);
$this->resources[$cacheKey] = array();
$this->resourceHierarchyLevels[$cacheKey] = array();
}
/**
* {@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;
}
}

View File

@ -31,7 +31,6 @@ CHANGELOG
* ArrayToChoicesTransformer to ChoicesToValuesTransformer * ArrayToChoicesTransformer to ChoicesToValuesTransformer
* ScalarToChoiceTransformer to ChoiceToValueTransformer * ScalarToChoiceTransformer to ChoiceToValueTransformer
to be consistent with the naming in ChoiceListInterface. 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. They were merged into ChoiceList and have no public equivalent anymore.
* choice fields now throw a FormException if neither the "choices" nor the * choice fields now throw a FormException if neither the "choices" nor the
"choice_list" option is set "choice_list" option is set
@ -172,4 +171,11 @@ CHANGELOG
* ChoiceType now caches its created choice lists to improve performance * 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 * [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. 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. * [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

View File

@ -0,0 +1,125 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\Templating;
use Symfony\Component\Form\AbstractRendererEngine;
use Symfony\Component\Form\FormViewInterface;
use Symfony\Component\Templating\EngineInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
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;
}
}

View File

@ -0,0 +1,324 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\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 <bschussek@gmail.com>
*/
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));
}
$variables = array_replace_recursive($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 explicitely fallback to "form_widget" in his
// custom "choice_widget", for example because he only wants to wrap
// a <div> 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 (= "_<id>_<section>" 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 explicitely to the helper
$variables = array_replace_recursive($view->getVars(), $variables);
} 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 explicitely passed variables
$variables = array_replace_recursive($this->variableMap[$mapKey], $variables);
}
// 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))
));
}
// 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;
}
}

View File

@ -0,0 +1,146 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form;
/**
* Adapter for rendering form templates with a specific templating engine.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
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:
*
* <code>
* form_widget
* text_widget
* url_widget
* </code>
*
* 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:
*
* <code>
* form_widget
* text_widget
* url_widget
* </code>
*
* 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());
}

View File

@ -0,0 +1,180 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
/**
* Renders a form into HTML.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
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:
*
* <form action="..." method="post" <?php echo $renderer->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:
*
* <?php echo $renderer->renderWidget($form) ?>
*
* You can pass options during the call:
*
* <?php echo $renderer->renderWidget($form, array('attr' => array('class' => 'foo'))) ?>
*
* <?php echo $renderer->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.
*
* <code>
* <input type="hidden" name="token" value="<?php $renderer->renderCsrfToken('rm_user_'.$user->getId()) ?>">
* </code>
*
* Check the token in your action using the same intention.
*
* <code>
* $csrfProvider = $this->get('form.csrf_provider');
* if (!$csrfProvider->isCsrfTokenValid('rm_user_'.$user->getId(), $token)) {
* throw new \RuntimeException('CSRF attack detected.');
* }
* </code>
*
* @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);
}

View File

@ -545,7 +545,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
] ]
/following-sibling::div /following-sibling::div
[ [
./label ./label[.="child"]
/following-sibling::div /following-sibling::div
[ [
./div ./div

View File

@ -0,0 +1,105 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
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));
}
}

View File

@ -15,65 +15,6 @@ use Symfony\Component\Form\Util\FormUtil;
class FormUtilTest extends \PHPUnit_Framework_TestCase 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() public function singularifyProvider()
{ {
// see http://english-zone.com/spelling/plurals.html // see http://english-zone.com/spelling/plurals.html

View File

@ -191,35 +191,6 @@ abstract class FormUtil
return $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. * Returns whether the given data is empty.
* *