[Form][FrameworkBundle][TwigBundle] Refactored the PHP and Twig templating layer

Support for theming in PHP templates has been dropped.

True theming should support theme inheritance, e.g. mytheme <- table <- default.
Currently, the Templating component does not support such inheritance. As the
only purpose of the themes so far was to style field groups with tables or
divs, and because automatic rendering of field groups/forms through the render()
method is discouraged and only recommended for rapid prototyping, themes are
dropped for now.
This commit is contained in:
Bernhard Schussek 2010-11-15 12:57:41 +01:00 committed by Fabien Potencier
parent 23d7967f81
commit 1bbdb5ec07
62 changed files with 614 additions and 1193 deletions

View File

@ -20,8 +20,7 @@
<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.security.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\SecurityHelper</parameter>
<parameter key="templating.form.class">Symfony\Bundle\FrameworkBundle\Templating\Form\Form</parameter>
<parameter key="templating.html.generator.class">Symfony\Bundle\FrameworkBundle\Templating\HtmlGenerator</parameter>
<parameter key="templating.helper.form.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper</parameter>
<parameter key="templating.output_escaper">false</parameter>
<parameter key="templating.assets.version">null</parameter>
<parameter key="templating.assets.base_urls" type="collection"></parameter>
@ -29,8 +28,6 @@
</parameters>
<services>
<service id="templating.html.generator" class="%templating.html.generator.class%" />
<service id="templating.engine" class="%templating.engine.class%">
<argument type="service" id="service_container" />
<argument type="service" id="templating.loader" />
@ -111,9 +108,9 @@
<argument type="service" id="security.context" on-invalid="ignore" />
</service>
<service id="templating.form" class="%templating.form.class%">
<service id="templating.helper.form" class="%templating.helper.form.class%">
<tag name="templating.helper" alias="form" />
<argument type="service" id="templating" />
<argument type="service" id="templating.html.generator" />
</service>
<service id="templating.loader" alias="templating.loader.filesystem" />

View File

@ -0,0 +1,7 @@
<input type="checkbox"
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
value="<?php echo $field->getDisplayedData() ?>"
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
<?php if ($field->isChecked()): ?>checked="checked"<?php endif ?>
/>

View File

@ -0,0 +1,47 @@
<?php if ($field->isExpanded()): ?>
<?php foreach ($field as $choice => $child): ?>
<?php echo $view['form']->render($child) ?>
<label for="<?php echo $child->getId() ?>"><?php echo $field->getLabel($choice) ?></label>
<?php endforeach ?>
<?php else: ?>
<select
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
<?php if ($field->isDisabled()): ?> disabled="disabled"<?php endif ?>
<?php if ($field->isMultipleChoice()): ?> multiple="multiple"<?php endif ?>
>
<?php if (count($field->getPreferredChoices()) > 0): ?>
<?php foreach ($field->getPreferredChoices() as $choice => $label): ?>
<?php if ($label instanceof \Traversable): ?>
<optgroup label="<?php echo $choice ?>">
<?php foreach ($label as $nestedChoice => $nestedLabel): ?>
<option value="<?php echo $nestedChoice ?>"<?php if ($field->isChoiceSelected($nestedChoice)): ?> selected="selected"<?php endif?>>
<?php echo $nestedLabel ?>
</option>
<?php endforeach ?>
</optgroup>
<?php else: ?>
<option value="<?php echo $choice ?>"<?php if ($field->isChoiceSelected($choice)): ?> selected="selected"<?php endif?>>
<?php echo $label ?>
</option>
<?php endif ?>
<?php endforeach ?>
<option disabled="disabled"><?php echo isset($separator) ? $separator : '-----------------' ?></option>
<?php endif ?>
<?php foreach ($field->getOtherChoices() as $choice => $label): ?>
<?php if ($label instanceof \Traversable): ?>
<optgroup label="<?php echo $choice ?>">
<?php foreach ($label as $nestedChoice => $nestedLabel): ?>
<option value="<?php echo $nestedChoice ?>"<?php if ($field->isChoiceSelected($nestedChoice)): ?> selected="selected"<?php endif?>>
<?php echo $nestedLabel ?>
</option>
<?php endforeach ?>
</optgroup>
<?php else: ?>
<option value="<?php echo $choice ?>"<?php if ($field->isChoiceSelected($choice)): ?> selected="selected"<?php endif?>>
<?php echo $label ?>
</option>
<?php endif ?>
<?php endforeach ?>
</select>
<?php endif ?>

View File

@ -0,0 +1,14 @@
<?php if ($field->isField()): ?>
<input type="text"
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
value="<?php echo $field->getDisplayedData() ?>"
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
/>
<?php else: ?>
<?php echo str_replace(array('{{ year }}', '{{ month }}', '{{ day }}'), array(
$view['form']->render($field['year']),
$view['form']->render($field['month']),
$view['form']->render($field['day']),
), $field->getPattern()) ?>
<?php endif ?>

View File

@ -0,0 +1,3 @@
<?php echo $view['form']->render($field['date']) ?>
<!-- keep space -->
<?php echo $view['form']->render($field['time']) ?>

View File

@ -1,7 +1,7 @@
<?php if ($errors): ?>
<?php if ($field->hasErrors()): ?>
<ul>
<?php foreach ($errors as $error): ?>
<?php foreach ($field->getErrors() as $error): ?>
<li><?php echo $view['translator']->trans($error[0], $error[1], 'validators') ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php endif ?>

View File

@ -0,0 +1,13 @@
<?php echo $view['form']->errors($field) ?>
<div>
<?php foreach ($field->getVisibleFields() as $child): ?>
<div>
<?php echo $view['form']->label($child) ?>
<?php echo $view['form']->errors($child) ?>
<?php echo $view['form']->render($child) ?>
</div>
<?php endforeach; ?>
</div>
<?php echo $view['form']->hidden($field) ?>

View File

@ -0,0 +1,5 @@
<input type="file"
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
/>

View File

@ -1,9 +0,0 @@
<?php echo $group->errors() ?>
<div>
<?php foreach ($group as $field): ?>
<?php echo $field->render() ?>
<?php endforeach; ?>
</div>
<?php echo $group->hidden() ?>

View File

@ -1,5 +0,0 @@
<div>
<?php echo $field->label() ?>
<?php echo $field->errors() ?>
<?php echo $field->field() ?>
</div>

View File

@ -1,9 +0,0 @@
<?php echo $group->errors() ?>
<table>
<?php foreach ($group as $field): ?>
<?php echo $field->render() ?>
<?php endforeach; ?>
</table>
<?php echo $group->hidden() ?>

View File

@ -1,9 +0,0 @@
<tr>
<th>
<?php echo $field->label() ?>
</th>
<td>
<?php echo $field->errors() ?>
<?php echo $field->widget() ?>
</td>
</tr>

View File

@ -1,3 +1,3 @@
<?php foreach ($hidden as $field): ?>
<?php echo $field->widget() ?>
<?php foreach ($field->getAllHiddenFields() as $child): ?>
<?php echo $view['form']->render($child) ?>
<?php endforeach; ?>

View File

@ -0,0 +1,6 @@
<input type="hidden"
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
value="<?php echo $field->getDisplayedData() ?>"
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
/>

View File

@ -1 +1 @@
<label for="<?php echo $id ?>"><?php echo $view['translator']->trans($label) ?></label>
<label for="<?php echo $field->getId() ?>"><?php echo $view['translator']->trans($label) ?></label>

View File

@ -0,0 +1,4 @@
<?php echo str_replace('{{ widget }}',
$view['form']->render($field, array(), array(), 'FrameworkBundle:Form:number_field.php'),
$field->getPattern()
) ?>

View File

@ -0,0 +1,6 @@
<input type="text"
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
value="<?php echo $field->getDisplayedData() ?>"
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
/>

View File

@ -0,0 +1 @@
<?php echo $view['form']->render($field, array(), array(), 'FrameworkBundle:Form:number_field.php') ?> %

View File

@ -0,0 +1,7 @@
<input type="radio"
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
value="<?php echo $field->getDisplayedData() ?>"
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
<?php if ($field->isChecked()): ?>checked="checked"<?php endif ?>
/>

View File

@ -0,0 +1,8 @@
<input type="text"
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
value="<?php echo $field->getDisplayedData() ?>"
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
<?php // FIXME if (!isset($attr['maxlength']) && $field->getMaxLength() > 0) $attr['maxlength'] = $field->getMaxLength() ?>
<?php echo $view['form']->attributes($attr) ?>
/>

View File

@ -0,0 +1,3 @@
<textarea id="<?php echo $field->getId() ?>" name="<?php echo $field->getName() ?>" <?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>>
<?php echo $view->escape($field->getDisplayedData()) ?>
</textarea>

View File

@ -0,0 +1,12 @@
<?php
// There should be no spaces between the colons and the widgets, that's why
// this block is written in a single PHP tag
echo $view['form']->render($field['hour'], array('size' => 1));
echo ':';
echo $view['form']->render($field['minute'], array('size' => 1));
if ($field->isWithSeconds()) {
echo ':';
echo $view['form']->render($field['second'], array('size' => 1));
}
?>

View File

@ -1,9 +0,0 @@
<?php if ($origin->getOption('expanded')): ?>
<?php foreach ($field as $child): ?>
<?php echo $child->widget() ?>
<?php endforeach; ?>
<?php else: ?>
<?php echo $generator->contentTag('select',
$generator->choices($origin->getPreferredChoices(), $origin->getOtherChoices(), $origin->getEmptyValue(), $origin->getSelected()),
$attributes) ?>
<?php endif; ?>

View File

@ -1,9 +0,0 @@
<?php if ($origin->isField()): ?>
<?php echo $generator->tag('input', $attributes) ?>
<?php else: ?>
<?php echo str_replace(array('{{ year }}', '{{ month }}', '{{ day }}'), array(
$field['year']->widget($attributes),
$field['month']->widget($attributes),
$field['day']->widget($attributes),
), $origin->getPattern()) ?>
<?php endif; ?>

View File

@ -1,2 +0,0 @@
<?php echo $field['date']->widget($attributes) ?>
<?php echo $field['time']->widget($attributes) ?>

View File

@ -1 +0,0 @@
<?php echo $generator->tag('input', $attributes) ?>

View File

@ -1,6 +0,0 @@
<?php echo str_replace('{{ widget }}', $view->render('FrameworkBundle:Form:widget/input_field.php', array(
'field' => $field,
'origin' => $origin,
'attributes' => $attributes,
'generator' => $generator,
)), $origin->getPattern()) ?>

View File

@ -1,6 +0,0 @@
<?php echo $view->render('FrameworkBundle:Form:widget/input_field.php', array(
'field' => $field,
'origin' => $origin,
'attributes' => $attributes,
'generator' => $generator,
)) ?> %

View File

@ -1 +0,0 @@
<?php echo $generator->contentTag('textarea', $view->escape($origin->getDisplayedData()), $attributes) ?>

View File

@ -1,9 +0,0 @@
<?php if ($origin->isField()): ?>
<?php echo $generator->tag('input', $attributes) ?>
<?php else: ?>
<?php echo $field['hour']->widget($attributes).':'.$field['minute']->widget($attributes) ?>
<?php if ($origin->getOption('with_seconds')): ?>
<?php echo ':'.$field['second']->widget($attributes) ?>
<?php endif; ?>
<?php endif; ?>

View File

@ -1,11 +0,0 @@
<?php echo $view->render('FrameworkBundle:Form:widget/input_field.php', array(
'field' => $field,
'origin' => $origin,
'attributes' => $attributes,
'generator' => $generator,
))
?>
<?php if ($label = $origin->getOption('label')): ?>
<?php echo $generator->contentTag('label', $view['translator']->trans($label), array('for' => $origin->getId())) ?>
<?php endif; ?>

View File

@ -1,132 +0,0 @@
<?php
namespace Symfony\Bundle\FrameworkBundle\Templating\Form;
use Symfony\Component\Templating\Engine;
use Symfony\Component\Form\FieldInterface as FormFieldInterface;
use Symfony\Component\Form\HybridField;
use Symfony\Component\Form\FieldGroupInterface;
use Symfony\Bundle\FrameworkBundle\Templating\HtmlGeneratorInterface;
use Symfony\Component\OutputEscaper\SafeDecoratorInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
*
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
abstract class BaseField implements FieldInterface, SafeDecoratorInterface
{
protected $engine;
protected $field;
protected $generator;
protected $theme;
protected $doctype;
public function __construct(FormFieldInterface $field, Engine $engine, HtmlGeneratorInterface $generator, $theme, $doctype)
{
$this->field = $field;
$this->engine = $engine;
$this->generator = $generator;
$this->theme = $theme;
}
public function getIterator()
{
if (!$this->field instanceof FieldGroupInterface) {
throw new \LogicException(sprintf('Cannot iterate a non group field (%s)', $this->field->getKey()));
}
$fields = array();
foreach ($this->field->getFields() as $field) {
if (!$field->isHidden()) {
$fields[] = $field;
}
}
return new \ArrayIterator($this->wrapFields($fields));
}
/**
* Returns true if the bound field exists (implements the \ArrayAccess interface).
*
* @param string $key The key of the bound field
*
* @return Boolean true if the widget exists, false otherwise
*/
public function offsetExists($key)
{
if (!$this->field instanceof FieldGroupInterface) {
throw new \LogicException(sprintf('Cannot access a non group field as an array (%s)', $this->field->getKey()));
}
return $this->field->has($key);
}
/**
* Returns the form field associated with the name (implements the \ArrayAccess interface).
*
* @param string $key The offset of the value to get
*
* @return Field A form field instance
*/
public function offsetGet($key)
{
if (!$this->field instanceof FieldGroupInterface) {
throw new \LogicException(sprintf('Cannot access a non group field as an array (%s)', $this->field->getKey()));
}
return $this->createField($this->field->get($key));
}
/**
* Throws an exception saying that values cannot be set (implements the \ArrayAccess interface).
*
* @param string $offset (ignored)
* @param string $value (ignored)
*
* @throws \LogicException
*/
public function offsetSet($key, $field)
{
throw new \LogicException('This helper is read-only');
}
/**
* Throws an exception saying that values cannot be unset (implements the \ArrayAccess interface).
*
* @param string $key
*
* @throws \LogicException
*/
public function offsetUnset($key)
{
throw new \LogicException('This helper is read-only');
}
protected function wrapFields($fields)
{
foreach ($fields as $id => $field) {
$fields[$id] = $this->createField($field);
}
return $fields;
}
protected function createField(FormFieldInterface $field)
{
if ($field instanceof FieldGroupInterface && !$field instanceof HybridField) {
return new FieldGroup($field, $this->engine, $this->generator, $this->theme, $this->doctype);
}
return new Field($field, $this->engine, $this->generator, $this->theme, $this->doctype);
}
}

View File

@ -1,107 +0,0 @@
<?php
namespace Symfony\Bundle\FrameworkBundle\Templating\Form;
use Symfony\Component\Form\FieldGroupInterface;
use Symfony\Component\Form\HybridField;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Field wraps a Form\FieldInterface instance.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Field extends BaseField
{
static protected $cache = array();
public function render($template = null)
{
if ($this->field instanceof FieldGroupInterface && !$this->field instanceof HybridField) {
throw new \LogicException(sprintf('Cannot render a group field as a row (%s)', $this->field->getKey()));
}
if (null === $template) {
$template = sprintf('FrameworkBundle:Form:group/%s/row.php', $this->theme);
}
return $this->engine->render($template, array('field' => $this));
}
public function data()
{
return $this->field->getData();
}
public function widget(array $attributes = array(), $template = null)
{
if ($this->field instanceof FieldGroupInterface && !$this->field instanceof HybridField) {
throw new \LogicException(sprintf('Cannot render a group field (%s)', $this->field->getKey()));
}
if (null === $template) {
$template = $this->getTemplate();
}
return $this->engine->render($template, array(
'field' => $this,
'origin' => $this->field,
'attributes' => array_merge($this->field->getAttributes(), $attributes),
'generator' => $this->generator,
));
}
public function label($label = false, $template = null)
{
if (null === $template) {
$template = 'FrameworkBundle:Form:label.php';
}
return $this->engine->render($template, array(
'field' => $this,
'id' => $this->field->getId(),
'key' => $this->field->getKey(),
'label' => $label ? $label : ucfirst(strtolower(str_replace('_', ' ', $this->field->getKey())))
));
}
public function errors($template = null)
{
if (null === $template) {
$template = 'FrameworkBundle:Form:errors.php';
}
return $this->engine->render($template, array('field' => $this, 'errors' => $this->field->getErrors()));
}
protected function getTemplate()
{
$class = get_class($this->field);
if (isset(self::$cache[$class])) {
return self::$cache[$class];
}
// find a template for the given class or one of its parents
do {
$parts = explode('\\', $class);
$c = array_pop($parts);
$underscore = strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), strtr($c, '_', '.')));
if ($this->engine->exists($template = 'FrameworkBundle:Form:widget/'.$underscore.'.php')) {
return self::$cache[$class] = $template;
}
} while (false !== $class = get_parent_class($class));
throw new \RuntimeException(sprintf('Unable to find a template to render the "%s" widget.', $this->field->getKey()));
}
}

View File

@ -1,75 +0,0 @@
<?php
namespace Symfony\Bundle\FrameworkBundle\Templating\Form;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* FieldGroup wraps a Form\FieldGroupInterface instance.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class FieldGroup extends BaseField
{
/**
* Renders the form tag.
*
* This method only renders the opening form tag.
* You need to close it after the form rendering.
*
* This method takes into account the multipart widgets.
*
* @param string $url The URL for the action
* @param array $attributes An array of HTML attributes
*
* @return string An HTML representation of the opening form tag
*/
public function form($url, array $attributes = array())
{
return sprintf('<form%s>', $this->generator->attributes(array_merge(array(
'action' => $url,
'method' => isset($attributes['method']) ? strtolower($attributes['method']) : 'post',
'enctype' => $this->field->isMultipart() ? 'multipart/form-data' : null,
), $attributes)));
}
public function render($template = null)
{
if (null === $template) {
$template = sprintf('FrameworkBundle:Form:group/%s/field_group.php', $this->theme);
}
return $this->engine->render($template, array('group' => $this));
}
public function hidden($template = null)
{
if (null === $template) {
$template = 'FrameworkBundle:Form:hidden.php';
}
return $this->engine->render($template, array(
'group' => $this,
'hidden' => $this->wrapFields($this->field->getHiddenFields(true))
));
}
public function errors($template = null)
{
if (null === $template) {
$template = 'FrameworkBundle:Form:errors.php';
}
return $this->engine->render($template, array(
'group' => $this,
'errors' => $this->field->getErrors()
));
}
}

View File

@ -1,22 +0,0 @@
<?php
namespace Symfony\Bundle\FrameworkBundle\Templating\Form;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
*
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
interface FieldInterface extends \IteratorAggregate, \ArrayAccess
{
function render($template = null);
}

View File

@ -1,44 +0,0 @@
<?php
namespace Symfony\Bundle\FrameworkBundle\Templating\Form;
use Symfony\Component\Templating\Helper\Helper;
use Symfony\Component\Templating\Engine;
use Symfony\Component\Form\FieldGroupInterface;
use Symfony\Bundle\FrameworkBundle\Templating\HtmlGeneratorInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Form is a factory that wraps Form instances.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Form
{
public $generator;
protected $engine;
protected $theme;
protected $doctype;
public function __construct(Engine $engine, HtmlGeneratorInterface $generator, $theme = 'table', $doctype = 'xhtml')
{
$this->engine = $engine;
$this->generator = $generator;
$this->theme = $theme;
$this->doctype = $doctype;
}
public function get(FieldGroupInterface $group, $theme = null, $doctype = null)
{
return new FieldGroup($group, $this->engine, $this->generator, null === $theme ? $this->theme : $theme, null === $doctype ? $this->doctype : $doctype);
}
}

View File

@ -0,0 +1,183 @@
<?php
namespace Symfony\Bundle\FrameworkBundle\Templating\Helper;
use Symfony\Component\Templating\Helper\Helper;
use Symfony\Component\Templating\Engine;
use Symfony\Component\Form\FieldInterface;
use Symfony\Component\Form\FieldGroupInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Form is a factory that wraps Form instances.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
class FormHelper extends Helper
{
static protected $cache = array();
protected $engine;
public function __construct(Engine $engine)
{
$this->engine = $engine;
}
public function getName()
{
return 'form';
}
public function attributes($attributes)
{
if ($attributes instanceof \Traversable) {
$attributes = iterator_to_array($attributes);
}
return implode('', array_map(array($this, 'attributesCallback'), array_keys($attributes), array_values($attributes)));
}
private function attribute($name, $value)
{
return sprintf('%s="%s"', $name, true === $value ? $name : $value);
}
/**
* Prepares an attribute key and value for HTML representation.
*
* It removes empty attributes, except for the value one.
*
* @param string $name The attribute name
* @param string $value The attribute value
*
* @return string The HTML representation of the HTML key attribute pair.
*/
private function attributesCallback($name, $value)
{
if (false === $value || null === $value || ('' === $value && 'value' != $name)) {
return '';
} else {
return ' '.$this->attribute($name, $value);
}
}
/**
* Renders the form tag.
*
* This method only renders the opening form tag.
* You need to close it after the form rendering.
*
* This method takes into account the multipart widgets.
*
* @param string $url The URL for the action
* @param array $attributes An array of HTML attributes
*
* @return string An HTML representation of the opening form tag
*/
public function enctype(/*Form */$form)
{
return $form->isMultipart() ? ' enctype="multipart/form-data"' : '';
}
public function render(/*FieldInterface */$field, array $attributes = array(), array $parameters = array(), $template = null)
{
if (null === $template) {
$template = $this->lookupTemplate($field);
if (null === $template) {
throw new \RuntimeException(sprintf('Unable to find a template to render the "%s" widget.', $field->getKey()));
}
}
return trim($this->engine->render($template, array(
'field' => $field,
'attr' => $attributes,
'params' => $parameters,
)));
}
public function label(/*FieldInterface */$field, $label = false, array $parameters = array(), $template = null)
{
if (null === $template) {
$template = 'FrameworkBundle:Form:label.php';
}
return $this->engine->render($template, array(
'field' => $field,
'params' => $parameters,
'label' => $label ? $label : ucfirst(strtolower(str_replace('_', ' ', $field->getKey())))
));
}
public function errors(/*FieldInterface */$field, array $parameters = array(), $template = null)
{
if (null === $template) {
$template = 'FrameworkBundle:Form:errors.php';
}
return $this->engine->render($template, array(
'field' => $field,
'params' => $parameters,
));
}
public function hidden(/*FieldGroupInterface */$group, array $parameters = array(), $template = null)
{
if (null === $template) {
$template = 'FrameworkBundle:Form:hidden.php';
}
return $this->engine->render($template, array(
'field' => $group,
'params' => $parameters,
));
}
protected function lookupTemplate(/*FieldInterface */$field)
{
if ($field instanceof \Symfony\Component\OutputEscaper\ObjectDecorator) {
$field = $field->getRawValue();
}
$fqClassName = get_class($field);
$template = null;
if (isset(self::$cache[$fqClassName])) {
return self::$cache[$fqClassName];
}
// find a template for the given class or one of its parents
$currentFqClassName = $fqClassName;
do {
$parts = explode('\\', $currentFqClassName);
$className = array_pop($parts);
$underscoredName = strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), strtr($className, '_', '.')));
if ($this->engine->exists($guess = 'FrameworkBundle:Form:'.$underscoredName.'.php')) {
$template = $guess;
}
$currentFqClassName = get_parent_class($currentFqClassName);
} while (null === $template && false !== $currentFqClassName);
if (null === $template && $field instanceof FieldGroupInterface) {
$template = 'FrameworkBundle:Form:field_group.php';
}
self::$cache[$fqClassName] = $template;
return $template;
}
}

View File

@ -1,171 +0,0 @@
<?php
namespace Symfony\Bundle\FrameworkBundle\Templating;
/**
* An implementation of HtmlGeneratorInterface
*
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class HtmlGenerator implements HtmlGeneratorInterface
{
/**
* Whether to produce XHTML compliant code
* @var boolean
*/
protected static $xhtml = true;
/**
* The charset used during generating
* @var string
*/
protected $charset;
/**
* Sets the charset used for rendering
*
* @param string $charset
*/
public function __construct($charset = 'UTF-8')
{
$this->charset = $charset;
}
/**
* Sets the XHTML generation flag.
*
* @param bool $boolean true if renderers must be generated as XHTML, false otherwise
*/
static public function setXhtml($boolean)
{
self::$xhtml = (boolean) $boolean;
}
/**
* Returns whether to generate XHTML tags or not.
*
* @return bool true if renderers must be generated as XHTML, false otherwise
*/
static public function isXhtml()
{
return self::$xhtml;
}
/**
* {@inheritDoc}
*/
public function tag($tag, $attributes = array())
{
if (empty($tag)) {
return '';
}
return sprintf('<%s%s%s', $tag, $this->attributes($attributes), self::$xhtml ? ' />' : (strtolower($tag) == 'input' ? '>' : sprintf('></%s>', $tag)));
}
/**
* {@inheritDoc}
*/
public function contentTag($tag, $content = null, $attributes = array())
{
if (empty($tag)) {
return '';
}
return sprintf('<%s%s>%s</%s>', $tag, $this->attributes($attributes), $content, $tag);
}
/**
* {@inheritDoc}
*/
public function attribute($name, $value)
{
if (true === $value) {
return self::$xhtml ? sprintf('%s="%s"', $name, $this->escape($name)) : $this->escape($name);
} else {
return sprintf('%s="%s"', $name, $this->escape($value));
}
}
/**
* {@inheritDoc}
*/
public function attributes(array $attributes)
{
return implode('', array_map(array($this, 'attributesCallback'), array_keys($attributes), array_values($attributes)));
}
public function choices(array $preferredChoices, array $choices, $empty, array $selected)
{
$html = '';
if (false !== $empty) {
$html .= $this->doChoices(array('' => $empty), $selected)."\n";
}
if (count($preferredChoices) > 0) {
$html .= $this->doChoices($preferredChoices, $selected)."\n";
$html .= $this->contentTag('option', $origin->getOption('separator'), array('disabled' => true))."\n";
}
$html .= $this->doChoices($choices, $selected)."\n";
return $html;
}
protected function doChoices(array $choices, array $selected)
{
$options = array();
foreach ($choices as $key => $option) {
if (is_array($option)) {
$options[] = $this->contentTag(
'optgroup',
"\n".$this->doChoices($option, $selected)."\n",
array('label' => $this->escape($key))
);
} else {
$attributes = array('value' => $this->escape($key));
if (isset($selected[strval($key)])) {
$attributes['selected'] = true;
}
$options[] = $this->contentTag(
'option',
$this->escape($option),
$attributes
);
}
}
return implode("\n", $options);
}
/**
* Prepares an attribute key and value for HTML representation.
*
* It removes empty attributes, except for the value one.
*
* @param string $name The attribute name
* @param string $value The attribute value
*
* @return string The HTML representation of the HTML key attribute pair.
*/
private function attributesCallback($name, $value)
{
if (false === $value || null === $value || ('' === $value && 'value' != $name)) {
return '';
} else {
return ' '.$this->attribute($name, $value);
}
}
/**
* {@inheritDoc}
*/
public function escape($value)
{
return htmlspecialchars((string) $value, ENT_QUOTES, $this->charset, false);
}
}

View File

@ -1,63 +0,0 @@
<?php
namespace Symfony\Bundle\FrameworkBundle\Templating;
/**
* Marks classes able to generate HTML code
*
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
interface HtmlGeneratorInterface
{
/**
* Escapes a value for safe output in HTML
*
* Double escaping of already-escaped sequences is avoided by this method.
*
* @param string $value The unescaped or partially escaped value
*
* @return string The fully escaped value
*/
function escape($value);
/**
* Generates the HTML code for a tag attribute
*
* @param string $name The attribute name
* @param string $value The attribute value
*
* @return string The HTML code of the attribute
*/
function attribute($name, $value);
/**
* Generates the HTML code for multiple tag attributes
*
* @param array $attributes An array with attribute names as keys and
* attribute values as elements
*
* @return string The HTML code of the attribute list
*/
function attributes(array $attributes);
/**
* Generates the HTML code for a tag without content
*
* @param string $tag The name of the tag
* @param array $attributes The attributes for the tag
*
* @return string The HTML code for the tag
*/
function tag($tag, $attributes = array());
/**
* Generates the HTML code for a tag with content
*
* @param string $tag The name of the tag
* @param string $content The content of the tag
* @param array $attributes The attributes for the tag
*
* @return string The HTML code for the tag
*/
function contentTag($tag, $content, $attributes = array());
}

View File

@ -22,6 +22,7 @@ use Symfony\Bundle\FrameworkBundle\Templating\HtmlGeneratorInterface;
/**
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
class FormExtension extends \Twig_Extension
{
@ -31,15 +32,12 @@ class FormExtension extends \Twig_Extension
protected $templates;
protected $environment;
protected $themes;
protected $generator;
public function __construct(HtmlGeneratorInterface $generator, array $resources = array())
public function __construct(array $resources = array())
{
$this->generator = $generator;
$this->themes = new \SplObjectStorage();
$this->resources = array_merge(array(
'TwigBundle::form.twig',
'TwigBundle::widgets.twig',
), $resources);
}
@ -79,89 +77,51 @@ class FormExtension extends \Twig_Extension
'render' => new \Twig_Filter_Method($this, 'render', array('is_safe' => array('html'))),
'render_hidden' => new \Twig_Filter_Method($this, 'renderHidden', array('is_safe' => array('html'))),
'render_errors' => new \Twig_Filter_Method($this, 'renderErrors', array('is_safe' => array('html'))),
'render_widget' => new \Twig_Filter_Method($this, 'renderWidget', array('is_safe' => array('html'))),
'render_label' => new \Twig_Filter_Method($this, 'renderLabel', array('is_safe' => array('html'))),
'render_data' => new \Twig_Filter_Method($this, 'renderData', array('is_safe' => array('html'))),
'render_choices' => new \Twig_Filter_Method($this, 'renderChoices', array('is_safe' => array('html'))),
);
}
/**
* Renders the HTML enctype in the form tag, if necessary
*
* Example usage in Twig templates:
*
* <form action="..." method="post" {{ form|render_enctype }}>
*
* @param Form $form The form for which to render the encoding type
*/
public function renderEnctype(Form $form)
{
return $form->isMultipart() ? 'enctype="multipart/form-data"' : '';
}
public function render(FieldInterface $field, array $attributes = array())
/**
* Renders the HTML for an individual form field
*
* Example usage in Twig:
*
* {{ field|render }}
*
* You can pass additional variables during the call:
*
* {{ field|render(['param': 'value']) }}
*
* @param FieldInterface $field The field to render
* @param array $params Additional variables passed to the template
* @param string $resources
*/
public function render(FieldInterface $field, array $attributes = array(), array $parameters = array(), $resources = null)
{
if (null === $this->templates) {
$this->templates = $this->resolveResources($this->resources);
}
if ($field instanceof CollectionField) {
return $this->templates['group']->getBlock('collection', array(
'collection' => $field,
'attributes' => $attributes,
));
}
// FieldGroupInterface instances that need to be rendered as
// a field instead of group should return false in isGroup()
if ($field instanceof FieldGroupInterface && true === $field->isGroup()) {
return $this->templates['group']->getBlock('group', array(
'group' => $field,
'attributes' => $attributes,
));
}
return $this->templates['field']->getBlock('field', array(
'field' => $field,
'attributes' => $attributes,
));
}
public function renderHidden(FieldGroupInterface $form)
{
if (null === $this->templates) {
$this->templates = $this->resolveResources($this->resources);
}
return $this->templates['hidden']->getBlock('hidden', array(
'fields' => $form->getHiddenFields()
));
}
public function renderErrors($formOrField)
{
if (null === $this->templates) {
$this->templates = $this->resolveResources($this->resources);
}
return $this->templates['errors']->getBlock('errors', array(
'errors' => $formOrField->getErrors()
));
}
public function renderLabel(FieldInterface $field, $label = null, array $attributes = array())
{
if (null === $this->templates) {
$this->templates = $this->resolveResources($this->resources);
}
return $this->templates['label']->getBlock('label', array(
'id' => $field->getId(),
'key' => $field->getKey(),
'label' => null !== $label ? $label : ucfirst(strtolower(str_replace('_', ' ', $field->getKey()))),
'attributes' => $attributes,
));
}
public function renderWidget(FieldInterface $field, array $attributes = array(), $resources = null)
{
if (null === $this->templates) {
$this->templates = $this->resolveResources($this->resources);
}
if (null === $resources) {
if (null !== $resources) {
// The developer provided a custom theme in the filter call
$resources = array($resources);
} else {
// The default theme is used
$parent = $field;
$resources = array();
while ($parent = $parent->getParent()) {
@ -169,31 +129,71 @@ class FormExtension extends \Twig_Extension
$resources = $this->themes[$parent];
}
}
} else {
$resources = array($resources);
}
list($widget, $template) = $this->getWidget($field, $resources);
return $template->getBlock($widget, array(
'field' => $field,
'attributes' => array_merge($field->getAttributes(), $attributes),
'attr' => $attributes,
'params' => $parameters,
));
}
public function renderData(FieldInterface $field)
/**
* Renders all hidden fields of the given field group
*
* @param FieldGroupInterface $group The field group
* @param array $params Additional variables passed to the
* template
*/
public function renderHidden(FieldGroupInterface $group, array $parameters = array())
{
return $field->getData();
if (null === $this->templates) {
$this->templates = $this->resolveResources($this->resources);
}
public function renderChoices(FieldInterface $field)
return $this->templates['hidden']->getBlock('hidden', array(
'field' => $group,
'params' => $parameters,
));
}
/**
* Renders the errors of the given field
*
* @param FieldInterface $field The field to render the errors for
* @param array $params Additional variables passed to the template
*/
public function renderErrors(FieldInterface $field, array $parameters = array())
{
return $this->generator->choices(
$field->getPreferredChoices(),
$field->getOtherChoices(),
$field->getEmptyValue(),
$field->getSelected()
);
if (null === $this->templates) {
$this->templates = $this->resolveResources($this->resources);
}
return $this->templates['errors']->getBlock('errors', array(
'field' => $field,
'params' => $parameters,
));
}
/**
* Renders the label of the given field
*
* @param FieldInterface $field The field to render the label for
* @param array $params Additional variables passed to the template
*/
public function renderLabel(FieldInterface $field, $label = null, array $parameters = array())
{
if (null === $this->templates) {
$this->templates = $this->resolveResources($this->resources);
}
return $this->templates['label']->getBlock('label', array(
'field' => $field,
'params' => $parameters,
'label' => null !== $label ? $label : ucfirst(strtolower(str_replace('_', ' ', $field->getKey()))),
));
}
protected function getWidget(FieldInterface $field, array $resources = array())

View File

@ -1,62 +0,0 @@
<?php
namespace Symfony\Bundle\TwigBundle\Extension;
use Symfony\Bundle\FrameworkBundle\Templating\HtmlGeneratorInterface;
use Symfony\Bundle\TwigBundle\TokenParser\TagTokenParser;
use Symfony\Bundle\TwigBundle\TokenParser\ContentTagTokenParser;
use Symfony\Bundle\TwigBundle\TokenParser\ChoiceTagTokenParser;
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class HtmlExtension extends \Twig_Extension
{
protected $generator;
public function __construct(HtmlGeneratorInterface $generator)
{
$this->generator = $generator;
}
public function getGenerator()
{
return $this->generator;
}
/**
* Returns the token parser instance to add to the existing list.
*
* @return array An array of Twig_TokenParser instances
*/
public function getTokenParsers()
{
return array(
// {% tag "input" with attributes %}
new TagTokenParser(),
// {% contenttag "textarea" with attributes %}content{% endcontenttag %}
new ContentTagTokenParser(),
);
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName()
{
return 'html';
}
}

View File

@ -43,11 +43,6 @@
<argument type="service" id="translator" />
</service>
<service id="twig.extension.html" class="Symfony\Bundle\TwigBundle\Extension\HtmlExtension">
<tag name="twig.extension" />
<argument type="service" id="templating.html.generator" />
</service>
<service id="twig.extension.helpers" class="Symfony\Bundle\TwigBundle\Extension\TemplatingExtension">
<tag name="twig.extension" />
<argument type="service" id="service_container" />
@ -55,7 +50,6 @@
<service id="twig.extension.form" class="Symfony\Bundle\TwigBundle\Extension\FormExtension">
<tag name="twig.extension" />
<argument type="service" id="templating.html.generator" />
<argument>%twig.form.resources%</argument>
</service>

View File

@ -1,28 +1,16 @@
{% block group %}
{{ group|render_errors }}
{% for field in group %}
{% if not field.ishidden %}
{{ field|render }}
{% block field_group %}
{{ field|render_errors }}
{% for child in field %}
{% if not child.ishidden %}
<div>
{{ child|render_label }}
{{ child|render_errors }}
{{ child|render }}
</div>
{% endif %}
{% endfor %}
{{ group|render_hidden }}
{% endblock group %}
{% block field %}
<div>
{{ field|render_label }}
{{ field|render_errors }}
{{ field|render_widget }}
</div>
{% endblock field %}
{% block collection %}
<div>
{% for field in collection %}
{{ field|render }}
{% endfor %}
</div>
{% endblock collection %}
{{ field|render_hidden }}
{% endblock field_group %}
{% block errors %}
{% if errors %}
@ -35,11 +23,117 @@
{% endblock errors %}
{% block hidden %}
{% for field in fields %}
{{ field|render_widget }}
{% for child in field.allHiddenFields %}
{{ child|render }}
{% endfor %}
{% endblock hidden %}
{% block label %}
<label for="{{ id }}">{% trans label %}</label>
<label for="{{ field.id }}">{% trans label %}</label>
{% endblock label %}
{% block attributes %}
{% for key, value in attr %}
{{ key }}="{{ value}}"
{% endfor %}
{% endblock attributes %}
{% block field_attributes %}
id="{{ field.id }}" name="{{ field.name }}"{% if field.disabled %} disabled="disabled"{% endif %}
{% display attributes %}
{% endblock field_attributes %}
{% block text_field %}
{#{% set attr.maxlength = attr.maxlength|default(field.maxlength) %}#}
<input type="text" {% display field_attributes %} value="{{ field.displayedData }}" />
{% endblock text_field %}
{% block password_field %}
{#{% set attr.maxlength = attr.maxlength|default(field.maxlength) %}#}
<input type="password" {% display field_attributes %} value="{{ field.displayedData }}" />
{% endblock password_field %}
{% block hidden_field %}
<input type="hidden" {% display field_attributes %} value="{{ field.displayedData }}" />
{% endblock hidden_field %}
{% block textarea_field %}
<textarea {% display field_attributes %}>{{ field.displayedData }}</textarea>
{% endblock textarea_field %}
{% block options %}
{% for choice, label in options %}
{% if field.isChoiceGroup(label) %}
<optgroup label="{{ choice }}">
{% for nestedChoice, nestedLabel in label %}
<option value="{{ nestedChoice }}"{% if field.isChoiceSelected(nestedChoice) %} selected="selected"{% endif %}>{{ nestedLabel }}</option>
{% endfor %}
</optgroup>
{% else %}
<option value="{{ choice }}"{% if field.isChoiceSelected(choice) %} selected="selected"{% endif %}>{{ label }}</option>
{% endif %}
{% endfor %}
{% endblock options %}
{% block choice_field %}
{% if field.isExpanded %}
{% for choice, child in field %}
{{ child|render }}
<label for="{{ child.id }}">{{ field.label(choice) }}</label>
{% endfor %}
{% else %}
<select {% display field_attributes %}{% if field.isMultipleChoice %} multiple="multiple"{% endif %}>
{% if field.preferredChoices|length > 0 %}
{% set options = field.preferredChoices %}
{% display options %}
<option disabled="disabled">{{ params.separator|default('-------------------') }}</option>
{% endif %}
{% set options = field.otherChoices %}
{% display options %}
</select>
{% endif %}
{% endblock choice_field %}
{% block checkbox_field %}
<input type="checkbox" {% display field_attributes %}{% if field.hasValue %} value="{{ field.value }}"{% endif %}{% if field.ischecked %} checked="checked"{% endif %} />
{% endblock checkbox_field %}
{% block radio_field %}
<input type="radio" {% display field_attributes %}{% if field.hasValue %} value="{{ field.value }}"{% endif %}{% if field.ischecked %} checked="checked"{% endif %} />
{% endblock radio_field %}
{% block date_time_field %}
{{ field.date|render }}
{{ field.time|render }}
{% endblock date_time_field %}
{% block date_field %}
{% if field.isfield %}
{% display text_field %}
{% else %}
{{ field.pattern|replace(['{{ year }}': field.year|render, '{{ month }}': field.month|render, '{{ day }}': field.day|render]) }}
{% endif %}
{% endblock date_field %}
{% block time_field %}
{# TODO the next line should be set attr.size = 1, but that's not supported yet by Twig #}
{% if field.isfield %}{% set attr = ['size': 1] %}{% endif %}
{{ field.hour|render(attr) }}:{{ field.minute|render(attr) }}{% if field.isWithSeconds %}:{{ field.second|render(attr) }}{% endif %}
{% endblock time_field %}
{% block number_field %}
<input type="text" {% display field_attributes %} value="{{ field.displayedData }}" />
{% endblock number_field %}
{% block money_field %}
{% set widget %}{% display number_field %}{% endset %}
{{ field.pattern|replace(['{{ widget }}': widget])|raw }}
{% endblock money_field %}
{% block percent_field %}
{% display text_field %} %
{% endblock percent_field %}
{% block file_field %}
<input type="file" {% display field_attributes %} />
{% endblock file_field %}

View File

@ -1,57 +0,0 @@
{% block input_field %}
{% tag "input" with attributes %}
{% endblock input_field %}
{% block textarea_field %}
{% contenttag "textarea" with attributes %}{{ field.displayedData }}{% endcontenttag %}
{% endblock textarea_field %}
{% block choice_field %}
{% if field.options.expanded %}
{% for child in field %}
{{ child|render_widget }}
{% endfor %}
{% else %}
{% contenttag "select" with attributes %}
{{ field|render_choices }}
{% endcontenttag %}
{% endif %}
{% endblock choice_field %}
{% block toggle_field %}
{% display input_field %}
{% if field.options.label %}
{% contenttag "label" with ['for': field.id] %}{% trans field.options.label %}{% endcontenttag %}
{% endif %}
{% endblock toggle_field %}
{% block date_time_field %}
{{ field.date|render_widget }}
{{ field.time|render_widget }}
{% endblock date_time_field %}
{% block date_field %}
{% if field.field %}
{% display input_field %}
{% else %}
{{ field.pattern|replace(['{{ year }}': field.year|render_widget, '{{ month }}': field.month|render_widget, '{{ day }}': field.day|render_widget,]) }}
{% endif %}
{% endblock date_field %}
{% block time_field %}
{% if field.isfield %}
{% display input_field %}
{% else %}
{{ field.hour|render_widget }}:{{ field.minute|render_widget }}
{% if field.options.with_seconds %}:{{ field.second|render_widget }}{% endif %}
{% endif %}
{% endblock time_field %}
{% block money_field %}
{% set widget %}{% display input_field %}{% endset %}
{{ field.pattern|replace(['{{ widget }}': widget]) }}
{% endblock money_field %}
{% block percent_field %}
{% display input_field %} %
{% endblock percent_field %}

View File

@ -18,13 +18,4 @@ namespace Symfony\Component\Form;
*/
class CheckboxField extends ToggleField
{
/**
* {@inheritDoc}
*/
public function __construct($key, array $options = array())
{
$options['type'] = 'checkbox';
parent::__construct($key, $options);
}
}

View File

@ -50,7 +50,7 @@ class ChoiceField extends HybridField
$this->preferredChoices = array_flip($this->getOption('preferred_choices'));
}
if ($this->getOption('expanded')) {
if ($this->isExpanded()) {
$this->setFieldMode(self::GROUP);
$choices = $this->getOption('choices');
@ -71,34 +71,19 @@ class ChoiceField extends HybridField
parent::configure();
}
/**
* {@inheritDoc}
*/
public function getAttributes()
public function getName()
{
$attributes = array(
'id' => $this->getId(),
'name' => $this->getName(),
'disabled' => $this->isDisabled(),
);
// TESTME
$name = parent::getName();
// Add "[]" to the name in case a select tag with multiple options is
// displayed. Otherwise only one of the selected options is sent in the
// POST request.
if ($this->getOption('multiple') && !$this->getOption('expanded')) {
$attributes['name'] .= '[]';
if ($this->isMultipleChoice() && !$this->isExpanded()) {
$name .= '[]';
}
if ($this->getOption('multiple')) {
$attributes['multiple'] = 'multiple';
}
return array_merge(parent::getAttributes(), $attributes);
}
public function getSelected()
{
return array_flip(array_map('strval', (array) $this->getDisplayedData()));
return $name;
}
public function getPreferredChoices()
@ -116,6 +101,33 @@ class ChoiceField extends HybridField
return $this->isRequired() ? false : $this->getOption('empty_value');
}
public function getLabel($choice)
{
$choices = $this->getOption('choices');
return isset($choices[$choice]) ? $choices[$choice] : null;
}
public function isChoiceGroup($choice)
{
return is_array($choice) || $choice instanceof \Traversable;
}
public function isChoiceSelected($choice)
{
return in_array($choice, (array) $this->getDisplayedData());
}
public function isMultipleChoice()
{
return $this->getOption('multiple');
}
public function isExpanded()
{
return $this->getOption('expanded');
}
/**
* Returns a new field of type radio button or checkbox.
*
@ -124,17 +136,16 @@ class ChoiceField extends HybridField
*/
protected function newChoiceField($choice, $label)
{
if ($this->getOption('multiple')) {
if ($this->isMultipleChoice()) {
return new CheckboxField($choice, array(
'value' => $choice,
'label' => $label,
));
}
} else {
return new RadioField($choice, array(
'value' => $choice,
'label' => $label,
));
}
}
/**
* {@inheritDoc}
@ -144,7 +155,7 @@ class ChoiceField extends HybridField
*/
public function bind($value)
{
if (!$this->getOption('multiple') && $this->getOption('expanded')) {
if (!$this->isMultipleChoice() && $this->isExpanded()) {
$value = $value === null ? array() : array($value => true);
}
@ -166,12 +177,12 @@ class ChoiceField extends HybridField
*/
protected function transform($value)
{
if ($this->getOption('expanded')) {
if ($this->isExpanded()) {
$value = parent::transform($value);
$choices = $this->getOption('choices');
foreach ($choices as $choice => $_) {
$choices[$choice] = $this->getOption('multiple')
$choices[$choice] = $this->isMultipleChoice()
? in_array($choice, (array)$value, true)
: ($choice === $value);
}
@ -196,7 +207,7 @@ class ChoiceField extends HybridField
*/
protected function reverseTransform($value)
{
if ($this->getOption('expanded')) {
if ($this->isExpanded()) {
$choices = array();
foreach ($value as $choice => $selected) {
@ -205,7 +216,7 @@ class ChoiceField extends HybridField
}
}
if ($this->getOption('multiple')) {
if ($this->isMultipleChoice()) {
$value = $choices;
} else {
$value = count($choices) > 0 ? current($choices) : null;

View File

@ -54,7 +54,7 @@ class CollectionField extends FieldGroup
public function setData($collection)
{
if (!is_array($collection) && !$collection instanceof \Traversable) {
throw new UnexpectedTypeException('The data must be an array');
throw new UnexpectedTypeException('The data passed to the CollectionField must be an array or a Traversable');
}
foreach ($this as $name => $field) {

View File

@ -145,23 +145,6 @@ class DateField extends HybridField
}
}
/**
* {@inheritDoc}
*/
public function getAttributes()
{
if ($this->isField()) {
return array_merge(parent::getAttributes(), array(
'id' => $this->getId(),
'name' => $this->getName(),
'value' => $this->getDisplayedData(),
'type' => 'text',
));
}
return parent::getAttributes();
}
/**
* Generates an array of choices for the given values
*

View File

@ -103,11 +103,6 @@ abstract class Field extends Configurable implements FieldInterface
// TODO
}
public function getAttributes()
{
return array();
}
/**
* Returns the data of the field as it is displayed to the user.
*

View File

@ -216,7 +216,7 @@ class FieldGroup extends Field implements \IteratorAggregate, FieldGroupInterfac
*
* @return array
*/
public function getVisibleFieldsRecursively()
public function getAllVisibleFields()
{
return $this->getFieldsByVisibility(false, true);
}
@ -239,7 +239,7 @@ class FieldGroup extends Field implements \IteratorAggregate, FieldGroupInterfac
*
* @return array
*/
public function getHiddenFieldsRecursively()
public function getAllHiddenFields()
{
return $this->getFieldsByVisibility(true, true);
}
@ -255,13 +255,12 @@ class FieldGroup extends Field implements \IteratorAggregate, FieldGroupInterfac
protected function getFieldsByVisibility($hidden, $recursive)
{
$fields = array();
$hidden = (bool)$hidden;
foreach ($this->fields as $field) {
if ($field instanceof FieldGroup) {
if ($recursive) {
if ($field instanceof FieldGroup && $recursive) {
$fields = array_merge($fields, $field->getFieldsByVisibility($hidden, $recursive));
}
} else if ((bool)$hidden === $field->isHidden()) {
} else if ($hidden === $field->isHidden()) {
$fields[] = $field;
}
}
@ -447,11 +446,6 @@ class FieldGroup extends Field implements \IteratorAggregate, FieldGroupInterfac
return false;
}
public function isGroup()
{
return true;
}
/**
* Returns true if the bound field exists (implements the \ArrayAccess interface).
*

View File

@ -18,9 +18,4 @@ namespace Symfony\Component\Form;
*/
interface FieldGroupInterface extends FieldInterface, \ArrayAccess, \Traversable, \Countable
{
/**
* Returns whether the Field instance really is a group
* @return bool
*/
public function isGroup();
}

View File

@ -14,28 +14,8 @@ namespace Symfony\Component\Form;
/**
* A file field to upload files.
*/
class FileField extends InputField
class FileField extends Field
{
/**
* {@inheritDoc}
*/
public function __construct($key, array $options = array())
{
$options['type'] = 'file';
parent::__construct($key, $options);
}
/**
* {@inheritDoc}
*/
public function getAttributes()
{
return array_merge(parent::getAttributes(), array(
'type' => 'file',
));
}
/**
* {@inheritDoc}
*/

View File

@ -16,18 +16,8 @@ namespace Symfony\Component\Form;
*
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
class HiddenField extends InputField
class HiddenField extends Field
{
/**
* {@inheritDoc}
*/
public function __construct($key, array $options = array())
{
$options['type'] = 'hidden';
parent::__construct($key, $options);
}
/**
* {@inheritDoc}
*/

View File

@ -1,44 +0,0 @@
<?php
namespace Symfony\Component\Form;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Base class for all low-level fields represented by input tags
*
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
class InputField extends Field
{
/**
* {@inheritDoc}
*/
protected function configure()
{
$this->addRequiredOption('type');
parent::configure();
}
/**
* {@inheritDoc}
*/
public function getAttributes()
{
return array_merge(parent::getAttributes(), array(
'id' => $this->getId(),
'name' => $this->getName(),
'value' => $this->getDisplayedData(),
'disabled' => $this->isDisabled(),
'type' => $this->getOption('type'),
));
}
}

View File

@ -53,7 +53,7 @@ class MoneyField extends NumberField
* The pattern contains the placeholder "{{ widget }}" where the HTML tag should
* be inserted
*/
protected function getPattern()
public function getPattern()
{
if (!$this->getOption('currency')) {
return '{{ widget }}';

View File

@ -18,18 +18,8 @@ use Symfony\Component\Form\ValueTransformer\NumberToLocalizedStringTransformer;
*
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
class NumberField extends InputField
class NumberField extends Field
{
/**
* {@inheritDoc}
*/
public function __construct($key, array $options = array())
{
$options['type'] = 'text';
parent::__construct($key, $options);
}
/**
* {@inheritDoc}
*/

View File

@ -28,15 +28,11 @@ class PasswordField extends TextField
parent::configure();
}
/**
* {@inheritDoc}
*/
public function getAttributes()
public function getDisplayedData()
{
return array_merge(parent::getAttributes(), array(
// override getDisplayedData() instead?
'value' => $this->getOption('always_empty') && !$this->isBound() ? '' : $this->getDisplayedData(),
'type' => 'password',
));
// TESTME
return $this->getOption('always_empty') && !$this->isBound()
? ''
: parent::getDisplayedData();
}
}

View File

@ -21,21 +21,9 @@ class RadioField extends ToggleField
/**
* {@inheritDoc}
*/
public function __construct($key, array $options = array())
public function getName()
{
$options['type'] = 'radio';
parent::__construct($key, $options);
}
/**
* {@inheritDoc}
*/
public function getAttributes()
{
return array_merge(parent::getAttributes(), array(
// TODO: should getName() be overridden instead?
'name' => $this->getParent() ? $this->getParent()->getName() : $this->getName(),
));
// TESTME
return $this->getParent() ? $this->getParent()->getName() : $this->getName();
}
}

View File

@ -16,18 +16,8 @@ namespace Symfony\Component\Form;
*
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
class TextField extends InputField
class TextField extends Field
{
/**
* {@inheritDoc}
*/
public function __construct($key, array $options = array())
{
$options['type'] = 'text';
parent::__construct($key, $options);
}
/**
* {@inheritDoc}
*/
@ -38,13 +28,8 @@ class TextField extends InputField
parent::configure();
}
/**
* {@inheritDoc}
*/
public function getAttributes()
public function getMaxLength()
{
return array_merge(parent::getAttributes(), array(
'maxlength' => $this->getOption('max_length'),
));
return $this->getOption('max_length');
}
}

View File

@ -18,16 +18,4 @@ namespace Symfony\Component\Form;
*/
class TextareaField extends Field
{
/**
* {@inheritDoc}
*/
public function getAttributes()
{
return array_merge(parent::getAttributes(), array(
'id' => $this->getId(),
'name' => $this->getName(),
'rows' => 4,
'cols' => 30,
));
}
}

View File

@ -121,18 +121,9 @@ class TimeField extends FieldGroup
return self::INPUT === $this->getOption('widget');
}
/**
* {@inheritDoc}
*/
public function getAttributes()
public function isWithSeconds()
{
if ($this->isField()) {
return array_merge(parent::getAttributes(), array(
'size' => '1',
));
}
return parent::getAttributes();
return $this->getOption('with_seconds');
}
/**

View File

@ -18,7 +18,7 @@ use Symfony\Component\Form\ValueTransformer\BooleanToStringTransformer;
*
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
abstract class ToggleField extends InputField
abstract class ToggleField extends Field
{
/**
* {@inheritDoc}
@ -26,21 +26,24 @@ abstract class ToggleField extends InputField
protected function configure()
{
$this->addOption('value');
$this->addOption('label');
parent::configure();
$this->setValueTransformer(new BooleanToStringTransformer());
}
/**
* {@inheritDoc}
*/
public function getAttributes()
public function isChecked()
{
return array_merge(parent::getAttributes(), array(
'value' => $this->getOption('value'),
'checked' => (string) $this->getDisplayedData() !== '' && $this->getDisplayedData() !== 0,
));
return $this->getData();
}
public function getValue()
{
return $this->getOption('value');
}
public function hasValue()
{
return $this->getValue() !== null;
}
}