Adapted Form, Validator, File and I18N component to new coding standards

This commit is contained in:
Bernhard Schussek 2010-06-24 11:24:08 +02:00 committed by Fabien Potencier
parent ee83847cec
commit bcd4b6d140
227 changed files with 12877 additions and 13518 deletions

View File

@ -17,13 +17,13 @@ namespace Symfony\Components\File\Exception;
*/ */
class AccessDeniedException extends FileException class AccessDeniedException extends FileException
{ {
/** /**
* Constructor. * Constructor.
* *
* @param string $path The path to the accessed file * @param string $path The path to the accessed file
*/ */
public function __construct($path) public function __construct($path)
{ {
parent::__construct(sprintf('The file %s could not be accessed', $path)); parent::__construct(sprintf('The file %s could not be accessed', $path));
} }
} }

View File

@ -17,13 +17,13 @@ namespace Symfony\Components\File\Exception;
*/ */
class FileNotFoundException extends FileException class FileNotFoundException extends FileException
{ {
/** /**
* Constructor. * Constructor.
* *
* @param string $path The path to the file that was not found * @param string $path The path to the file that was not found
*/ */
public function __construct($path) public function __construct($path)
{ {
parent::__construct(sprintf('The file %s does not exist', $path)); parent::__construct(sprintf('The file %s does not exist', $path));
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -20,46 +20,42 @@ use Symfony\Components\File\Exception\AccessDeniedException;
*/ */
class ContentTypeMimeTypeGuesser implements MimeTypeGuesserInterface class ContentTypeMimeTypeGuesser implements MimeTypeGuesserInterface
{ {
/** /**
* Returns whether this guesser is supported on the corrent OS/PHP setup * Returns whether this guesser is supported on the corrent OS/PHP setup
* *
* @return boolean * @return boolean
*/ */
static public function isSupported() static public function isSupported()
{
return function_exists('mime_content_type');
}
/**
* Guesses the mime type of the file with the given path
*
* @see MimeTypeGuesserInterface::guess()
*/
public function guess($path)
{
if (!is_file($path))
{ {
throw new FileNotFoundException($path); return function_exists('mime_content_type');
} }
if (!is_readable($path)) /**
* Guesses the mime type of the file with the given path
*
* @see MimeTypeGuesserInterface::guess()
*/
public function guess($path)
{ {
throw new AccessDeniedException($path); if (!is_file($path)) {
throw new FileNotFoundException($path);
}
if (!is_readable($path)) {
throw new AccessDeniedException($path);
}
if (!self::isSupported() || !is_readable($path)) {
return null;
}
$type = mime_content_type($path);
// remove charset (added as of PHP 5.3)
if (false !== $pos = strpos($type, ';')) {
$type = substr($type, 0, $pos);
}
return $type;
} }
if (!self::isSupported() || !is_readable($path))
{
return null;
}
$type = mime_content_type($path);
// remove charset (added as of PHP 5.3)
if (false !== $pos = strpos($type, ';'))
{
$type = substr($type, 0, $pos);
}
return $type;
}
} }

View File

@ -20,42 +20,38 @@ use Symfony\Components\File\Exception\AccessDeniedException;
*/ */
class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
{ {
/** /**
* Guesses the mime type of the file with the given path * Guesses the mime type of the file with the given path
* *
* @see MimeTypeGuesserInterface::guess() * @see MimeTypeGuesserInterface::guess()
*/ */
public function guess($path) public function guess($path)
{
if (!is_file($path))
{ {
throw new FileNotFoundException($path); if (!is_file($path)) {
throw new FileNotFoundException($path);
}
if (!is_readable($path)) {
throw new AccessDeniedException($path);
}
ob_start();
// need to use --mime instead of -i. see #6641
passthru(sprintf('file -b --mime %s 2>/dev/null', escapeshellarg($path)), $return);
if ($return > 0) {
ob_end_clean();
return null;
}
$type = trim(ob_get_clean());
if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-]+)#i', $type, $match)) {
// it's not a type, but an error message
return null;
}
return $match[1];
} }
if (!is_readable($path))
{
throw new AccessDeniedException($path);
}
ob_start();
// need to use --mime instead of -i. see #6641
passthru(sprintf('file -b --mime %s 2>/dev/null', escapeshellarg($path)), $return);
if ($return > 0)
{
ob_end_clean();
return null;
}
$type = trim(ob_get_clean());
if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-]+)#i', $type, $match))
{
// it's not a type, but an error message
return null;
}
return $match[1];
}
} }

View File

@ -20,51 +20,46 @@ use Symfony\Components\File\Exception\AccessDeniedException;
*/ */
class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
{ {
/** /**
* Returns whether this guesser is supported on the corrent OS/PHP setup * Returns whether this guesser is supported on the corrent OS/PHP setup
* *
* @return boolean * @return boolean
*/ */
static public function isSupported() static public function isSupported()
{
return function_exists('finfo_open');
}
/**
* Guesses the mime type of the file with the given path
*
* @see MimeTypeGuesserInterface::guess()
*/
public function guess($path)
{
if (!is_file($path))
{ {
throw new FileNotFoundException($path); return function_exists('finfo_open');
} }
if (!is_readable($path)) /**
* Guesses the mime type of the file with the given path
*
* @see MimeTypeGuesserInterface::guess()
*/
public function guess($path)
{ {
throw new AccessDeniedException($path); if (!is_file($path)) {
throw new FileNotFoundException($path);
}
if (!is_readable($path)) {
throw new AccessDeniedException($path);
}
if (!self::isSupported()) {
return null;
}
if (!$finfo = new \finfo(FILEINFO_MIME)) {
return null;
}
$type = $finfo->file($path);
// remove charset (added as of PHP 5.3)
if (false !== $pos = strpos($type, ';')) {
$type = substr($type, 0, $pos);
}
return $type;
} }
if (!self::isSupported())
{
return null;
}
if (!$finfo = new \finfo(FILEINFO_MIME))
{
return null;
}
$type = $finfo->file($path);
// remove charset (added as of PHP 5.3)
if (false !== $pos = strpos($type, ';'))
{
$type = substr($type, 0, $pos);
}
return $type;
}
} }

View File

@ -31,99 +31,92 @@ use Symfony\Components\File\Exception\AccessDeniedException;
*/ */
class MimeTypeGuesser implements MimeTypeGuesserInterface class MimeTypeGuesser implements MimeTypeGuesserInterface
{ {
/** /**
* The singleton instance * The singleton instance
* @var MimeTypeGuesser * @var MimeTypeGuesser
*/ */
static private $instance = null; static private $instance = null;
/** /**
* All registered MimeTypeGuesserInterface instances * All registered MimeTypeGuesserInterface instances
* @var array * @var array
*/ */
protected $guessers = array(); protected $guessers = array();
/** /**
* Returns the singleton instance * Returns the singleton instance
* *
* @return MimeTypeGuesser * @return MimeTypeGuesser
*/ */
static public function getInstance() static public function getInstance()
{
if (is_null(self::$instance))
{ {
self::$instance = new self(); if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
} }
return self::$instance; /**
} * Registers all natively provided mime type guessers
*/
/** private function __construct()
* Registers all natively provided mime type guessers
*/
private function __construct()
{
$this->register(new FileBinaryMimeTypeGuesser());
if (ContentTypeMimeTypeGuesser::isSupported())
{ {
$this->register(new ContentTypeMimeTypeGuesser()); $this->register(new FileBinaryMimeTypeGuesser());
if (ContentTypeMimeTypeGuesser::isSupported()) {
$this->register(new ContentTypeMimeTypeGuesser());
}
if (FileinfoMimeTypeGuesser::isSupported()) {
$this->register(new FileinfoMimeTypeGuesser());
}
} }
if (FileinfoMimeTypeGuesser::isSupported()) /**
* Registers a new mime type guesser
*
* When guessing, this guesser is preferred over previously registered ones.
*
* @param MimeTypeGuesserInterface $guesser
*/
public function register(MimeTypeGuesserInterface $guesser)
{ {
$this->register(new FileinfoMimeTypeGuesser()); array_unshift($this->guessers, $guesser);
}
}
/**
* Registers a new mime type guesser
*
* When guessing, this guesser is preferred over previously registered ones.
*
* @param MimeTypeGuesserInterface $guesser
*/
public function register(MimeTypeGuesserInterface $guesser)
{
array_unshift($this->guessers, $guesser);
}
/**
* Tries to guess the mime type of the given file
*
* The file is passed to each registered mime type guesser in reverse order
* of their registration (last registered is queried first). Once a guesser
* returns a value that is not NULL, this method terminates and returns the
* value.
*
* @param string $path The path to the file
* @return string The mime type or NULL, if none could be guessed
* @throws FileException If the file does not exist
*/
public function guess($path)
{
if (!is_file($path))
{
throw new FileNotFoundException($path);
} }
if (!is_readable($path)) /**
* Tries to guess the mime type of the given file
*
* The file is passed to each registered mime type guesser in reverse order
* of their registration (last registered is queried first). Once a guesser
* returns a value that is not NULL, this method terminates and returns the
* value.
*
* @param string $path The path to the file
* @return string The mime type or NULL, if none could be guessed
* @throws FileException If the file does not exist
*/
public function guess($path)
{ {
throw new AccessDeniedException($path); if (!is_file($path)) {
throw new FileNotFoundException($path);
}
if (!is_readable($path)) {
throw new AccessDeniedException($path);
}
$mimeType = null;
foreach ($this->guessers as $guesser) {
$mimeType = $guesser->guess($path);
if (!is_null($mimeType)) {
break;
}
}
return $mimeType;
} }
$mimeType = null;
foreach ($this->guessers as $guesser)
{
$mimeType = $guesser->guess($path);
if (!is_null($mimeType))
{
break;
}
}
return $mimeType;
}
} }

View File

@ -17,13 +17,13 @@ namespace Symfony\Components\File\MimeType;
*/ */
interface MimeTypeGuesserInterface interface MimeTypeGuesserInterface
{ {
/** /**
* Guesses the mime type of the file with the given path * Guesses the mime type of the file with the given path
* *
* @param string $path The path to the file * @param string $path The path to the file
* @return string The mime type or NULL, if none could be guessed * @return string The mime type or NULL, if none could be guessed
* @throws FileNotFoundException If the file does not exist * @throws FileNotFoundException If the file does not exist
* @throws AccessDeniedException If the file could not be read * @throws AccessDeniedException If the file could not be read
*/ */
public function guess($path); public function guess($path);
} }

View File

@ -20,111 +20,103 @@ use Symfony\Components\File\Exception\FileException;
*/ */
class UploadedFile extends File class UploadedFile extends File
{ {
protected $originalName; protected $originalName;
protected $mimeType; protected $mimeType;
protected $size; protected $size;
protected $error; protected $error;
protected $moved = false; protected $moved = false;
/** /**
* Accepts the information of the uploaded file as provided by the PHP * Accepts the information of the uploaded file as provided by the PHP
* global $_FILES. * global $_FILES.
* *
* @param string $tmpName The full temporary path to the file * @param string $tmpName The full temporary path to the file
* @param string $name The original file name * @param string $name The original file name
* @param string $type The type of the file as provided by PHP * @param string $type The type of the file as provided by PHP
* @param integer $size The file size * @param integer $size The file size
* @param string $error The error constant of the upload. Should be * @param string $error The error constant of the upload. Should be
* one of PHP's UPLOAD_XXX constants. * one of PHP's UPLOAD_XXX constants.
*/ */
public function __construct($path, $originalName, $mimeType, $size, $error) public function __construct($path, $originalName, $mimeType, $size, $error)
{
if (!ini_get('file_uploads'))
{ {
throw new FileException(sprintf('Unable to create UploadedFile because "file_uploads" is disabled in your php.ini file (%s)', get_cfg_var('cfg_file_path'))); if (!ini_get('file_uploads')) {
throw new FileException(sprintf('Unable to create UploadedFile because "file_uploads" is disabled in your php.ini file (%s)', get_cfg_var('cfg_file_path')));
}
parent::__construct($path);
if (is_null($error)) {
$error = UPLOAD_ERR_OK;
}
if (is_null($mimeType)) {
$mimeType = 'application/octet-stream';
}
$this->originalName = (string)$originalName;
$this->mimeType = $mimeType;
$this->size = $size;
$this->error = $error;
} }
parent::__construct($path); /**
* Returns the mime type of the file.
if (is_null($error)) *
* The mime type is guessed using the functions finfo(), mime_content_type()
* and the system binary "file" (in this order), depending on which of those
* is available on the current operating system.
*
* @returns string The guessed mime type, e.g. "application/pdf"
*/
public function getMimeType()
{ {
$error = UPLOAD_ERR_OK; $mimeType = parent::getMimeType();
if (is_null($mimeType)) {
$mimeType = $this->mimeType;
}
return $mimeType;
} }
if (is_null($mimeType)) /**
* Returns the original file name including its extension.
*
* @returns string The file name
*/
public function getOriginalName()
{ {
$mimeType = 'application/octet-stream'; return $this->originalName;
} }
$this->originalName = (string)$originalName; /**
$this->mimeType = $mimeType; * Returns the upload error.
$this->size = $size; *
$this->error = $error; * If the upload was successful, the constant UPLOAD_ERR_OK is returned.
} * Otherwise one of the other UPLOAD_ERR_XXX constants is returned.
*
/** * @returns integer The upload error
* Returns the mime type of the file. */
* public function getError()
* The mime type is guessed using the functions finfo(), mime_content_type()
* and the system binary "file" (in this order), depending on which of those
* is available on the current operating system.
*
* @returns string The guessed mime type, e.g. "application/pdf"
*/
public function getMimeType()
{
$mimeType = parent::getMimeType();
if (is_null($mimeType))
{ {
$mimeType = $this->mimeType; return $this->error;
} }
return $mimeType; /**
} * Moves the file to a new location.
*
/** * @param string $newPath
* Returns the original file name including its extension. */
* public function move($newPath)
* @returns string The file name
*/
public function getOriginalName()
{
return $this->originalName;
}
/**
* Returns the upload error.
*
* If the upload was successful, the constant UPLOAD_ERR_OK is returned.
* Otherwise one of the other UPLOAD_ERR_XXX constants is returned.
*
* @returns integer The upload error
*/
public function getError()
{
return $this->error;
}
/**
* Moves the file to a new location.
*
* @param string $newPath
*/
public function move($newPath)
{
if (!$this->moved)
{ {
if (!move_uploaded_file($this->getPath(), $newPath)) if (!$this->moved) {
{ if (!move_uploaded_file($this->getPath(), $newPath)) {
throw new FileException(sprintf('Could not move file %s to %s', $this->getPath(), $newPath)); throw new FileException(sprintf('Could not move file %s to %s', $this->getPath(), $newPath));
} }
$this->moved = true; $this->moved = true;
} else {
parent::move($newPath);
}
} }
else
{
parent::move($newPath);
}
}
} }

View File

@ -20,15 +20,15 @@ namespace Symfony\Components\Form;
*/ */
class BirthdayField extends DateField class BirthdayField extends DateField
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{ {
$currentYear = date('Y'); $currentYear = date('Y');
$this->addOption('years', range($currentYear-120, $currentYear)); $this->addOption('years', range($currentYear-120, $currentYear));
parent::configure(); parent::configure();
} }
} }

View File

@ -20,13 +20,13 @@ use Symfony\Components\Form\ValueTransformer\BooleanToStringTransformer;
*/ */
class CheckboxField extends ToggleField class CheckboxField extends ToggleField
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function render(array $attributes = array()) public function render(array $attributes = array())
{ {
return parent::render(array_merge(array( return parent::render(array_merge(array(
'type' => 'checkbox', 'type' => 'checkbox',
), $attributes)); ), $attributes));
} }
} }

View File

@ -15,274 +15,237 @@ use Symfony\Components\Form\ValueTransformer\BooleanToStringTransformer;
*/ */
class ChoiceField extends HybridField class ChoiceField extends HybridField
{ {
/** /**
* Stores the preferred choices with the choices as keys * Stores the preferred choices with the choices as keys
* @var array * @var array
*/ */
protected $preferredChoices = array(); protected $preferredChoices = array();
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{
$this->addRequiredOption('choices');
$this->addOption('preferred_choices', array());
$this->addOption('separator', '----------');
$this->addOption('multiple', false);
$this->addOption('expanded', false);
$this->addOption('empty_value', '');
$this->addOption('translate_choices', false);
if (count($this->getOption('preferred_choices')) > 0)
{ {
$this->preferredChoices = array_flip($this->getOption('preferred_choices')); $this->addRequiredOption('choices');
$this->addOption('preferred_choices', array());
$this->addOption('separator', '----------');
$this->addOption('multiple', false);
$this->addOption('expanded', false);
$this->addOption('empty_value', '');
$this->addOption('translate_choices', false);
if (false && $diff = array_diff_key($this->options, $this->knownOptions)) if (count($this->getOption('preferred_choices')) > 0) {
{ $this->preferredChoices = array_flip($this->getOption('preferred_choices'));
//throw new InvalidOptionsException(sprintf('%s does not support the following options: "%s".', get_class($this), implode('", "', array_keys($diff))), array_keys($diff));
}
}
if ($this->getOption('expanded')) if (false && $diff = array_diff_key($this->options, $this->knownOptions)) {
{ //throw new InvalidOptionsException(sprintf('%s does not support the following options: "%s".', get_class($this), implode('", "', array_keys($diff))), array_keys($diff));
$this->setFieldMode(self::GROUP); }
$choices = $this->getOption('choices');
foreach ($this->getOption('preferred_choices') as $choice)
{
$this->add($this->newChoiceField($choice, $choices[$choice]));
unset($choices[$choice]);
}
foreach ($this->getOption('choices') as $choice => $value)
{
$this->add($this->newChoiceField($choice, $value));
}
}
else
{
$this->setFieldMode(self::FIELD);
}
}
/**
* Returns a new field of type radio button or checkbox.
*
* @param string $key The key for the option
* @param string $label The label for the option
*/
protected function newChoiceField($choice, $label)
{
if ($this->getOption('multiple'))
{
return new CheckboxField($choice, array(
'value' => $choice,
'label' => $label,
'translate_label' => $this->getOption('translate_choices'),
));
}
else
{
return new RadioField($choice, array(
'value' => $choice,
'label' => $label,
'translate_label' => $this->getOption('translate_choices'),
));
}
}
/**
* {@inheritDoc}
*
* Takes care of converting the input from a single radio button
* to an array.
*/
public function bind($value)
{
if (!$this->getOption('multiple') && $this->getOption('expanded'))
{
$value = $value === null ? array() : array($value => true);
}
parent::bind($value);
}
/**
* Transforms a single choice or an array of choices to a format appropriate
* for the nested checkboxes/radio buttons.
*
* The result is an array with the options as keys and true/false as values,
* depending on whether a given option is selected. If this field is rendered
* as select tag, the value is not modified.
*
* @param mixed $value An array if "multiple" is set to true, a scalar
* value otherwise.
* @return mixed An array if "expanded" or "multiple" is set to true,
* a scalar value otherwise.
*/
protected function transform($value)
{
if ($this->getOption('expanded'))
{
$choices = $this->getOption('choices');
foreach ($choices as $choice => $_)
{
$choices[$choice] = $this->getOption('multiple')
? in_array($choice, (array)$value, true)
: ($choice === $value);
}
return $choices;
}
else
{
return parent::transform($value);
}
}
/**
* Transforms a checkbox/radio button array to a single choice or an array
* of choices.
*
* The input value is an array with the choices as keys and true/false as
* values, depending on whether a given choice is selected. The output
* is an array with the selected choices or a single selected choice.
*
* @param mixed $value An array if "expanded" or "multiple" is set to true,
* a scalar value otherwise.
* @return mixed $value An array if "multiple" is set to true, a scalar
* value otherwise.
*/
protected function reverseTransform($value)
{
if ($this->getOption('expanded'))
{
$choices = array();
foreach ($value as $choice => $selected)
{
if ($selected)
{
$choices[] = $choice;
}
}
if ($this->getOption('multiple'))
{
return $choices;
}
else
{
return count($choices) > 0 ? current($choices) : null;
}
}
else
{
return parent::reverseTransform($value);
}
}
/**
* {@inheritDoc}
*/
public function render(array $attributes = array())
{
if ($this->getOption('expanded'))
{
$html = "";
foreach ($this as $field)
{
$html .= $field->render()."\n";
}
return $html;
}
else
{
$attrs['id'] = $this->getId();
$attrs['name'] = $this->getName();
$attrs['disabled'] = $this->isDisabled();
// 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'))
{
$attrs['name'] .= '[]';
}
if ($this->getOption('multiple'))
{
$attrs['multiple'] = 'multiple';
}
$selected = array_flip(array_map('strval', (array)$this->getDisplayedData()));
$html = "\n";
if (!$this->isRequired())
{
$html .= $this->renderChoices(array('' => $this->getOption('empty_value')), $selected)."\n";
}
$choices = $this->getOption('choices');
if (count($this->preferredChoices) > 0)
{
$html .= $this->renderChoices(array_intersect_key($choices, $this->preferredChoices), $selected)."\n";
$html .= $this->generator->contentTag('option', $this->getOption('separator'), array('disabled' => true))."\n";
}
$html .= $this->renderChoices(array_diff_key($choices, $this->preferredChoices), $selected)."\n";
return $this->generator->contentTag('select', $html, array_merge($attrs, $attributes));
}
}
/**
* Returns an array of option tags for the choice field
*
* @return array An array of option tags
*/
protected function renderChoices(array $choices, array $selected)
{
$options = array();
foreach ($choices as $key => $option)
{
if (is_array($option))
{
$options[] = $this->generator->contentTag(
'optgroup',
"\n".$this->renderChoices($option, $selected)."\n",
array('label' => $this->generator->escape($key))
);
}
else
{
$attributes = array('value' => $this->generator->escape($key));
if (isset($selected[strval($key)]))
{
$attributes['selected'] = true;
} }
if ($this->getOption('translate_choices')) if ($this->getOption('expanded')) {
{ $this->setFieldMode(self::GROUP);
$option = $this->translate($option);
}
$options[] = $this->generator->contentTag( $choices = $this->getOption('choices');
'option',
$this->generator->escape($option), foreach ($this->getOption('preferred_choices') as $choice) {
$attributes $this->add($this->newChoiceField($choice, $choices[$choice]));
); unset($choices[$choice]);
} }
foreach ($this->getOption('choices') as $choice => $value) {
$this->add($this->newChoiceField($choice, $value));
}
} else {
$this->setFieldMode(self::FIELD);
}
} }
return implode("\n", $options); /**
} * Returns a new field of type radio button or checkbox.
*
* @param string $key The key for the option
* @param string $label The label for the option
*/
protected function newChoiceField($choice, $label)
{
if ($this->getOption('multiple')) {
return new CheckboxField($choice, array(
'value' => $choice,
'label' => $label,
'translate_label' => $this->getOption('translate_choices'),
));
} else {
return new RadioField($choice, array(
'value' => $choice,
'label' => $label,
'translate_label' => $this->getOption('translate_choices'),
));
}
}
/**
* {@inheritDoc}
*
* Takes care of converting the input from a single radio button
* to an array.
*/
public function bind($value)
{
if (!$this->getOption('multiple') && $this->getOption('expanded')) {
$value = $value === null ? array() : array($value => true);
}
parent::bind($value);
}
/**
* Transforms a single choice or an array of choices to a format appropriate
* for the nested checkboxes/radio buttons.
*
* The result is an array with the options as keys and true/false as values,
* depending on whether a given option is selected. If this field is rendered
* as select tag, the value is not modified.
*
* @param mixed $value An array if "multiple" is set to true, a scalar
* value otherwise.
* @return mixed An array if "expanded" or "multiple" is set to true,
* a scalar value otherwise.
*/
protected function transform($value)
{
if ($this->getOption('expanded')) {
$choices = $this->getOption('choices');
foreach ($choices as $choice => $_) {
$choices[$choice] = $this->getOption('multiple')
? in_array($choice, (array)$value, true)
: ($choice === $value);
}
return $choices;
} else {
return parent::transform($value);
}
}
/**
* Transforms a checkbox/radio button array to a single choice or an array
* of choices.
*
* The input value is an array with the choices as keys and true/false as
* values, depending on whether a given choice is selected. The output
* is an array with the selected choices or a single selected choice.
*
* @param mixed $value An array if "expanded" or "multiple" is set to true,
* a scalar value otherwise.
* @return mixed $value An array if "multiple" is set to true, a scalar
* value otherwise.
*/
protected function reverseTransform($value)
{
if ($this->getOption('expanded')) {
$choices = array();
foreach ($value as $choice => $selected) {
if ($selected) {
$choices[] = $choice;
}
}
if ($this->getOption('multiple')) {
return $choices;
} else {
return count($choices) > 0 ? current($choices) : null;
}
} else {
return parent::reverseTransform($value);
}
}
/**
* {@inheritDoc}
*/
public function render(array $attributes = array())
{
if ($this->getOption('expanded')) {
$html = "";
foreach ($this as $field) {
$html .= $field->render()."\n";
}
return $html;
} else {
$attrs['id'] = $this->getId();
$attrs['name'] = $this->getName();
$attrs['disabled'] = $this->isDisabled();
// 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')) {
$attrs['name'] .= '[]';
}
if ($this->getOption('multiple')) {
$attrs['multiple'] = 'multiple';
}
$selected = array_flip(array_map('strval', (array)$this->getDisplayedData()));
$html = "\n";
if (!$this->isRequired()) {
$html .= $this->renderChoices(array('' => $this->getOption('empty_value')), $selected)."\n";
}
$choices = $this->getOption('choices');
if (count($this->preferredChoices) > 0) {
$html .= $this->renderChoices(array_intersect_key($choices, $this->preferredChoices), $selected)."\n";
$html .= $this->generator->contentTag('option', $this->getOption('separator'), array('disabled' => true))."\n";
}
$html .= $this->renderChoices(array_diff_key($choices, $this->preferredChoices), $selected)."\n";
return $this->generator->contentTag('select', $html, array_merge($attrs, $attributes));
}
}
/**
* Returns an array of option tags for the choice field
*
* @return array An array of option tags
*/
protected function renderChoices(array $choices, array $selected)
{
$options = array();
foreach ($choices as $key => $option) {
if (is_array($option)) {
$options[] = $this->generator->contentTag(
'optgroup',
"\n".$this->renderChoices($option, $selected)."\n",
array('label' => $this->generator->escape($key))
);
} else {
$attributes = array('value' => $this->generator->escape($key));
if (isset($selected[strval($key)])) {
$attributes['selected'] = true;
}
if ($this->getOption('translate_choices')) {
$option = $this->translate($option);
}
$options[] = $this->generator->contentTag(
'option',
$this->generator->escape($option),
$attributes
);
}
}
return implode("\n", $options);
}
} }

View File

@ -21,83 +21,75 @@ use Symfony\Components\Form\Exception\UnexpectedTypeException;
*/ */
class CollectionField extends FieldGroup class CollectionField extends FieldGroup
{ {
/** /**
* The prototype for the inner fields * The prototype for the inner fields
* @var FieldInterface * @var FieldInterface
*/ */
protected $prototype; protected $prototype;
/** /**
* Repeats the given field twice to verify the user's input * Repeats the given field twice to verify the user's input
* *
* @param FieldInterface $innerField * @param FieldInterface $innerField
*/ */
public function __construct(FieldInterface $innerField, array $options = array()) public function __construct(FieldInterface $innerField, array $options = array())
{
$this->prototype = $innerField;
parent::__construct($innerField->getKey(), $options);
}
protected function configure()
{
$this->addOption('modifiable', false);
if ($this->getOption('modifiable'))
{ {
$field = $this->newField('$$key$$', null); $this->prototype = $innerField;
// TESTME
$field->setRequired(false);
$this->add($field);
}
}
public function setData($collection) parent::__construct($innerField->getKey(), $options);
{
if (!is_array($collection) && !$collection instanceof Traversable)
{
throw new UnexpectedTypeException('The data must be an array');
} }
foreach ($collection as $name => $value) protected function configure()
{ {
$this->add($this->newField($name, $name)); $this->addOption('modifiable', false);
if ($this->getOption('modifiable')) {
$field = $this->newField('$$key$$', null);
// TESTME
$field->setRequired(false);
$this->add($field);
}
} }
parent::setData($collection); public function setData($collection)
}
public function bind($taintedData)
{
if (is_null($taintedData))
{ {
$taintedData = array(); if (!is_array($collection) && !$collection instanceof Traversable) {
throw new UnexpectedTypeException('The data must be an array');
}
foreach ($collection as $name => $value) {
$this->add($this->newField($name, $name));
}
parent::setData($collection);
} }
foreach ($this as $name => $field) public function bind($taintedData)
{ {
if (!isset($taintedData[$name]) && $this->getOption('modifiable') && $name != '$$key$$') if (is_null($taintedData)) {
{ $taintedData = array();
$this->remove($name); }
}
foreach ($this as $name => $field) {
if (!isset($taintedData[$name]) && $this->getOption('modifiable') && $name != '$$key$$') {
$this->remove($name);
}
}
foreach ($taintedData as $name => $value) {
if (!isset($this[$name]) && $this->getOption('modifiable')) {
$this->add($this->newField($name, $name));
}
}
return parent::bind($taintedData);
} }
foreach ($taintedData as $name => $value) protected function newField($key, $propertyPath)
{ {
if (!isset($this[$name]) && $this->getOption('modifiable')) $field = clone $this->prototype;
{ $field->setKey($key);
$this->add($this->newField($name, $name)); $field->setPropertyPath($propertyPath === null ? null : '['.$propertyPath.']');
} return $field;
} }
return parent::bind($taintedData);
}
protected function newField($key, $propertyPath)
{
$field = clone $this->prototype;
$field->setKey($key);
$field->setPropertyPath($propertyPath === null ? null : '['.$propertyPath.']');
return $field;
}
} }

View File

@ -19,124 +19,119 @@ use Symfony\Components\Form\Exception\InvalidOptionsException;
*/ */
abstract class Configurable abstract class Configurable
{ {
/** /**
* The options and their values * The options and their values
* @var array * @var array
*/ */
private $options = array(); private $options = array();
/** /**
* The names of the valid options * The names of the valid options
* @var array * @var array
*/ */
private $knownOptions = array(); private $knownOptions = array();
/** /**
* The names of the required options * The names of the required options
* @var array * @var array
*/ */
private $requiredOptions = array(); private $requiredOptions = array();
/** /**
* The allowed values for each option * The allowed values for each option
* @var array * @var array
*/ */
private $allowedValues = array(); private $allowedValues = array();
/** /**
* Reads, validates and stores the given options * Reads, validates and stores the given options
* *
* @param array $options * @param array $options
*/ */
public function __construct(array $options = array()) public function __construct(array $options = array())
{
$this->options = array_merge($this->options, $options);
$this->configure();
// check option names
if ($diff = array_diff_key($this->options, $this->knownOptions))
{ {
throw new InvalidOptionsException(sprintf('%s does not support the following options: "%s".', get_class($this), implode('", "', array_keys($diff))), array_keys($diff)); $this->options = array_merge($this->options, $options);
$this->configure();
// check option names
if ($diff = array_diff_key($this->options, $this->knownOptions)) {
throw new InvalidOptionsException(sprintf('%s does not support the following options: "%s".', get_class($this), implode('", "', array_keys($diff))), array_keys($diff));
}
// check required options
if ($diff = array_diff_key($this->requiredOptions, $this->options)) {
throw new MissingOptionsException(sprintf('%s requires the following options: \'%s\'.', get_class($this), implode('", "', array_keys($diff))), array_keys($diff));
}
} }
// check required options /**
if ($diff = array_diff_key($this->requiredOptions, $this->options)) * Configures the valid options
*
* This method should call addOption() or addRequiredOption() for every
* accepted option.
*/
protected function configure()
{ {
throw new MissingOptionsException(sprintf('%s requires the following options: \'%s\'.', get_class($this), implode('", "', array_keys($diff))), array_keys($diff));
}
}
/**
* Configures the valid options
*
* This method should call addOption() or addRequiredOption() for every
* accepted option.
*/
protected function configure()
{
}
/**
* Returns an option value.
*
* @param string $name The option name
*
* @return mixed The option value
*/
public function getOption($name)
{
return array_key_exists($name, $this->options) ? $this->options[$name] : null;
}
/**
* Adds a new option value with a default value.
*
* @param string $name The option name
* @param mixed $value The default value
*/
protected function addOption($name, $value = null, array $allowedValues = array())
{
$this->knownOptions[$name] = true;
if (!array_key_exists($name, $this->options))
{
$this->options[$name] = $value;
} }
if (count($allowedValues) > 0 && !in_array($this->options[$name], $allowedValues)) /**
* Returns an option value.
*
* @param string $name The option name
*
* @return mixed The option value
*/
public function getOption($name)
{ {
throw new InvalidOptionsException(sprintf('The option "%s" is expected to be one of "%s", but is "%s"', $name, implode('", "', $allowedValues), $this->options[$name]), array($name)); return array_key_exists($name, $this->options) ? $this->options[$name] : null;
} }
}
/** /**
* Adds a required option. * Adds a new option value with a default value.
* *
* @param string $name The option name * @param string $name The option name
*/ * @param mixed $value The default value
protected function addRequiredOption($name, array $allowedValues = array()) */
{ protected function addOption($name, $value = null, array $allowedValues = array())
$this->knownOptions[$name] = true;
$this->requiredOptions[$name] = true;
// only test if the option is set, otherwise an error will be thrown
// anyway
if (isset($this->options[$name]) && count($allowedValues) > 0 && !in_array($this->options[$name], $allowedValues))
{ {
throw new InvalidOptionsException(sprintf('The option "%s" is expected to be one of "%s", but is "%s"', $name, implode('", "', $allowedValues), $this->options[$name]), array($name)); $this->knownOptions[$name] = true;
}
}
/** if (!array_key_exists($name, $this->options)) {
* Returns true if the option exists. $this->options[$name] = $value;
* }
* @param string $name The option name
* if (count($allowedValues) > 0 && !in_array($this->options[$name], $allowedValues)) {
* @return bool true if the option is set, false otherwise throw new InvalidOptionsException(sprintf('The option "%s" is expected to be one of "%s", but is "%s"', $name, implode('", "', $allowedValues), $this->options[$name]), array($name));
*/ }
public function hasOption($name) }
{
return isset($this->options[$name]); /**
} * Adds a required option.
*
* @param string $name The option name
*/
protected function addRequiredOption($name, array $allowedValues = array())
{
$this->knownOptions[$name] = true;
$this->requiredOptions[$name] = true;
// only test if the option is set, otherwise an error will be thrown
// anyway
if (isset($this->options[$name]) && count($allowedValues) > 0 && !in_array($this->options[$name], $allowedValues)) {
throw new InvalidOptionsException(sprintf('The option "%s" is expected to be one of "%s", but is "%s"', $name, implode('", "', $allowedValues), $this->options[$name]), array($name));
}
}
/**
* Returns true if the option exists.
*
* @param string $name The option name
*
* @return bool true if the option is set, false otherwise
*/
public function hasOption($name)
{
return isset($this->options[$name]);
}
} }

View File

@ -4,11 +4,11 @@ namespace Symfony\Components\Form\Configurator;
interface ConfiguratorInterface interface ConfiguratorInterface
{ {
public function initialize($object); public function initialize($object);
public function getClass($fieldName); public function getClass($fieldName);
public function getOptions($fieldName); public function getOptions($fieldName);
public function isRequired($fieldName); public function isRequired($fieldName);
} }

View File

@ -4,32 +4,32 @@ namespace Symfony\Components\Form\Configurator;
class ValidatorConfigurator implements ConfiguratorInterface class ValidatorConfigurator implements ConfiguratorInterface
{ {
protected $metaData = null; protected $metaData = null;
protected $classMetaData = null; protected $classMetaData = null;
public function __construct(MetaDataInterface $metaData) public function __construct(MetaDataInterface $metaData)
{ {
$this->metaData = $metaData; $this->metaData = $metaData;
} }
public function initialize($object) public function initialize($object)
{ {
$this->classMetaData = $this->metaData->getClassMetaData(get_class($object)); $this->classMetaData = $this->metaData->getClassMetaData(get_class($object));
} }
public function getClass($fieldName) public function getClass($fieldName)
{ {
} }
public function getOptions($fieldName) public function getOptions($fieldName)
{ {
} }
public function isRequired($fieldName) public function isRequired($fieldName)
{ {
return $this->classMetaData->getPropertyMetaData($fieldName)->hasConstraint('NotNull') return $this->classMetaData->getPropertyMetaData($fieldName)->hasConstraint('NotNull')
|| $this->classMetaData->getPropertyMetaData($fieldName)->hasConstraint('NotEmpty'); || $this->classMetaData->getPropertyMetaData($fieldName)->hasConstraint('NotEmpty');
} }
} }

View File

@ -19,244 +19,224 @@ use Symfony\Components\Form\ValueTransformer\DateTimeToArrayTransformer;
class DateField extends HybridField class DateField extends HybridField
{ {
const FULL = 'full'; const FULL = 'full';
const LONG = 'long'; const LONG = 'long';
const MEDIUM = 'medium'; const MEDIUM = 'medium';
const SHORT = 'short'; const SHORT = 'short';
const DATETIME = 'datetime'; const DATETIME = 'datetime';
const STRING = 'string'; const STRING = 'string';
const TIMESTAMP = 'timestamp'; const TIMESTAMP = 'timestamp';
const RAW = 'raw'; const RAW = 'raw';
const INPUT = 'input'; const INPUT = 'input';
const CHOICE = 'choice'; const CHOICE = 'choice';
protected static $formats = array( protected static $formats = array(
self::FULL, self::FULL,
self::LONG, self::LONG,
self::MEDIUM, self::MEDIUM,
self::SHORT, self::SHORT,
);
protected static $intlFormats = array(
self::FULL => \IntlDateFormatter::FULL,
self::LONG => \IntlDateFormatter::LONG,
self::MEDIUM => \IntlDateFormatter::MEDIUM,
self::SHORT => \IntlDateFormatter::SHORT,
);
protected static $widgets = array(
self::INPUT,
self::CHOICE,
);
protected static $types = array(
self::DATETIME,
self::STRING,
self::TIMESTAMP,
self::RAW,
);
/**
* The ICU formatter instance
* @var \IntlDateFormatter
*/
protected $formatter;
/**
* Configures the text field.
*
* Available options:
*
* * widget: How to render the field ("input" or "select"). Default: "input"
* * years: An array of years for the year select tag (optional)
* * months: An array of months for the month select tag (optional)
* * days: An array of days for the day select tag (optional)
* * format: See DateValueTransformer. Default: medium
* * type: The type of the date ("date", "datetime" or "timestamp"). Default: "date"
* * data_timezone: The timezone of the data
* * user_timezone: The timezone of the user entering a new value
* * pattern: The pattern for the select boxes when "widget" is "select".
* You can use the placeholders "%year%", "%month%" and "%day%".
* Default: locale dependent
*
* @param array $options Options for this field
* @throws \InvalidArgumentException Thrown if you want to show a timestamp with the select widget.
*/
protected function configure()
{
$this->addOption('years', range(date('Y') - 5, date('Y') + 5));
$this->addOption('months', range(1, 12));
$this->addOption('days', range(1, 31));
$this->addOption('format', self::MEDIUM, self::$formats);
$this->addOption('type', self::DATETIME, self::$types);
$this->addOption('data_timezone', 'UTC');
$this->addOption('user_timezone', 'UTC');
$this->addOption('widget', self::CHOICE, self::$widgets);
$this->addOption('pattern');
$this->formatter = new \IntlDateFormatter(
$this->locale,
self::$intlFormats[$this->getOption('format')],
\IntlDateFormatter::NONE
); );
$transformers = array(); protected static $intlFormats = array(
self::FULL => \IntlDateFormatter::FULL,
self::LONG => \IntlDateFormatter::LONG,
self::MEDIUM => \IntlDateFormatter::MEDIUM,
self::SHORT => \IntlDateFormatter::SHORT,
);
if ($this->getOption('type') === self::STRING) protected static $widgets = array(
self::INPUT,
self::CHOICE,
);
protected static $types = array(
self::DATETIME,
self::STRING,
self::TIMESTAMP,
self::RAW,
);
/**
* The ICU formatter instance
* @var \IntlDateFormatter
*/
protected $formatter;
/**
* Configures the text field.
*
* Available options:
*
* * widget: How to render the field ("input" or "select"). Default: "input"
* * years: An array of years for the year select tag (optional)
* * months: An array of months for the month select tag (optional)
* * days: An array of days for the day select tag (optional)
* * format: See DateValueTransformer. Default: medium
* * type: The type of the date ("date", "datetime" or "timestamp"). Default: "date"
* * data_timezone: The timezone of the data
* * user_timezone: The timezone of the user entering a new value
* * pattern: The pattern for the select boxes when "widget" is "select".
* You can use the placeholders "%year%", "%month%" and "%day%".
* Default: locale dependent
*
* @param array $options Options for this field
* @throws \InvalidArgumentException Thrown if you want to show a timestamp with the select widget.
*/
protected function configure()
{ {
$transformers[] = new StringToDateTimeTransformer(array( $this->addOption('years', range(date('Y') - 5, date('Y') + 5));
'input_timezone' => $this->getOption('data_timezone'), $this->addOption('months', range(1, 12));
'output_timezone' => $this->getOption('data_timezone'), $this->addOption('days', range(1, 31));
'format' => 'Y-m-d', $this->addOption('format', self::MEDIUM, self::$formats);
)); $this->addOption('type', self::DATETIME, self::$types);
} $this->addOption('data_timezone', 'UTC');
else if ($this->getOption('type') === self::TIMESTAMP) $this->addOption('user_timezone', 'UTC');
{ $this->addOption('widget', self::CHOICE, self::$widgets);
$transformers[] = new TimestampToDateTimeTransformer(array( $this->addOption('pattern');
'output_timezone' => $this->getOption('data_timezone'),
'input_timezone' => $this->getOption('data_timezone'), $this->formatter = new \IntlDateFormatter(
)); $this->locale,
} self::$intlFormats[$this->getOption('format')],
else if ($this->getOption('type') === self::RAW) \IntlDateFormatter::NONE
{ );
$transformers[] = new ReversedTransformer(new DateTimeToArrayTransformer(array(
'input_timezone' => $this->getOption('data_timezone'), $transformers = array();
'output_timezone' => $this->getOption('data_timezone'),
'fields' => array('year', 'month', 'day'), if ($this->getOption('type') === self::STRING) {
))); $transformers[] = new StringToDateTimeTransformer(array(
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('data_timezone'),
'format' => 'Y-m-d',
));
} else if ($this->getOption('type') === self::TIMESTAMP) {
$transformers[] = new TimestampToDateTimeTransformer(array(
'output_timezone' => $this->getOption('data_timezone'),
'input_timezone' => $this->getOption('data_timezone'),
));
} else if ($this->getOption('type') === self::RAW) {
$transformers[] = new ReversedTransformer(new DateTimeToArrayTransformer(array(
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('data_timezone'),
'fields' => array('year', 'month', 'day'),
)));
}
if ($this->getOption('widget') === self::INPUT) {
$transformers[] = new DateTimeToLocalizedStringTransformer(array(
'date_format' => $this->getOption('format'),
'time_format' => DateTimeToLocalizedStringTransformer::NONE,
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('user_timezone'),
));
$this->setFieldMode(self::FIELD);
} else {
$transformers[] = new DateTimeToArrayTransformer(array(
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('user_timezone'),
));
$this->setFieldMode(self::GROUP);
$this->add(new ChoiceField('year', array(
'choices' => $this->generatePaddedChoices($this->getOption('years'), 4),
)));
$this->add(new ChoiceField('month', array(
'choices' => $this->generateMonthChoices($this->getOption('months')),
)));
$this->add(new ChoiceField('day', array(
'choices' => $this->generatePaddedChoices($this->getOption('days'), 2),
)));
}
if (count($transformers) > 0) {
$this->setValueTransformer(new ValueTransformerChain($transformers));
}
} }
if ($this->getOption('widget') === self::INPUT) /**
* Generates an array of choices for the given values
*
* If the values are shorter than $padLength characters, they are padded with
* zeros on the left side.
*
* @param array $values The available choices
* @param integer $padLength The length to pad the choices
* @return array An array with the input values as keys and the
* padded values as values
*/
protected function generatePaddedChoices(array $values, $padLength)
{ {
$transformers[] = new DateTimeToLocalizedStringTransformer(array( $choices = array();
'date_format' => $this->getOption('format'),
'time_format' => DateTimeToLocalizedStringTransformer::NONE,
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('user_timezone'),
));
$this->setFieldMode(self::FIELD); foreach ($values as $value) {
} $choices[$value] = str_pad($value, $padLength, '0', STR_PAD_LEFT);
else }
{
$transformers[] = new DateTimeToArrayTransformer(array(
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('user_timezone'),
));
$this->setFieldMode(self::GROUP); return $choices;
$this->add(new ChoiceField('year', array(
'choices' => $this->generatePaddedChoices($this->getOption('years'), 4),
)));
$this->add(new ChoiceField('month', array(
'choices' => $this->generateMonthChoices($this->getOption('months')),
)));
$this->add(new ChoiceField('day', array(
'choices' => $this->generatePaddedChoices($this->getOption('days'), 2),
)));
} }
if (count($transformers) > 0) /**
* Generates an array of localized month choices
*
* @param array $months The month numbers to generate
* @return array The localized months respecting the configured
* locale and date format
*/
protected function generateMonthChoices(array $months)
{ {
$this->setValueTransformer(new ValueTransformerChain($transformers)); $pattern = $this->formatter->getPattern();
}
}
/** if (preg_match('/M+/', $pattern, $matches)) {
* Generates an array of choices for the given values $this->formatter->setPattern($matches[0]);
* $choices = array();
* If the values are shorter than $padLength characters, they are padded with
* zeros on the left side.
*
* @param array $values The available choices
* @param integer $padLength The length to pad the choices
* @return array An array with the input values as keys and the
* padded values as values
*/
protected function generatePaddedChoices(array $values, $padLength)
{
$choices = array();
foreach ($values as $value) foreach ($months as $month) {
{ $choices[$month] = $this->formatter->format(gmmktime(0, 0, 0, $month));
$choices[$value] = str_pad($value, $padLength, '0', STR_PAD_LEFT); }
$this->formatter->setPattern($pattern);
} else {
$choices = $this->generatePaddedChoices($months, 2);
}
return $choices;
} }
return $choices; /**
} * {@inheritDoc}
*/
/** public function render(array $attributes = array())
* Generates an array of localized month choices
*
* @param array $months The month numbers to generate
* @return array The localized months respecting the configured
* locale and date format
*/
protected function generateMonthChoices(array $months)
{
$pattern = $this->formatter->getPattern();
if (preg_match('/M+/', $pattern, $matches))
{ {
$this->formatter->setPattern($matches[0]); if ($this->getOption('widget') === self::INPUT) {
$choices = array(); return $this->generator->tag('input', array_merge(array(
'id' => $this->getId(),
'name' => $this->getName(),
'value' => $this->getDisplayedData(),
'type' => 'text',
), $attributes));
} else {
// set order as specified in the pattern
if ($this->getOption('pattern')) {
$pattern = $this->getOption('pattern');
}
// set right order with respect to locale (e.g.: de_DE=dd.MM.yy; en_US=M/d/yy)
// lookup various formats at http://userguide.icu-project.org/formatparse/datetime
else if (preg_match('/^([yMd]+).+([yMd]+).+([yMd]+)$/', $this->formatter->getPattern())) {
$pattern = preg_replace(array('/y+/', '/M+/', '/d+/'), array('%year%', '%month%', '%day%'), $this->formatter->getPattern());
}
// default fallback
else {
$pattern = '%year%-%month%-%day%';
}
foreach ($months as $month) return str_replace(array('%year%', '%month%', '%day%'), array(
{ $this->get('year')->render($attributes),
$choices[$month] = $this->formatter->format(gmmktime(0, 0, 0, $month)); $this->get('month')->render($attributes),
} $this->get('day')->render($attributes),
), $pattern);
$this->formatter->setPattern($pattern); }
} }
else
{
$choices = $this->generatePaddedChoices($months, 2);
}
return $choices;
}
/**
* {@inheritDoc}
*/
public function render(array $attributes = array())
{
if ($this->getOption('widget') === self::INPUT)
{
return $this->generator->tag('input', array_merge(array(
'id' => $this->getId(),
'name' => $this->getName(),
'value' => $this->getDisplayedData(),
'type' => 'text',
), $attributes));
}
else
{
// set order as specified in the pattern
if ($this->getOption('pattern'))
{
$pattern = $this->getOption('pattern');
}
// set right order with respect to locale (e.g.: de_DE=dd.MM.yy; en_US=M/d/yy)
// lookup various formats at http://userguide.icu-project.org/formatparse/datetime
else if (preg_match('/^([yMd]+).+([yMd]+).+([yMd]+)$/', $this->formatter->getPattern()))
{
$pattern = preg_replace(array('/y+/', '/M+/', '/d+/'), array('%year%', '%month%', '%day%'), $this->formatter->getPattern());
}
// default fallback
else
{
$pattern = '%year%-%month%-%day%';
}
return str_replace(array('%year%', '%month%', '%day%'), array(
$this->get('year')->render($attributes),
$this->get('month')->render($attributes),
$this->get('day')->render($attributes),
), $pattern);
}
}
} }

View File

@ -14,115 +14,112 @@ use Symfony\Components\Form\ValueTransformer\ValueTransformerChain;
*/ */
class DateTimeField extends FieldGroup class DateTimeField extends FieldGroup
{ {
const DATETIME = 'datetime'; const DATETIME = 'datetime';
const STRING = 'string'; const STRING = 'string';
const TIMESTAMP = 'timestamp'; const TIMESTAMP = 'timestamp';
protected static $types = array( protected static $types = array(
self::DATETIME, self::DATETIME,
self::STRING, self::STRING,
self::TIMESTAMP, self::TIMESTAMP,
); );
protected static $dateWidgets = array( protected static $dateWidgets = array(
DateField::CHOICE, DateField::CHOICE,
DateField::INPUT, DateField::INPUT,
); );
protected static $timeWidgets = array( protected static $timeWidgets = array(
TimeField::CHOICE, TimeField::CHOICE,
TimeField::INPUT, TimeField::INPUT,
); );
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function configure() public function configure()
{
$this->addOption('years', range(date('Y') - 5, date('Y') + 5));
$this->addOption('months', range(1, 12));
$this->addOption('days', range(1, 31));
$this->addOption('hours', range(0, 23));
$this->addOption('minutes', range(0, 59));
$this->addOption('seconds', range(0, 59));
$this->addOption('data_timezone', 'UTC');
$this->addOption('user_timezone', 'UTC');
$this->addOption('date_widget', DateField::INPUT, self::$dateWidgets);
$this->addOption('time_widget', TimeField::CHOICE, self::$timeWidgets);
$this->addOption('type', self::DATETIME, self::$types);
$this->addOption('with_seconds', false);
$this->add(new DateField('date', array(
'type' => DateField::RAW,
'widget' => $this->getOption('date_widget'),
'data_timezone' => $this->getOption('user_timezone'),
'user_timezone' => $this->getOption('user_timezone'),
'years' => $this->getOption('years'),
'months' => $this->getOption('months'),
'days' => $this->getOption('days'),
)));
$this->add(new TimeField('time', array(
'type' => TimeField::RAW,
'widget' => $this->getOption('time_widget'),
'data_timezone' => $this->getOption('user_timezone'),
'user_timezone' => $this->getOption('user_timezone'),
'with_seconds' => $this->getOption('with_seconds'),
'hours' => $this->getOption('hours'),
'minutes' => $this->getOption('minutes'),
'seconds' => $this->getOption('seconds'),
)));
$transformers = array();
if ($this->getOption('type') == self::STRING)
{ {
$transformers[] = new StringToDateTimeTransformer(array( $this->addOption('years', range(date('Y') - 5, date('Y') + 5));
'input_timezone' => $this->getOption('data_timezone'), $this->addOption('months', range(1, 12));
'output_timezone' => $this->getOption('data_timezone'), $this->addOption('days', range(1, 31));
)); $this->addOption('hours', range(0, 23));
} $this->addOption('minutes', range(0, 59));
else if ($this->getOption('type') == self::TIMESTAMP) $this->addOption('seconds', range(0, 59));
{ $this->addOption('data_timezone', 'UTC');
$transformers[] = new TimestampToDateTimeTransformer(array( $this->addOption('user_timezone', 'UTC');
'input_timezone' => $this->getOption('data_timezone'), $this->addOption('date_widget', DateField::INPUT, self::$dateWidgets);
'output_timezone' => $this->getOption('data_timezone'), $this->addOption('time_widget', TimeField::CHOICE, self::$timeWidgets);
)); $this->addOption('type', self::DATETIME, self::$types);
$this->addOption('with_seconds', false);
$this->add(new DateField('date', array(
'type' => DateField::RAW,
'widget' => $this->getOption('date_widget'),
'data_timezone' => $this->getOption('user_timezone'),
'user_timezone' => $this->getOption('user_timezone'),
'years' => $this->getOption('years'),
'months' => $this->getOption('months'),
'days' => $this->getOption('days'),
)));
$this->add(new TimeField('time', array(
'type' => TimeField::RAW,
'widget' => $this->getOption('time_widget'),
'data_timezone' => $this->getOption('user_timezone'),
'user_timezone' => $this->getOption('user_timezone'),
'with_seconds' => $this->getOption('with_seconds'),
'hours' => $this->getOption('hours'),
'minutes' => $this->getOption('minutes'),
'seconds' => $this->getOption('seconds'),
)));
$transformers = array();
if ($this->getOption('type') == self::STRING) {
$transformers[] = new StringToDateTimeTransformer(array(
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('data_timezone'),
));
} else if ($this->getOption('type') == self::TIMESTAMP) {
$transformers[] = new TimestampToDateTimeTransformer(array(
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('data_timezone'),
));
}
$transformers[] = new DateTimeToArrayTransformer(array(
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('user_timezone'),
));
$this->setValueTransformer(new ValueTransformerChain($transformers));
} }
$transformers[] = new DateTimeToArrayTransformer(array( /**
'input_timezone' => $this->getOption('data_timezone'), * {@inheritDoc}
'output_timezone' => $this->getOption('user_timezone'), */
)); protected function transform($value)
{
$value = parent::transform($value);
$this->setValueTransformer(new ValueTransformerChain($transformers)); return array('date' => $value, 'time' => $value);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function transform($value) protected function reverseTransform($value)
{ {
$value = parent::transform($value); return parent::reverseTransform(array_merge($value['date'], $value['time']));
}
return array('date' => $value, 'time' => $value); /**
} * {@inheritDoc}
*/
public function render(array $attributes = array())
{
$html = $this->get('date')->render($attributes)."\n";
$html .= $this->get('time')->render($attributes);
/** return $html;
* {@inheritDoc} }
*/
protected function reverseTransform($value)
{
return parent::reverseTransform(array_merge($value['date'], $value['time']));
}
/**
* {@inheritDoc}
*/
public function render(array $attributes = array())
{
$html = $this->get('date')->render($attributes)."\n";
$html .= $this->get('time')->render($attributes);
return $html;
}
} }

View File

@ -4,17 +4,17 @@ namespace Symfony\Components\Form\Exception;
class InvalidOptionsException extends FormException class InvalidOptionsException extends FormException
{ {
private $options; private $options;
public function __construct($message, array $options) public function __construct($message, array $options)
{ {
parent::__construct($message); parent::__construct($message);
$this->options = $options; $this->options = $options;
} }
public function getOptions() public function getOptions()
{ {
return $this->options; return $this->options;
} }
} }

View File

@ -4,17 +4,17 @@ namespace Symfony\Components\Form\Exception;
class MissingOptionsException extends FormException class MissingOptionsException extends FormException
{ {
private $options; private $options;
public function __construct($message, array $options) public function __construct($message, array $options)
{ {
parent::__construct($message); parent::__construct($message);
$this->options = $options; $this->options = $options;
} }
public function getOptions() public function getOptions()
{ {
return $this->options; return $this->options;
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -12,248 +12,248 @@ use Symfony\Components\I18N\TranslatorInterface;
*/ */
interface FieldInterface extends Localizable, Translatable interface FieldInterface extends Localizable, Translatable
{ {
/** /**
* Marks a constraint violation in a form field * Marks a constraint violation in a form field
* @var integer * @var integer
*/ */
const FIELD_ERROR = 0; const FIELD_ERROR = 0;
/** /**
* Marks a constraint violation in the data of a form field * Marks a constraint violation in the data of a form field
* @var integer * @var integer
*/ */
const DATA_ERROR = 1; const DATA_ERROR = 1;
/** /**
* Clones this field. * Clones this field.
*/ */
public function __clone(); public function __clone();
/** /**
* Sets the parent field. * Sets the parent field.
* *
* @param FieldInterface $parent The parent field * @param FieldInterface $parent The parent field
*/ */
public function setParent(FieldInterface $parent = null); public function setParent(FieldInterface $parent = null);
/** /**
* Sets the key by which the field is identified in field groups. * Sets the key by which the field is identified in field groups.
* *
* Once this field is nested in a field group, i.e. after setParent() was * Once this field is nested in a field group, i.e. after setParent() was
* called for the first time, this method should throw an exception. * called for the first time, this method should throw an exception.
* *
* @param string $key The key of the field * @param string $key The key of the field
* @throws BadMethodCallException When the field already has a parent * @throws BadMethodCallException When the field already has a parent
*/ */
public function setKey($key); public function setKey($key);
/** /**
* Returns the key by which the field is identified in field groups. * Returns the key by which the field is identified in field groups.
* *
* @return string The key of the field. * @return string The key of the field.
*/ */
public function getKey(); public function getKey();
/** /**
* Returns the name of the field. * Returns the name of the field.
* *
* @return string When the field has no parent, the name is equal to its * @return string When the field has no parent, the name is equal to its
* key. If the field has a parent, the name is composed of * key. If the field has a parent, the name is composed of
* the parent's name and the field's key, where the field's * the parent's name and the field's key, where the field's
* key is wrapped in squared brackets * key is wrapped in squared brackets
* (e.g. "parent_name[field_key]") * (e.g. "parent_name[field_key]")
*/ */
public function getName(); public function getName();
/** /**
* Returns the ID of the field. * Returns the ID of the field.
* *
* @return string The ID of a field is equal to its name, where all * @return string The ID of a field is equal to its name, where all
* sequences of squared brackets are replaced by a single * sequences of squared brackets are replaced by a single
* underscore (e.g. if the name is "parent_name[field_key]", * underscore (e.g. if the name is "parent_name[field_key]",
* the ID is "parent_name_field_key"). * the ID is "parent_name_field_key").
*/ */
public function getId(); public function getId();
/** /**
* Sets the property path * Sets the property path
* *
* The property path determines the property or a sequence of properties * The property path determines the property or a sequence of properties
* that a field updates in the data of the field group. * that a field updates in the data of the field group.
* *
* @param string $propertyPath * @param string $propertyPath
*/ */
public function setPropertyPath($propertyPath); public function setPropertyPath($propertyPath);
/** /**
* Returns the property path of the field * Returns the property path of the field
* *
* @return PropertyPath * @return PropertyPath
*/ */
public function getPropertyPath(); public function getPropertyPath();
/** /**
* Writes a property value of the object into the field * Writes a property value of the object into the field
* *
* The chosen property is determined by the field's property path. * The chosen property is determined by the field's property path.
* *
* @param array|object $objectOrArray * @param array|object $objectOrArray
*/ */
public function updateFromObject(&$objectOrArray); public function updateFromObject(&$objectOrArray);
/** /**
* Writes a the field value into a property of the object * Writes a the field value into a property of the object
* *
* The chosen property is determined by the field's property path. * The chosen property is determined by the field's property path.
* *
* @param array|object $objectOrArray * @param array|object $objectOrArray
*/ */
public function updateObject(&$objectOrArray); public function updateObject(&$objectOrArray);
/** /**
* Returns the normalized data of the field. * Returns the normalized data of the field.
* *
* @return mixed When the field is not bound, the default data is returned. * @return mixed When the field is not bound, the default data is returned.
* When the field is bound, the normalized bound data is * When the field is bound, the normalized bound data is
* returned if the field is valid, null otherwise. * returned if the field is valid, null otherwise.
*/ */
public function getData(); public function getData();
/** /**
* Returns the data of the field as it is displayed to the user. * Returns the data of the field as it is displayed to the user.
* *
* @return string|array When the field is not bound, the transformed * @return string|array When the field is not bound, the transformed
* default data is returned. When the field is bound, * default data is returned. When the field is bound,
* the bound data is returned. * the bound data is returned.
*/ */
public function getDisplayedData(); public function getDisplayedData();
/** /**
* Sets the default data * Sets the default data
* *
* @param mixed $default The default data * @param mixed $default The default data
* @throws UnexpectedTypeException If the default data is invalid * @throws UnexpectedTypeException If the default data is invalid
*/ */
public function setData($default); public function setData($default);
/** /**
* Binds POST data to the field, transforms and validates it. * Binds POST data to the field, transforms and validates it.
* *
* @param string|array $taintedData The POST data * @param string|array $taintedData The POST data
* @return boolean Whether the form is valid * @return boolean Whether the form is valid
* @throws InvalidConfigurationException when the field is not configured * @throws InvalidConfigurationException when the field is not configured
* correctly * correctly
*/ */
public function bind($taintedData); public function bind($taintedData);
/** /**
* Recursively adds constraint violations to the fields * Recursively adds constraint violations to the fields
* *
* Violations in the form fields usually have property paths like: * Violations in the form fields usually have property paths like:
* *
* <code> * <code>
* iterator[firstName].data * iterator[firstName].data
* iterator[firstName].displayedData * iterator[firstName].displayedData
* iterator[Address].iterator[street].displayedData * iterator[Address].iterator[street].displayedData
* ... * ...
* </code> * </code>
* *
* Violations in the form data usually have property paths like: * Violations in the form data usually have property paths like:
* *
* <code> * <code>
* data.firstName * data.firstName
* data.Address.street * data.Address.street
* ... * ...
* </code> * </code>
* *
* @param FieldInterface $field * @param FieldInterface $field
* @param PropertyPath $path * @param PropertyPath $path
* @param ConstraintViolation$violation * @param ConstraintViolation$violation
*/ */
public function addError($message, PropertyPath $path = null, $type = null); public function addError($message, PropertyPath $path = null, $type = null);
/** /**
* Renders this field. * Renders this field.
* *
* @param array $attributes The attributes to include in the rendered * @param array $attributes The attributes to include in the rendered
* output * output
* @return string The rendered output of this field * @return string The rendered output of this field
*/ */
public function render(array $attributes = array()); public function render(array $attributes = array());
/** /**
* Renders the errors of this field. * Renders the errors of this field.
* *
* @return string The rendered output of the field errors * @return string The rendered output of the field errors
*/ */
public function renderErrors(); public function renderErrors();
/** /**
* Returns whether the field is bound. * Returns whether the field is bound.
* *
* @return boolean * @return boolean
*/ */
public function isBound(); public function isBound();
/** /**
* Returns whether the field is valid. * Returns whether the field is valid.
* *
* @return boolean * @return boolean
*/ */
public function isValid(); public function isValid();
/** /**
* Returns whether the field requires a multipart form. * Returns whether the field requires a multipart form.
* *
* @return boolean * @return boolean
*/ */
public function isMultipart(); public function isMultipart();
/** /**
* Returns whether the field is required to be filled out. * Returns whether the field is required to be filled out.
* *
* If the field has a parent and the parent is not required, this method * If the field has a parent and the parent is not required, this method
* will always return false. Otherwise the value set with setRequired() * will always return false. Otherwise the value set with setRequired()
* is returned. * is returned.
* *
* @return boolean * @return boolean
*/ */
public function isRequired(); public function isRequired();
/** /**
* Returns whether this field is disabled * Returns whether this field is disabled
* *
* The content of a disabled field is displayed, but not allowed to be * The content of a disabled field is displayed, but not allowed to be
* modified. The validation of modified, disabled fields should fail. * modified. The validation of modified, disabled fields should fail.
* *
* Fields whose parents are disabled are considered disabled regardless of * Fields whose parents are disabled are considered disabled regardless of
* their own state. * their own state.
* *
* @return boolean * @return boolean
*/ */
public function isDisabled(); public function isDisabled();
/** /**
* Returns whether the field is hidden * Returns whether the field is hidden
* *
* @return boolean * @return boolean
*/ */
public function isHidden(); public function isHidden();
/** /**
* Sets whether this field is required to be filled out when submitted. * Sets whether this field is required to be filled out when submitted.
* *
* @param boolean $required * @param boolean $required
*/ */
public function setRequired($required); public function setRequired($required);
/** /**
* Sets the generator used for rendering HTML. * Sets the generator used for rendering HTML.
* *
* Usually there is one generator instance shared between all fields of a * Usually there is one generator instance shared between all fields of a
* form. * form.
* *
* @param string $charset * @param string $charset
*/ */
public function setGenerator(HtmlGeneratorInterface $generator); public function setGenerator(HtmlGeneratorInterface $generator);
} }

View File

@ -34,528 +34,491 @@ use Symfony\Components\File\UploadedFile;
*/ */
class Form extends FieldGroup class Form extends FieldGroup
{ {
protected static $defaultCsrfSecret = null; protected static $defaultCsrfSecret = null;
protected static $defaultCsrfProtection = false; protected static $defaultCsrfProtection = false;
protected static $defaultCsrfFieldName = '_csrf_token'; protected static $defaultCsrfFieldName = '_csrf_token';
protected static $defaultLocale = null; protected static $defaultLocale = null;
protected static $defaultTranslator = null; protected static $defaultTranslator = null;
protected $validator = null; protected $validator = null;
protected $validationGroups = null; protected $validationGroups = null;
private $csrfSecret = null; private $csrfSecret = null;
private $csrfFieldName = null; private $csrfFieldName = null;
/** /**
* Constructor. * Constructor.
* *
* @param array $defaults An array of field default values * @param array $defaults An array of field default values
* @param array $options An array of options * @param array $options An array of options
* @param string $defaultCsrfSecret A Csrf secret * @param string $defaultCsrfSecret A Csrf secret
*/ */
public function __construct($name, $object, ValidatorInterface $validator, array $options = array()) public function __construct($name, $object, ValidatorInterface $validator, array $options = array())
{
$this->generator = new HtmlGenerator();
$this->validator = $validator;
parent::__construct($name, $options);
$this->setData($object);
$this->setCsrfFieldName(self::$defaultCsrfFieldName);
if (self::$defaultCsrfSecret !== null)
{ {
$this->setCsrfSecret(self::$defaultCsrfSecret); $this->generator = new HtmlGenerator();
} $this->validator = $validator;
else
{
$this->setCsrfSecret(md5(__FILE__.php_uname()));
}
if (self::$defaultCsrfProtection !== false) parent::__construct($name, $options);
{
$this->enableCsrfProtection();
}
if (self::$defaultLocale !== null) $this->setData($object);
{ $this->setCsrfFieldName(self::$defaultCsrfFieldName);
$this->setLocale(self::$defaultLocale);
}
if (self::$defaultTranslator !== null) if (self::$defaultCsrfSecret !== null) {
{ $this->setCsrfSecret(self::$defaultCsrfSecret);
$this->setTranslator(self::$defaultTranslator); } else {
} $this->setCsrfSecret(md5(__FILE__.php_uname()));
}
/**
* Sets the charset used for rendering HTML
*
* This method overrides the internal HTML generator! If you want to use
* your own generator, use setGenerator() instead.
*
* @param string $charset
*/
public function setCharset($charset)
{
$this->setGenerator(new HtmlGenerator($charset));
}
/**
* Sets the validation groups for this form.
*
* @param array|string $validationGroups
*/
public function setValidationGroups($validationGroups)
{
$this->validationGroups = $validationGroups === null ? $validationGroups : (array) $validationGroups;
}
/**
* Returns the validation groups for this form.
*
* @return array
*/
public function getValidationGroups()
{
return $this->validationGroups;
}
/**
* Sets the default locale for newly created forms.
*
* @param string $defaultLocale
*/
static public function setDefaultLocale($defaultLocale)
{
self::$defaultLocale = $defaultLocale;
}
/**
* Returns the default locale for newly created forms.
*
* @return string
*/
static public function getDefaultLocale()
{
return self::$defaultLocale;
}
/**
* Sets the default translator for newly created forms.
*
* @param TranslatorInterface $defaultTranslator
*/
static public function setDefaultTranslator(TranslatorInterface $defaultTranslator)
{
self::$defaultTranslator = $defaultTranslator;
}
/**
* Returns the default translator for newly created forms.
*
* @return TranslatorInterface
*/
static public function getDefaultTranslator()
{
return self::$defaultTranslator;
}
/**
* Binds the form with values and files.
*
* This method is final because it is very easy to break a form when
* overriding this method and adding logic that depends on $taintedFiles.
* You should override doBind() instead where the uploaded files are
* already merged into the data array.
*
* @param array $taintedValues The form data of the $_POST array
* @param array $taintedFiles The form data of the $_FILES array
* @return boolean Whether the form is valid
*/
final public function bind($taintedValues, array $taintedFiles = null)
{
if ($taintedFiles === null)
{
if ($this->isMultipart() && $this->getParent() === null)
{
throw new \InvalidArgumentException('You must provide a files array for multipart forms');
}
$taintedFiles = array();
}
else
{
$taintedFiles = self::convertFileInformation(self::fixPhpFilesArray($taintedFiles));
}
$this->doBind(self::deepArrayUnion($taintedValues, $taintedFiles));
if ($this->getParent() === null)
{
if ($violations = $this->validator->validate($this, $this->getValidationGroups()))
{
foreach ($violations as $violation)
{
$propertyPath = new PropertyPath($violation->getPropertyPath());
if ($propertyPath->getCurrent() == 'data')
{
$type = self::DATA_ERROR;
$propertyPath->next(); // point at the first data element
}
else
{
$type = self::FIELD_ERROR;
}
$this->addError($violation->getMessage(), $propertyPath, $type);
} }
}
}
}
/** if (self::$defaultCsrfProtection !== false) {
* Binds the form with the given data. $this->enableCsrfProtection();
* }
* @param array $taintedData The data to bind to the form
* @return boolean Whether the form is valid
*/
protected function doBind(array $taintedData)
{
parent::bind($taintedData);
}
/** if (self::$defaultLocale !== null) {
* Gets the stylesheet paths associated with the form. $this->setLocale(self::$defaultLocale);
* }
* @return array An array of stylesheet paths
*/
public function getStylesheets()
{
return $this->getWidget()->getStylesheets();
}
/** if (self::$defaultTranslator !== null) {
* Gets the JavaScript paths associated with the form. $this->setTranslator(self::$defaultTranslator);
* }
* @return array An array of JavaScript paths
*/
public function getJavaScripts()
{
return $this->getWidget()->getJavaScripts();
}
/**
* Returns a CSRF token for the set CSRF secret
*
* If you want to change the algorithm used to compute the token, you
* can override this method.
*
* @param string $secret The secret string to use (null to use the current secret)
*
* @return string A token string
*/
protected function getCsrfToken()
{
return md5($this->csrfSecret.session_id().get_class($this));
}
/**
* @return true if this form is CSRF protected
*/
public function isCsrfProtected()
{
return $this->has($this->getCsrfFieldName());
}
/**
* Enables CSRF protection for this form.
*/
public function enableCsrfProtection()
{
if (!$this->isCsrfProtected())
{
$field = new HiddenField($this->getCsrfFieldName(), array(
'property_path' => null,
));
$field->setData($this->getCsrfToken());
$this->add($field);
}
}
/**
* Disables CSRF protection for this form.
*/
public function disableCsrfProtection()
{
if ($this->isCsrfProtected())
{
$this->remove($this->getCsrfFieldName());
}
}
/**
* Sets the CSRF field name used in this form
*
* @param string $name The CSRF field name
*/
public function setCsrfFieldName($name)
{
$this->csrfFieldName = $name;
}
/**
* Returns the CSRF field name used in this form
*
* @return string The CSRF field name
*/
public function getCsrfFieldName()
{
return $this->csrfFieldName;
}
/**
* Sets the CSRF secret used in this form
*
* @param string $secret
*/
public function setCsrfSecret($secret)
{
$this->csrfSecret = $secret;
}
/**
* Returns the CSRF secret used in this form
*
* @return string
*/
public function getCsrfSecret()
{
return $this->csrfSecret;
}
/**
* Returns whether the CSRF token is valid
*
* @return boolean
*/
public function isCsrfTokenValid()
{
if (!$this->isCsrfProtected())
{
return true;
}
else
{
return $this->get($this->getCsrfFieldName())->getDisplayedData() === $this->getCsrfToken();
}
}
/**
* Enables CSRF protection for all new forms
*/
static public function enableDefaultCsrfProtection()
{
self::$defaultCsrfProtection = true;
}
/**
* Disables Csrf protection for all forms.
*/
static public function disableDefaultCsrfProtection()
{
self::$defaultCsrfProtection = false;
}
/**
* Sets the CSRF field name used in all new CSRF protected forms
*
* @param string $name The CSRF field name
*/
static public function setDefaultCsrfFieldName($name)
{
self::$defaultCsrfFieldName = $name;
}
/**
* Returns the default CSRF field name
*
* @return string The CSRF field name
*/
static public function getDefaultCsrfFieldName()
{
return self::$defaultCsrfFieldName;
}
/**
* Sets the CSRF secret used in all new CSRF protected forms
*
* @param string $secret
*/
static public function setDefaultCsrfSecret($secret)
{
self::$defaultCsrfSecret = $secret;
}
/**
* Returns the default CSRF secret
*
* @return string
*/
static public function getDefaultCsrfSecret()
{
return self::$defaultCsrfSecret;
}
/**
* 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 renderFormTag($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->isMultipart() ? 'multipart/form-data' : null,
), $attributes)));
}
/**
* Returns whether the maximum POST size was reached in this request.
*
* @return boolean
*/
public function isPostMaxSizeReached()
{
if (isset($_SERVER['CONTENT_LENGTH']))
{
$length = (int) $_SERVER['CONTENT_LENGTH'];
$max = trim(ini_get('post_max_size'));
switch (strtolower(substr($max, -1)))
{
// The 'G' modifier is available since PHP 5.1.0
case 'g':
$max *= 1024;
case 'm':
$max *= 1024;
case 'k':
$max *= 1024;
}
return $length > $max;
}
else
{
return false;
}
}
/**
* Merges two arrays without reindexing numeric keys.
*
* @param array $array1 An array to merge
* @param array $array2 An array to merge
*
* @return array The merged array
*/
static protected function deepArrayUnion($array1, $array2)
{
foreach ($array2 as $key => $value)
{
if (is_array($value) && isset($array1[$key]) && is_array($array1[$key]))
{
$array1[$key] = self::deepArrayUnion($array1[$key], $value);
}
else
{
$array1[$key] = $value;
}
} }
return $array1; /**
} * Sets the charset used for rendering HTML
*
/** * This method overrides the internal HTML generator! If you want to use
* Fixes a malformed PHP $_FILES array. * your own generator, use setGenerator() instead.
* *
* PHP has a bug that the format of the $_FILES array differs, depending on * @param string $charset
* whether the uploaded file fields had normal field names or array-like */
* field names ("normal" vs. "parent[child]"). public function setCharset($charset)
*
* This method fixes the array to look like the "normal" $_FILES array.
*
* @param array $data
* @return array
*/
static protected function fixPhpFilesArray(array $data)
{
$fileKeys = array('error', 'name', 'size', 'tmp_name', 'type');
$keys = array_keys($data);
sort($keys);
$files = $data;
if ($fileKeys == $keys && isset($data['name']) && is_array($data['name']))
{ {
foreach ($fileKeys as $k) $this->setGenerator(new HtmlGenerator($charset));
{
unset($files[$k]);
}
foreach (array_keys($data['name']) as $key)
{
$files[$key] = self::fixPhpFilesArray(array(
'error' => $data['error'][$key],
'name' => $data['name'][$key],
'type' => $data['type'][$key],
'tmp_name' => $data['tmp_name'][$key],
'size' => $data['size'][$key],
));
}
} }
return $files; /**
} * Sets the validation groups for this form.
*
/** * @param array|string $validationGroups
* Converts uploaded files to instances of clsas UploadedFile. */
* public function setValidationGroups($validationGroups)
* @param array $files A (multi-dimensional) array of uploaded file information
* @return array A (multi-dimensional) array of UploadedFile instances
*/
static protected function convertFileInformation(array $files)
{
$fileKeys = array('error', 'name', 'size', 'tmp_name', 'type');
foreach ($files as $key => $data)
{ {
if (is_array($data)) $this->validationGroups = $validationGroups === null ? $validationGroups : (array) $validationGroups;
{ }
/**
* Returns the validation groups for this form.
*
* @return array
*/
public function getValidationGroups()
{
return $this->validationGroups;
}
/**
* Sets the default locale for newly created forms.
*
* @param string $defaultLocale
*/
static public function setDefaultLocale($defaultLocale)
{
self::$defaultLocale = $defaultLocale;
}
/**
* Returns the default locale for newly created forms.
*
* @return string
*/
static public function getDefaultLocale()
{
return self::$defaultLocale;
}
/**
* Sets the default translator for newly created forms.
*
* @param TranslatorInterface $defaultTranslator
*/
static public function setDefaultTranslator(TranslatorInterface $defaultTranslator)
{
self::$defaultTranslator = $defaultTranslator;
}
/**
* Returns the default translator for newly created forms.
*
* @return TranslatorInterface
*/
static public function getDefaultTranslator()
{
return self::$defaultTranslator;
}
/**
* Binds the form with values and files.
*
* This method is final because it is very easy to break a form when
* overriding this method and adding logic that depends on $taintedFiles.
* You should override doBind() instead where the uploaded files are
* already merged into the data array.
*
* @param array $taintedValues The form data of the $_POST array
* @param array $taintedFiles The form data of the $_FILES array
* @return boolean Whether the form is valid
*/
final public function bind($taintedValues, array $taintedFiles = null)
{
if ($taintedFiles === null) {
if ($this->isMultipart() && $this->getParent() === null) {
throw new \InvalidArgumentException('You must provide a files array for multipart forms');
}
$taintedFiles = array();
} else {
$taintedFiles = self::convertFileInformation(self::fixPhpFilesArray($taintedFiles));
}
$this->doBind(self::deepArrayUnion($taintedValues, $taintedFiles));
if ($this->getParent() === null) {
if ($violations = $this->validator->validate($this, $this->getValidationGroups())) {
foreach ($violations as $violation) {
$propertyPath = new PropertyPath($violation->getPropertyPath());
if ($propertyPath->getCurrent() == 'data') {
$type = self::DATA_ERROR;
$propertyPath->next(); // point at the first data element
} else {
$type = self::FIELD_ERROR;
}
$this->addError($violation->getMessage(), $propertyPath, $type);
}
}
}
}
/**
* Binds the form with the given data.
*
* @param array $taintedData The data to bind to the form
* @return boolean Whether the form is valid
*/
protected function doBind(array $taintedData)
{
parent::bind($taintedData);
}
/**
* Gets the stylesheet paths associated with the form.
*
* @return array An array of stylesheet paths
*/
public function getStylesheets()
{
return $this->getWidget()->getStylesheets();
}
/**
* Gets the JavaScript paths associated with the form.
*
* @return array An array of JavaScript paths
*/
public function getJavaScripts()
{
return $this->getWidget()->getJavaScripts();
}
/**
* Returns a CSRF token for the set CSRF secret
*
* If you want to change the algorithm used to compute the token, you
* can override this method.
*
* @param string $secret The secret string to use (null to use the current secret)
*
* @return string A token string
*/
protected function getCsrfToken()
{
return md5($this->csrfSecret.session_id().get_class($this));
}
/**
* @return true if this form is CSRF protected
*/
public function isCsrfProtected()
{
return $this->has($this->getCsrfFieldName());
}
/**
* Enables CSRF protection for this form.
*/
public function enableCsrfProtection()
{
if (!$this->isCsrfProtected()) {
$field = new HiddenField($this->getCsrfFieldName(), array(
'property_path' => null,
));
$field->setData($this->getCsrfToken());
$this->add($field);
}
}
/**
* Disables CSRF protection for this form.
*/
public function disableCsrfProtection()
{
if ($this->isCsrfProtected()) {
$this->remove($this->getCsrfFieldName());
}
}
/**
* Sets the CSRF field name used in this form
*
* @param string $name The CSRF field name
*/
public function setCsrfFieldName($name)
{
$this->csrfFieldName = $name;
}
/**
* Returns the CSRF field name used in this form
*
* @return string The CSRF field name
*/
public function getCsrfFieldName()
{
return $this->csrfFieldName;
}
/**
* Sets the CSRF secret used in this form
*
* @param string $secret
*/
public function setCsrfSecret($secret)
{
$this->csrfSecret = $secret;
}
/**
* Returns the CSRF secret used in this form
*
* @return string
*/
public function getCsrfSecret()
{
return $this->csrfSecret;
}
/**
* Returns whether the CSRF token is valid
*
* @return boolean
*/
public function isCsrfTokenValid()
{
if (!$this->isCsrfProtected()) {
return true;
} else {
return $this->get($this->getCsrfFieldName())->getDisplayedData() === $this->getCsrfToken();
}
}
/**
* Enables CSRF protection for all new forms
*/
static public function enableDefaultCsrfProtection()
{
self::$defaultCsrfProtection = true;
}
/**
* Disables Csrf protection for all forms.
*/
static public function disableDefaultCsrfProtection()
{
self::$defaultCsrfProtection = false;
}
/**
* Sets the CSRF field name used in all new CSRF protected forms
*
* @param string $name The CSRF field name
*/
static public function setDefaultCsrfFieldName($name)
{
self::$defaultCsrfFieldName = $name;
}
/**
* Returns the default CSRF field name
*
* @return string The CSRF field name
*/
static public function getDefaultCsrfFieldName()
{
return self::$defaultCsrfFieldName;
}
/**
* Sets the CSRF secret used in all new CSRF protected forms
*
* @param string $secret
*/
static public function setDefaultCsrfSecret($secret)
{
self::$defaultCsrfSecret = $secret;
}
/**
* Returns the default CSRF secret
*
* @return string
*/
static public function getDefaultCsrfSecret()
{
return self::$defaultCsrfSecret;
}
/**
* 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 renderFormTag($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->isMultipart() ? 'multipart/form-data' : null,
), $attributes)));
}
/**
* Returns whether the maximum POST size was reached in this request.
*
* @return boolean
*/
public function isPostMaxSizeReached()
{
if (isset($_SERVER['CONTENT_LENGTH'])) {
$length = (int) $_SERVER['CONTENT_LENGTH'];
$max = trim(ini_get('post_max_size'));
switch (strtolower(substr($max, -1))) {
// The 'G' modifier is available since PHP 5.1.0
case 'g':
$max *= 1024;
case 'm':
$max *= 1024;
case 'k':
$max *= 1024;
}
return $length > $max;
} else {
return false;
}
}
/**
* Merges two arrays without reindexing numeric keys.
*
* @param array $array1 An array to merge
* @param array $array2 An array to merge
*
* @return array The merged array
*/
static protected function deepArrayUnion($array1, $array2)
{
foreach ($array2 as $key => $value) {
if (is_array($value) && isset($array1[$key]) && is_array($array1[$key])) {
$array1[$key] = self::deepArrayUnion($array1[$key], $value);
} else {
$array1[$key] = $value;
}
}
return $array1;
}
/**
* Fixes a malformed PHP $_FILES array.
*
* PHP has a bug that the format of the $_FILES array differs, depending on
* whether the uploaded file fields had normal field names or array-like
* field names ("normal" vs. "parent[child]").
*
* This method fixes the array to look like the "normal" $_FILES array.
*
* @param array $data
* @return array
*/
static protected function fixPhpFilesArray(array $data)
{
$fileKeys = array('error', 'name', 'size', 'tmp_name', 'type');
$keys = array_keys($data); $keys = array_keys($data);
sort($keys); sort($keys);
if ($keys == $fileKeys) $files = $data;
{
$files[$key] = new UploadedFile($data['tmp_name'], $data['name'], $data['type'], $data['size'], $data['error']); if ($fileKeys == $keys && isset($data['name']) && is_array($data['name'])) {
foreach ($fileKeys as $k) {
unset($files[$k]);
}
foreach (array_keys($data['name']) as $key) {
$files[$key] = self::fixPhpFilesArray(array(
'error' => $data['error'][$key],
'name' => $data['name'][$key],
'type' => $data['type'][$key],
'tmp_name' => $data['tmp_name'][$key],
'size' => $data['size'][$key],
));
}
} }
else
{ return $files;
$files[$key] = self::convertFileInformation($data);
}
}
} }
return $files; /**
} * Converts uploaded files to instances of clsas UploadedFile.
*
* @param array $files A (multi-dimensional) array of uploaded file information
* @return array A (multi-dimensional) array of UploadedFile instances
*/
static protected function convertFileInformation(array $files)
{
$fileKeys = array('error', 'name', 'size', 'tmp_name', 'type');
foreach ($files as $key => $data) {
if (is_array($data)) {
$keys = array_keys($data);
sort($keys);
if ($keys == $fileKeys) {
$files[$key] = new UploadedFile($data['tmp_name'], $data['name'], $data['type'], $data['size'], $data['error']);
} else {
$files[$key] = self::convertFileInformation($data);
}
}
}
return $files;
}
} }

View File

@ -19,21 +19,21 @@ use Symfony\Components\Form\Renderer\InputHiddenRenderer;
*/ */
class HiddenField extends InputField class HiddenField extends InputField
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function render(array $attributes = array()) public function render(array $attributes = array())
{ {
return parent::render(array_merge(array( return parent::render(array_merge(array(
'type' => 'hidden', 'type' => 'hidden',
), $attributes)); ), $attributes));
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function isHidden() public function isHidden()
{ {
return true; return true;
} }
} }

View File

@ -10,136 +10,128 @@ namespace Symfony\Components\Form;
*/ */
class HtmlGenerator implements HtmlGeneratorInterface class HtmlGenerator implements HtmlGeneratorInterface
{ {
/** /**
* Whether to produce XHTML compliant code * Whether to produce XHTML compliant code
* @var boolean * @var boolean
*/ */
protected static $xhtml = true; protected static $xhtml = true;
/** /**
* The charset used during generating * The charset used during generating
* @var string * @var string
*/ */
protected $charset; protected $charset;
/** /**
* Sets the charset used for rendering * Sets the charset used for rendering
* *
* @param string $charset * @param string $charset
*/ */
public function __construct($charset = 'UTF-8') 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 ''; $this->charset = $charset;
} }
return sprintf('<%s%s%s', $tag, $this->attributes($attributes), self::$xhtml ? ' />' : (strtolower($tag) == 'input' ? '>' : sprintf('></%s>', $tag))); /**
} * Sets the XHTML generation flag.
*
/** * @param bool $boolean true if renderers must be generated as XHTML, false otherwise
* {@inheritDoc} */
*/ static public function setXhtml($boolean)
public function contentTag($tag, $content = null, $attributes = array())
{
if (empty($tag))
{ {
return ''; self::$xhtml = (boolean) $boolean;
} }
return sprintf('<%s%s>%s</%s>', $tag, $this->attributes($attributes), $content, $tag); /**
} * Returns whether to generate XHTML tags or not.
*
/** * @return bool true if renderers must be generated as XHTML, false otherwise
* {@inheritDoc} */
*/ static public function isXhtml()
public function attribute($name, $value)
{
if (true === $value)
{ {
return self::$xhtml ? sprintf('%s="%s"', $name, $this->escape($name)) : $this->escape($name); return self::$xhtml;
} }
else
/**
* {@inheritDoc}
*/
public function tag($tag, $attributes = array())
{ {
return sprintf('%s="%s"', $name, $this->escape($value)); if (empty($tag)) {
return '';
}
return sprintf('<%s%s%s', $tag, $this->attributes($attributes), self::$xhtml ? ' />' : (strtolower($tag) == 'input' ? '>' : sprintf('></%s>', $tag)));
} }
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function attributes(array $attributes) public function contentTag($tag, $content = null, $attributes = array())
{
return implode('', array_map(array($this, 'attributesCallback'), array_keys($attributes), array_values($attributes)));
}
/**
* 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 ''; if (empty($tag)) {
return '';
}
return sprintf('<%s%s>%s</%s>', $tag, $this->attributes($attributes), $content, $tag);
} }
else
/**
* {@inheritDoc}
*/
public function attribute($name, $value)
{ {
return ' '.$this->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} * {@inheritDoc}
*/ */
public function escape($value) public function attributes(array $attributes)
{ {
return $this->fixDoubleEscape(htmlspecialchars((string) $value, ENT_QUOTES, $this->charset)); return implode('', array_map(array($this, 'attributesCallback'), array_keys($attributes), array_values($attributes)));
} }
/** /**
* Fixes double escaped strings. * Prepares an attribute key and value for HTML representation.
* *
* @param string $escaped string to fix * It removes empty attributes, except for the value one.
* *
* @return string A single escaped string * @param string $name The attribute name
*/ * @param string $value The attribute value
protected function fixDoubleEscape($escaped) *
{ * @return string The HTML representation of the HTML key attribute pair.
return preg_replace('/&amp;([a-z]+|(#\d+)|(#x[\da-f]+));/i', '&$1;', $escaped); */
} 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 $this->fixDoubleEscape(htmlspecialchars((string) $value, ENT_QUOTES, $this->charset));
}
/**
* Fixes double escaped strings.
*
* @param string $escaped string to fix
*
* @return string A single escaped string
*/
protected function fixDoubleEscape($escaped)
{
return preg_replace('/&amp;([a-z]+|(#\d+)|(#x[\da-f]+));/i', '&$1;', $escaped);
}
} }

View File

@ -9,55 +9,55 @@ namespace Symfony\Components\Form;
*/ */
interface HtmlGeneratorInterface interface HtmlGeneratorInterface
{ {
/** /**
* Escapes a value for safe output in HTML * Escapes a value for safe output in HTML
* *
* Double escaping of already-escaped sequences is avoided by this method. * Double escaping of already-escaped sequences is avoided by this method.
* *
* @param string $value The unescaped or partially escaped value * @param string $value The unescaped or partially escaped value
* *
* @return string The fully escaped value * @return string The fully escaped value
*/ */
public function escape($value); public function escape($value);
/** /**
* Generates the HTML code for a tag attribute * Generates the HTML code for a tag attribute
* *
* @param string $name The attribute name * @param string $name The attribute name
* @param string $value The attribute value * @param string $value The attribute value
* *
* @return string The HTML code of the attribute * @return string The HTML code of the attribute
*/ */
public function attribute($name, $value); public function attribute($name, $value);
/** /**
* Generates the HTML code for multiple tag attributes * Generates the HTML code for multiple tag attributes
* *
* @param array $attributes An array with attribute names as keys and * @param array $attributes An array with attribute names as keys and
* attribute values as elements * attribute values as elements
* *
* @return string The HTML code of the attribute list * @return string The HTML code of the attribute list
*/ */
public function attributes(array $attributes); public function attributes(array $attributes);
/** /**
* Generates the HTML code for a tag without content * Generates the HTML code for a tag without content
* *
* @param string $tag The name of the tag * @param string $tag The name of the tag
* @param array $attributes The attributes for the tag * @param array $attributes The attributes for the tag
* *
* @return string The HTML code for the tag * @return string The HTML code for the tag
*/ */
public function tag($tag, $attributes = array()); public function tag($tag, $attributes = array());
/** /**
* Generates the HTML code for a tag with content * Generates the HTML code for a tag with content
* *
* @param string $tag The name of the tag * @param string $tag The name of the tag
* @param string $content The content of the tag * @param string $content The content of the tag
* @param array $attributes The attributes for the tag * @param array $attributes The attributes for the tag
* *
* @return string The HTML code for the tag * @return string The HTML code for the tag
*/ */
public function contentTag($tag, $content, $attributes = array()); public function contentTag($tag, $content, $attributes = array());
} }

View File

@ -14,88 +14,77 @@ namespace Symfony\Components\Form;
*/ */
class HybridField extends FieldGroup class HybridField extends FieldGroup
{ {
const FIELD = 0; const FIELD = 0;
const GROUP = 1; const GROUP = 1;
protected $mode = self::FIELD; protected $mode = self::FIELD;
/** /**
* Sets the current mode of the field * Sets the current mode of the field
* *
* Note that you can't switch modes anymore once you have added children to * Note that you can't switch modes anymore once you have added children to
* this field. * this field.
* *
* @param integer $mode One of the constants HybridField::FIELD and * @param integer $mode One of the constants HybridField::FIELD and
* HybridField::GROUP. * HybridField::GROUP.
*/ */
public function setFieldMode($mode) public function setFieldMode($mode)
{
if (count($this) > 0 && $mode === self::FIELD)
{ {
throw new FormException('Switching to mode FIELD is not allowed after adding nested fields'); if (count($this) > 0 && $mode === self::FIELD) {
throw new FormException('Switching to mode FIELD is not allowed after adding nested fields');
}
$this->mode = $mode;
} }
$this->mode = $mode; /**
} * {@inheritDoc}
*
/** * @throws FormException When the field is in mode HybridField::FIELD adding
* {@inheritDoc} * subfields is not allowed
* */
* @throws FormException When the field is in mode HybridField::FIELD adding public function add(FieldInterface $field)
* subfields is not allowed
*/
public function add(FieldInterface $field)
{
if ($this->mode === self::FIELD)
{ {
throw new FormException('You cannot add nested fields while in mode FIELD'); if ($this->mode === self::FIELD) {
throw new FormException('You cannot add nested fields while in mode FIELD');
}
return parent::add($field);
} }
return parent::add($field); /**
} * {@inheritDoc}
*/
public function getDisplayedData()
{
if ($this->mode === self::GROUP) {
return parent::getDisplayedData();
} else {
return Field::getDisplayedData();
}
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function getDisplayedData() public function setData($data)
{
if ($this->mode === self::GROUP)
{ {
return parent::getDisplayedData(); if ($this->mode === self::GROUP) {
parent::setData($data);
} else {
Field::setData($data);
}
} }
else
{
return Field::getDisplayedData();
}
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function setData($data) public function bind($data)
{
if ($this->mode === self::GROUP)
{ {
parent::setData($data); if ($this->mode === self::GROUP) {
parent::bind($data);
} else {
Field::bind($data);
}
} }
else
{
Field::setData($data);
}
}
/**
* {@inheritDoc}
*/
public function bind($data)
{
if ($this->mode === self::GROUP)
{
parent::bind($data);
}
else
{
Field::bind($data);
}
}
} }

View File

@ -9,16 +9,16 @@ namespace Symfony\Components\Form;
*/ */
abstract class InputField extends Field abstract class InputField extends Field
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function render(array $attributes = array()) public function render(array $attributes = array())
{ {
return $this->generator->tag('input', array_merge(array( return $this->generator->tag('input', array_merge(array(
'id' => $this->getId(), 'id' => $this->getId(),
'name' => $this->getName(), 'name' => $this->getName(),
'value' => $this->getDisplayedData(), 'value' => $this->getDisplayedData(),
'disabled' => $this->isDisabled(), 'disabled' => $this->isDisabled(),
), $attributes)); ), $attributes));
} }
} }

View File

@ -17,21 +17,21 @@ namespace Symfony\Components\Form;
*/ */
class IntegerField extends NumberField class IntegerField extends NumberField
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{ {
$this->addOption('precision', 0); $this->addOption('precision', 0);
parent::configure(); parent::configure();
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function getData() public function getData()
{ {
return (int)parent::getData(); return (int)parent::getData();
} }
} }

View File

@ -6,18 +6,18 @@ use Symfony\Components\Form\FieldGroupInterface;
class RecursiveFieldsWithPropertyPathIterator extends \IteratorIterator implements \RecursiveIterator class RecursiveFieldsWithPropertyPathIterator extends \IteratorIterator implements \RecursiveIterator
{ {
public function __construct(FieldGroupInterface $group) public function __construct(FieldGroupInterface $group)
{ {
parent::__construct($group); parent::__construct($group);
} }
public function getChildren() public function getChildren()
{ {
return new self($this->current()); return new self($this->current());
} }
public function hasChildren() public function hasChildren()
{ {
return $this->current() instanceof FieldGroupInterface && $this->current()->getPropertyPath() === null; return $this->current() instanceof FieldGroupInterface && $this->current()->getPropertyPath() === null;
} }
} }

View File

@ -9,10 +9,10 @@ namespace Symfony\Components\Form;
*/ */
interface Localizable interface Localizable
{ {
/** /**
* Sets the locale of the class. * Sets the locale of the class.
* *
* @param string $locale * @param string $locale
*/ */
public function setLocale($locale); public function setLocale($locale);
} }

View File

@ -19,93 +19,83 @@ use Symfony\Components\Form\ValueTransformer\MoneyToLocalizedStringTransformer;
*/ */
class MoneyField extends NumberField class MoneyField extends NumberField
{ {
/** /**
* Stores patterns for different locales and cultures * Stores patterns for different locales and cultures
* *
* A pattern decides which currency symbol is displayed and where it is in * A pattern decides which currency symbol is displayed and where it is in
* relation to the number. * relation to the number.
* *
* @var array * @var array
*/ */
protected static $patterns = array(); protected static $patterns = array();
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{
$this->addOption('precision', 2);
$this->addOption('divisor', 1);
$this->addOption('currency');
parent::configure();
$this->setValueTransformer(new MoneyToLocalizedStringTransformer(array(
'precision' => $this->getOption('precision'),
'grouping' => $this->getOption('grouping'),
'divisor' => $this->getOption('divisor'),
)));
}
/**
* {@inheritDoc}
*/
public function render(array $attributes = array())
{
$input = parent::render($attributes);
if ($this->getOption('currency'))
{ {
return str_replace('%widget%', $input, $this->getPattern($this->locale, $this->getOption('currency'))); $this->addOption('precision', 2);
} $this->addOption('divisor', 1);
else $this->addOption('currency');
{
return $input;
}
}
/** parent::configure();
* Returns the pattern for this locale
* $this->setValueTransformer(new MoneyToLocalizedStringTransformer(array(
* The pattern contains the placeholder "%widget%" where the HTML tag should 'precision' => $this->getOption('precision'),
* be inserted 'grouping' => $this->getOption('grouping'),
* 'divisor' => $this->getOption('divisor'),
* @param string $locale )));
*/
protected static function getPattern($locale, $currency)
{
if (!isset(self::$patterns[$locale]))
{
self::$patterns[$locale] = array();
} }
if (!isset(self::$patterns[$locale][$currency])) /**
* {@inheritDoc}
*/
public function render(array $attributes = array())
{ {
$format = new \NumberFormatter($locale, \NumberFormatter::CURRENCY); $input = parent::render($attributes);
$pattern = $format->formatCurrency('123', $currency);
// the spacings between currency symbol and number are ignored, because if ($this->getOption('currency')) {
// a single space leads to better readability in combination with input return str_replace('%widget%', $input, $this->getPattern($this->locale, $this->getOption('currency')));
// fields } else {
return $input;
// the regex also considers non-break spaces (0xC2 or 0xA0 in UTF-8) }
preg_match('/^([^\s\xc2\xa0]*)[\s\xc2\xa0]*123[,.]00[\s\xc2\xa0]*([^\s\xc2\xa0]*)$/', $pattern, $matches);
if (!empty($matches[1]))
{
self::$patterns[$locale] = $matches[1].' %widget%';
}
else if (!empty($matches[2]))
{
self::$patterns[$locale] = '%widget% '.$matches[2];
}
else
{
self::$patterns[$locale] = '%widget%';
}
} }
return self::$patterns[$locale]; /**
} * Returns the pattern for this locale
*
* The pattern contains the placeholder "%widget%" where the HTML tag should
* be inserted
*
* @param string $locale
*/
protected static function getPattern($locale, $currency)
{
if (!isset(self::$patterns[$locale])) {
self::$patterns[$locale] = array();
}
if (!isset(self::$patterns[$locale][$currency])) {
$format = new \NumberFormatter($locale, \NumberFormatter::CURRENCY);
$pattern = $format->formatCurrency('123', $currency);
// the spacings between currency symbol and number are ignored, because
// a single space leads to better readability in combination with input
// fields
// the regex also considers non-break spaces (0xC2 or 0xA0 in UTF-8)
preg_match('/^([^\s\xc2\xa0]*)[\s\xc2\xa0]*123[,.]00[\s\xc2\xa0]*([^\s\xc2\xa0]*)$/', $pattern, $matches);
if (!empty($matches[1])) {
self::$patterns[$locale] = $matches[1].' %widget%';
} else if (!empty($matches[2])) {
self::$patterns[$locale] = '%widget% '.$matches[2];
} else {
self::$patterns[$locale] = '%widget%';
}
}
return self::$patterns[$locale];
}
} }

View File

@ -20,28 +20,28 @@ use Symfony\Components\Form\ValueTransformer\NumberToLocalizedStringTransformer;
*/ */
class NumberField extends InputField class NumberField extends InputField
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{ {
// default precision is locale specific (usually around 3) // default precision is locale specific (usually around 3)
$this->addOption('precision'); $this->addOption('precision');
$this->addOption('grouping', false); $this->addOption('grouping', false);
$this->setValueTransformer(new NumberToLocalizedStringTransformer(array( $this->setValueTransformer(new NumberToLocalizedStringTransformer(array(
'precision' => $this->getOption('precision'), 'precision' => $this->getOption('precision'),
'grouping' => $this->getOption('grouping'), 'grouping' => $this->getOption('grouping'),
))); )));
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function render(array $attributes = array()) public function render(array $attributes = array())
{ {
return parent::render(array_merge(array( return parent::render(array_merge(array(
'type' => 'text', 'type' => 'text',
), $attributes)); ), $attributes));
} }
} }

View File

@ -19,24 +19,24 @@ use Symfony\Components\Form\Renderer\InputPasswordRenderer;
*/ */
class PasswordField extends TextField class PasswordField extends TextField
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{ {
parent::configure(); parent::configure();
$this->addOption('always_empty', true); $this->addOption('always_empty', true);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function render(array $attributes = array()) public function render(array $attributes = array())
{ {
return parent::render(array_merge(array( return parent::render(array_merge(array(
'value' => $this->getOption('always_empty') && !$this->isBound() ? '' : $this->getDisplayedData(), 'value' => $this->getOption('always_empty') && !$this->isBound() ? '' : $this->getDisplayedData(),
'type' => 'password', 'type' => 'password',
), $attributes)); ), $attributes));
} }
} }

View File

@ -19,28 +19,28 @@ use Symfony\Components\Form\ValueTransformer\PercentToLocalizedStringTransformer
*/ */
class PercentField extends NumberField class PercentField extends NumberField
{ {
const FRACTIONAL = 'fractional'; const FRACTIONAL = 'fractional';
const INTEGER = 'integer'; const INTEGER = 'integer';
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{ {
$this->addOption('precision', 0); $this->addOption('precision', 0);
$this->addOption('type', self::FRACTIONAL); $this->addOption('type', self::FRACTIONAL);
$this->setValueTransformer(new PercentToLocalizedStringTransformer(array( $this->setValueTransformer(new PercentToLocalizedStringTransformer(array(
'precision' => $this->getOption('precision'), 'precision' => $this->getOption('precision'),
'type' => $this->getOption('type'), 'type' => $this->getOption('type'),
))); )));
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function render(array $attributes = array()) public function render(array $attributes = array())
{ {
return parent::render($attributes).' %'; return parent::render($attributes).' %';
} }
} }

View File

@ -11,152 +11,145 @@ use Symfony\Components\Form\Exception\InvalidPropertyPathException;
*/ */
class PropertyPath class PropertyPath
{ {
/** /**
* The current index of the traversal * The current index of the traversal
* @var integer * @var integer
*/ */
protected $currentIndex = 0; protected $currentIndex = 0;
/** /**
* The elements of the property path * The elements of the property path
* @var array * @var array
*/ */
protected $elements = array(); protected $elements = array();
/** /**
* Contains a boolean for each property in $elements denoting whether this * Contains a boolean for each property in $elements denoting whether this
* element is a property. It is an index otherwise. * element is a property. It is an index otherwise.
* @var array * @var array
*/ */
protected $isProperty = array(); protected $isProperty = array();
/** /**
* String representation of the path * String representation of the path
* @var string * @var string
*/ */
protected $string; protected $string;
/** /**
* Parses the given property path * Parses the given property path
* *
* @param string $propertyPath * @param string $propertyPath
*/ */
public function __construct($propertyPath) public function __construct($propertyPath)
{
if (empty($propertyPath))
{ {
throw new InvalidPropertyPathException('The property path must not be empty'); if (empty($propertyPath)) {
throw new InvalidPropertyPathException('The property path must not be empty');
}
$this->string = $propertyPath;
$position = 0;
$remaining = $propertyPath;
// first element is evaluated differently - no leading dot for properties
$pattern = '/^((\w+)|\[(\w+)\])(.*)/';
while (preg_match($pattern, $remaining, $matches)) {
if (!empty($matches[2])) {
$this->elements[] = $matches[2];
$this->isProperty[] = true;
} else {
$this->elements[] = $matches[3];
$this->isProperty[] = false;
}
$position += strlen($matches[1]);
$remaining = $matches[4];
$pattern = '/^(\.(\w+)|\[(\w+)\])(.*)/';
}
if (!empty($remaining)) {
throw new InvalidPropertyPathException(sprintf(
'Could not parse property path "%s". Unexpected token "%s" at position %d',
$propertyPath,
$remaining{0},
$position
));
}
} }
$this->string = $propertyPath; /**
$position = 0; * Returns the string representation of the property path
$remaining = $propertyPath; *
* @return string
// first element is evaluated differently - no leading dot for properties */
$pattern = '/^((\w+)|\[(\w+)\])(.*)/'; public function __toString()
while (preg_match($pattern, $remaining, $matches))
{ {
if (!empty($matches[2])) return $this->string;
{
$this->elements[] = $matches[2];
$this->isProperty[] = true;
}
else
{
$this->elements[] = $matches[3];
$this->isProperty[] = false;
}
$position += strlen($matches[1]);
$remaining = $matches[4];
$pattern = '/^(\.(\w+)|\[(\w+)\])(.*)/';
} }
if (!empty($remaining)) /**
* Returns the current element of the path
*
* @return string
*/
public function getCurrent()
{ {
throw new InvalidPropertyPathException(sprintf( return $this->elements[$this->currentIndex];
'Could not parse property path "%s". Unexpected token "%s" at position %d',
$propertyPath,
$remaining{0},
$position
));
}
}
/**
* Returns the string representation of the property path
*
* @return string
*/
public function __toString()
{
return $this->string;
}
/**
* Returns the current element of the path
*
* @return string
*/
public function getCurrent()
{
return $this->elements[$this->currentIndex];
}
/**
* Returns whether the current element is a property
*
* @return boolean
*/
public function isProperty()
{
return $this->isProperty[$this->currentIndex];
}
/**
* Returns whether the currente element is an array index
*
* @return boolean
*/
public function isIndex()
{
return !$this->isProperty();
}
/**
* Returns whether there is a next element in the path
*
* @return boolean
*/
public function hasNext()
{
return isset($this->elements[$this->currentIndex + 1]);
}
/**
* Sets the internal cursor to the next element in the path
*
* Use hasNext() to verify whether there is a next element before calling this
* method, otherwise an exception will be thrown.
*
* @throws OutOfBoundsException If there is no next element
*/
public function next()
{
if (!$this->hasNext())
{
throw new \OutOfBoundsException('There is no next element in the path');
} }
++$this->currentIndex; /**
} * Returns whether the current element is a property
*
* @return boolean
*/
public function isProperty()
{
return $this->isProperty[$this->currentIndex];
}
/** /**
* Sets the internal cursor to the first element in the path * Returns whether the currente element is an array index
*/ *
public function rewind() * @return boolean
{ */
$this->currentIndex = 0; public function isIndex()
} {
return !$this->isProperty();
}
/**
* Returns whether there is a next element in the path
*
* @return boolean
*/
public function hasNext()
{
return isset($this->elements[$this->currentIndex + 1]);
}
/**
* Sets the internal cursor to the next element in the path
*
* Use hasNext() to verify whether there is a next element before calling this
* method, otherwise an exception will be thrown.
*
* @throws OutOfBoundsException If there is no next element
*/
public function next()
{
if (!$this->hasNext()) {
throw new \OutOfBoundsException('There is no next element in the path');
}
++$this->currentIndex;
}
/**
* Sets the internal cursor to the first element in the path
*/
public function rewind()
{
$this->currentIndex = 0;
}
} }

View File

@ -20,14 +20,14 @@ use Symfony\Components\Form\ValueTransformer\BooleanToStringTransformer;
*/ */
class RadioField extends ToggleField class RadioField extends ToggleField
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function render(array $attributes = array()) public function render(array $attributes = array())
{ {
return parent::render(array_merge(array( return parent::render(array_merge(array(
'type' => 'radio', 'type' => 'radio',
'name' => $this->getParent() ? $this->getParent()->getName() : $this->getName(), 'name' => $this->getParent() ? $this->getParent()->getName() : $this->getName(),
), $attributes)); ), $attributes));
} }
} }

View File

@ -13,79 +13,77 @@ use Symfony\Components\Form\Configurable;
*/ */
abstract class Renderer extends Configurable implements RendererInterface abstract class Renderer extends Configurable implements RendererInterface
{ {
/** /**
* The generator used for rendering the HTML * The generator used for rendering the HTML
* @var HtmlGeneratorInterface * @var HtmlGeneratorInterface
*/ */
protected $generator; protected $generator;
/** /**
* Gets the stylesheet paths associated with the renderer. * Gets the stylesheet paths associated with the renderer.
* *
* The array keys are files and values are the media names (separated by a ,): * The array keys are files and values are the media names (separated by a ,):
* *
* array('/path/to/file.css' => 'all', '/another/file.css' => 'screen,print') * array('/path/to/file.css' => 'all', '/another/file.css' => 'screen,print')
* *
* @return array An array of stylesheet paths * @return array An array of stylesheet paths
*/ */
public function getStylesheets() public function getStylesheets()
{
return array();
}
/**
* Gets the JavaScript paths associated with the renderer.
*
* @return array An array of JavaScript paths
*/
public function getJavaScripts()
{
return array();
}
/**
* {@inheritDoc}
*/
public function renderErrors(FieldInterface $field)
{
$html = '';
if ($field->hasErrors())
{ {
$html .= "<ul>\n"; return array();
foreach ($field->getErrors() as $error)
{
$html .= "<li>" . $error . "</li>\n";
}
$html .= "</ul>\n";
} }
return $html; /**
} * Gets the JavaScript paths associated with the renderer.
*
* @return array An array of JavaScript paths
*/
public function getJavaScripts()
{
return array();
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function setTranslator(TranslatorInterface $translator) public function renderErrors(FieldInterface $field)
{ {
// TODO $html = '';
}
/** if ($field->hasErrors()) {
* {@inheritDoc} $html .= "<ul>\n";
*/
public function setLocale($locale)
{
// TODO
}
/** foreach ($field->getErrors() as $error) {
* {@inheritDoc} $html .= "<li>" . $error . "</li>\n";
*/ }
public function setGenerator(HtmlGeneratorInterface $generator)
{ $html .= "</ul>\n";
$this->generator = $generator; }
}
return $html;
}
/**
* {@inheritDoc}
*/
public function setTranslator(TranslatorInterface $translator)
{
// TODO
}
/**
* {@inheritDoc}
*/
public function setLocale($locale)
{
// TODO
}
/**
* {@inheritDoc}
*/
public function setGenerator(HtmlGeneratorInterface $generator)
{
$this->generator = $generator;
}
} }

View File

@ -14,32 +14,32 @@ use Symfony\Components\Form\Translatable;
*/ */
interface RendererInterface extends Localizable, Translatable interface RendererInterface extends Localizable, Translatable
{ {
/** /**
* Sets the generator used for rendering the HTML * Sets the generator used for rendering the HTML
* *
* @param HtmlGeneratorInterface $generator * @param HtmlGeneratorInterface $generator
*/ */
public function setGenerator(HtmlGeneratorInterface $generator); public function setGenerator(HtmlGeneratorInterface $generator);
/** /**
* Returns the textual representation of the given field. * Returns the textual representation of the given field.
* *
* @param FieldInterface $field The form field * @param FieldInterface $field The form field
* @param array $attributes The attributes to include in the * @param array $attributes The attributes to include in the
* rendered output * rendered output
* @return string The rendered output * @return string The rendered output
* @throws InvalidArgumentException If the $field is not instance of the * @throws InvalidArgumentException If the $field is not instance of the
* expected class * expected class
*/ */
public function render(FieldInterface $field, array $attributes = array()); public function render(FieldInterface $field, array $attributes = array());
/** /**
* Returns the textual representation of the errors of the given field. * Returns the textual representation of the errors of the given field.
* *
* @param FieldInterface $field The form field * @param FieldInterface $field The form field
* @return string The rendered output * @return string The rendered output
* @throws InvalidArgumentException If the $field is not instance of the * @throws InvalidArgumentException If the $field is not instance of the
* expected class * expected class
*/ */
public function renderErrors(FieldInterface $field); public function renderErrors(FieldInterface $field);
} }

View File

@ -20,36 +20,34 @@ use Symfony\Components\Form\Field\ChoiceField;
*/ */
class TableRenderer extends Renderer class TableRenderer extends Renderer
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function render(FieldInterface $group, array $attributes = array()) public function render(FieldInterface $group, array $attributes = array())
{
$html = "<table>\n";
foreach ($group as $field)
{ {
$label = self::humanize($field->getKey()); $html = "<table>\n";
$html .= "<tr>\n"; foreach ($group as $field) {
$html .= "<td><label for=\"{$field->getId()}\">$label</label></td>\n"; $label = self::humanize($field->getKey());
$html .= "<td>\n";
if ($field->hasErrors()) $html .= "<tr>\n";
{ $html .= "<td><label for=\"{$field->getId()}\">$label</label></td>\n";
$html .= $field->renderErrors()."\n"; $html .= "<td>\n";
} if ($field->hasErrors()) {
$html .= $field->render()."\n"; $html .= $field->renderErrors()."\n";
$html .= "</td>"; }
$html .= "</tr>\n"; $html .= $field->render()."\n";
$html .= "</td>";
$html .= "</tr>\n";
}
$html .= "</table>\n";
return $html;
} }
$html .= "</table>\n"; protected static function humanize($text)
{
return $html; return ucfirst(strtolower(str_replace('_', ' ', $text)));
} }
protected static function humanize($text)
{
return ucfirst(strtolower(str_replace('_', ' ', $text)));
}
} }

View File

@ -17,72 +17,71 @@ namespace Symfony\Components\Form;
*/ */
class RepeatedField extends FieldGroup class RepeatedField extends FieldGroup
{ {
/** /**
* The prototype for the inner fields * The prototype for the inner fields
* @var FieldInterface * @var FieldInterface
*/ */
protected $prototype; protected $prototype;
/** /**
* Repeats the given field twice to verify the user's input * Repeats the given field twice to verify the user's input
* *
* @param FieldInterface $innerField * @param FieldInterface $innerField
*/ */
public function __construct(FieldInterface $innerField, array $options = array()) public function __construct(FieldInterface $innerField, array $options = array())
{
$this->prototype = $innerField;
parent::__construct($innerField->getKey(), $options);
}
/**
* {@inheritDoc}
*/
protected function configure()
{
$field = clone $this->prototype;
$field->setKey('first');
$field->setPropertyPath('first');
$this->add($field);
$field = clone $this->prototype;
$field->setKey('second');
$field->setPropertyPath('second');
$this->add($field);
}
/**
* Returns whether both entered values are equal
*
* @return bool
*/
public function isFirstEqualToSecond()
{
return $this->get('first')->getData() === $this->get('second')->getData();
}
/**
* Sets the values of both fields to this value
*
* @param mixed $data
*/
public function setData($data)
{
parent::setData(array('first' => $data, 'second' => $data));
}
/**
* Return only value of first password field.
*
* @return string The password.
*/
public function getData()
{
if ($this->isBound() && $this->isFirstEqualToSecond())
{ {
return $this->get('first')->getData(); $this->prototype = $innerField;
parent::__construct($innerField->getKey(), $options);
} }
return null; /**
} * {@inheritDoc}
*/
protected function configure()
{
$field = clone $this->prototype;
$field->setKey('first');
$field->setPropertyPath('first');
$this->add($field);
$field = clone $this->prototype;
$field->setKey('second');
$field->setPropertyPath('second');
$this->add($field);
}
/**
* Returns whether both entered values are equal
*
* @return bool
*/
public function isFirstEqualToSecond()
{
return $this->get('first')->getData() === $this->get('second')->getData();
}
/**
* Sets the values of both fields to this value
*
* @param mixed $data
*/
public function setData($data)
{
parent::setData(array('first' => $data, 'second' => $data));
}
/**
* Return only value of first password field.
*
* @return string The password.
*/
public function getData()
{
if ($this->isBound() && $this->isFirstEqualToSecond()) {
return $this->get('first')->getData();
}
return null;
}
} }

View File

@ -19,24 +19,24 @@ use Symfony\Components\Form\Renderer\InputTextRenderer;
*/ */
class TextField extends InputField class TextField extends InputField
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{ {
parent::configure(); parent::configure();
$this->addOption('max_length'); $this->addOption('max_length');
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function render(array $attributes = array()) public function render(array $attributes = array())
{ {
return parent::render(array_merge(array( return parent::render(array_merge(array(
'type' => 'text', 'type' => 'text',
'maxlength' => $this->getOption('max_length'), 'maxlength' => $this->getOption('max_length'),
), $attributes)); ), $attributes));
} }
} }

View File

@ -19,18 +19,18 @@ use Symfony\Components\Form\Renderer\TextareaRenderer;
*/ */
class TextareaField extends Field class TextareaField extends Field
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function render(array $attributes = array()) public function render(array $attributes = array())
{ {
$content = $this->generator->escape($this->getDisplayedData()); $content = $this->generator->escape($this->getDisplayedData());
return $this->generator->contentTag('textarea', $content, array_merge(array( return $this->generator->contentTag('textarea', $content, array_merge(array(
'id' => $this->getId(), 'id' => $this->getId(),
'name' => $this->getName(), 'name' => $this->getName(),
'rows' => 30, 'rows' => 30,
'cols' => 4, 'cols' => 4,
), $attributes)); ), $attributes));
} }
} }

View File

@ -11,147 +11,134 @@ use Symfony\Components\Form\ValueTransformer\ValueTransformerChain;
class TimeField extends FieldGroup class TimeField extends FieldGroup
{ {
const INPUT = 'input'; const INPUT = 'input';
const CHOICE = 'choice'; const CHOICE = 'choice';
const DATETIME = 'datetime'; const DATETIME = 'datetime';
const STRING = 'string'; const STRING = 'string';
const TIMESTAMP = 'timestamp'; const TIMESTAMP = 'timestamp';
const RAW = 'raw'; const RAW = 'raw';
protected static $widgets = array( protected static $widgets = array(
self::INPUT, self::INPUT,
self::CHOICE, self::CHOICE,
); );
protected static $types = array( protected static $types = array(
self::DATETIME, self::DATETIME,
self::STRING, self::STRING,
self::TIMESTAMP, self::TIMESTAMP,
self::RAW, self::RAW,
); );
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{
$this->addOption('hours', range(0, 23));
$this->addOption('minutes', range(0, 59));
$this->addOption('seconds', range(0, 59));
$this->addOption('widget', self::CHOICE, self::$widgets);
$this->addOption('type', self::DATETIME, self::$types);
$this->addOption('data_timezone', 'UTC');
$this->addOption('user_timezone', 'UTC');
$this->addOption('with_seconds', false);
if ($this->getOption('widget') == self::INPUT)
{ {
$this->add(new TextField('hour', array('max_length' => 2))); $this->addOption('hours', range(0, 23));
$this->add(new TextField('minute', array('max_length' => 2))); $this->addOption('minutes', range(0, 59));
$this->addOption('seconds', range(0, 59));
$this->addOption('widget', self::CHOICE, self::$widgets);
$this->addOption('type', self::DATETIME, self::$types);
$this->addOption('data_timezone', 'UTC');
$this->addOption('user_timezone', 'UTC');
$this->addOption('with_seconds', false);
if ($this->getOption('with_seconds')) if ($this->getOption('widget') == self::INPUT) {
{ $this->add(new TextField('hour', array('max_length' => 2)));
$this->add(new TextField('second', array('max_length' => 2))); $this->add(new TextField('minute', array('max_length' => 2)));
}
}
else
{
$this->add(new ChoiceField('hour', array(
'choices' => $this->generatePaddedChoices($this->getOption('hours'), 2),
)));
$this->add(new ChoiceField('minute', array(
'choices' => $this->generatePaddedChoices($this->getOption('minutes'), 2),
)));
if ($this->getOption('with_seconds')) if ($this->getOption('with_seconds')) {
{ $this->add(new TextField('second', array('max_length' => 2)));
$this->add(new ChoiceField('second', array( }
'choices' => $this->generatePaddedChoices($this->getOption('seconds'), 2), } else {
))); $this->add(new ChoiceField('hour', array(
} 'choices' => $this->generatePaddedChoices($this->getOption('hours'), 2),
)));
$this->add(new ChoiceField('minute', array(
'choices' => $this->generatePaddedChoices($this->getOption('minutes'), 2),
)));
if ($this->getOption('with_seconds')) {
$this->add(new ChoiceField('second', array(
'choices' => $this->generatePaddedChoices($this->getOption('seconds'), 2),
)));
}
}
$transformers = array();
if ($this->getOption('type') == self::STRING) {
$transformers[] = new StringToDateTimeTransformer(array(
'format' => 'H:i:s',
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('data_timezone'),
));
} else if ($this->getOption('type') == self::TIMESTAMP) {
$transformers[] = new TimestampToDateTimeTransformer(array(
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('data_timezone'),
));
} else if ($this->getOption('type') === self::RAW) {
$transformers[] = new ReversedTransformer(new DateTimeToArrayTransformer(array(
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('data_timezone'),
'fields' => array('hour', 'minute', 'second'),
)));
}
$transformers[] = new DateTimeToArrayTransformer(array(
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('user_timezone'),
// if the field is rendered as choice field, the values should be trimmed
// of trailing zeros to render the selected choices correctly
'pad' => $this->getOption('widget') == self::INPUT,
));
$this->setValueTransformer(new ValueTransformerChain($transformers));
} }
$transformers = array(); /**
* {@inheritDoc}
*/
public function render(array $attributes = array())
{
if ($this->getOption('widget') == self::INPUT) {
$attributes = array_merge(array(
'size' => '1',
), $attributes);
}
if ($this->getOption('type') == self::STRING) $html = $this->get('hour')->render($attributes);
{ $html .= ':' . $this->get('minute')->render($attributes);
$transformers[] = new StringToDateTimeTransformer(array(
'format' => 'H:i:s', if ($this->getOption('with_seconds')) {
'input_timezone' => $this->getOption('data_timezone'), $html .= ':' . $this->get('second')->render($attributes);
'output_timezone' => $this->getOption('data_timezone'), }
));
} return $html;
else if ($this->getOption('type') == self::TIMESTAMP)
{
$transformers[] = new TimestampToDateTimeTransformer(array(
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('data_timezone'),
));
}
else if ($this->getOption('type') === self::RAW)
{
$transformers[] = new ReversedTransformer(new DateTimeToArrayTransformer(array(
'input_timezone' => $this->getOption('data_timezone'),
'output_timezone' => $this->getOption('data_timezone'),
'fields' => array('hour', 'minute', 'second'),
)));
} }
$transformers[] = new DateTimeToArrayTransformer(array( /**
'input_timezone' => $this->getOption('data_timezone'), * Generates an array of choices for the given values
'output_timezone' => $this->getOption('user_timezone'), *
// if the field is rendered as choice field, the values should be trimmed * If the values are shorter than $padLength characters, they are padded with
// of trailing zeros to render the selected choices correctly * zeros on the left side.
'pad' => $this->getOption('widget') == self::INPUT, *
)); * @param array $values The available choices
* @param integer $padLength The length to pad the choices
$this->setValueTransformer(new ValueTransformerChain($transformers)); * @return array An array with the input values as keys and the
} * padded values as values
*/
/** protected function generatePaddedChoices(array $values, $padLength)
* {@inheritDoc}
*/
public function render(array $attributes = array())
{
if ($this->getOption('widget') == self::INPUT)
{ {
$attributes = array_merge(array( $choices = array();
'size' => '1',
), $attributes); foreach ($values as $value) {
$choices[$value] = str_pad($value, $padLength, '0', STR_PAD_LEFT);
}
return $choices;
} }
$html = $this->get('hour')->render($attributes);
$html .= ':' . $this->get('minute')->render($attributes);
if ($this->getOption('with_seconds'))
{
$html .= ':' . $this->get('second')->render($attributes);
}
return $html;
}
/**
* Generates an array of choices for the given values
*
* If the values are shorter than $padLength characters, they are padded with
* zeros on the left side.
*
* @param array $values The available choices
* @param integer $padLength The length to pad the choices
* @return array An array with the input values as keys and the
* padded values as values
*/
protected function generatePaddedChoices(array $values, $padLength)
{
$choices = array();
foreach ($values as $value)
{
$choices[$value] = str_pad($value, $padLength, '0', STR_PAD_LEFT);
}
return $choices;
}
} }

View File

@ -4,82 +4,73 @@ namespace Symfony\Components\Form;
class TimezoneField extends ChoiceField class TimezoneField extends ChoiceField
{ {
/** /**
* Stores the available timezone choices * Stores the available timezone choices
* @var array * @var array
*/ */
protected static $timezones = array(); protected static $timezones = array();
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function configure() public function configure()
{
$this->addOption('choices', self::getTimezoneChoices());
parent::configure();
}
/**
* Preselects the server timezone if the field is empty and required
*
* {@inheritDoc}
*/
public function getDisplayedData()
{
$data = parent::getDisplayedData();
if ($data == null && $this->isRequired())
{ {
$data = date_default_timezone_get(); $this->addOption('choices', self::getTimezoneChoices());
parent::configure();
} }
return $data; /**
} * Preselects the server timezone if the field is empty and required
*
/** * {@inheritDoc}
* Returns the timezone choices */
* public function getDisplayedData()
* The choices are generated from the ICU function
* \DateTimeZone::listIdentifiers(). They are cached during a single request,
* so multiple timezone fields on the same page don't lead to unnecessary
* overhead.
*
* @return array The timezone choices
*/
protected static function getTimezoneChoices()
{
if (count(self::$timezones) == 0)
{ {
foreach (\DateTimeZone::listIdentifiers() as $timezone) $data = parent::getDisplayedData();
{
$parts = explode('/', $timezone);
if (count($parts) > 2) if ($data == null && $this->isRequired()) {
{ $data = date_default_timezone_get();
$region = $parts[0];
$name = $parts[1].' - '.$parts[2];
}
else if (count($parts) > 1)
{
$region = $parts[0];
$name = $parts[1];
}
else
{
$region = 'Other';
$name = $parts[0];
} }
if (!isset(self::$timezones[$region])) return $data;
{
self::$timezones[$region] = array();
}
self::$timezones[$region][$timezone] = str_replace('_', ' ', $name);
}
} }
return self::$timezones; /**
} * Returns the timezone choices
*
* The choices are generated from the ICU function
* \DateTimeZone::listIdentifiers(). They are cached during a single request,
* so multiple timezone fields on the same page don't lead to unnecessary
* overhead.
*
* @return array The timezone choices
*/
protected static function getTimezoneChoices()
{
if (count(self::$timezones) == 0) {
foreach (\DateTimeZone::listIdentifiers() as $timezone) {
$parts = explode('/', $timezone);
if (count($parts) > 2) {
$region = $parts[0];
$name = $parts[1].' - '.$parts[2];
} else if (count($parts) > 1) {
$region = $parts[0];
$name = $parts[1];
} else {
$region = 'Other';
$name = $parts[0];
}
if (!isset(self::$timezones[$region])) {
self::$timezones[$region] = array();
}
self::$timezones[$region][$timezone] = str_replace('_', ' ', $name);
}
}
return self::$timezones;
}
} }

View File

@ -20,40 +20,38 @@ use Symfony\Components\Form\ValueTransformer\BooleanToStringTransformer;
*/ */
abstract class ToggleField extends InputField abstract class ToggleField extends InputField
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{
$this->addOption('value');
$this->addOption('label');
$this->addOption('translate_label', false);
$this->setValueTransformer(new BooleanToStringTransformer());
}
/**
* {@inheritDoc}
*/
public function render(array $attributes = array())
{
$html = parent::render(array_merge(array(
'value' => $this->getOption('value'),
'checked' => ((string)$this->getDisplayedData() !== '' && $this->getDisplayedData() !== 0),
), $attributes));
if ($label = $this->getOption('label'))
{ {
if ($this->getOption('translate_label')) $this->addOption('value');
{ $this->addOption('label');
$label = $this->translate($label); $this->addOption('translate_label', false);
}
$html .= ' '.$this->generator->contentTag('label', $label, array( $this->setValueTransformer(new BooleanToStringTransformer());
'for' => $this->getId(),
));
} }
return $html; /**
} * {@inheritDoc}
*/
public function render(array $attributes = array())
{
$html = parent::render(array_merge(array(
'value' => $this->getOption('value'),
'checked' => ((string)$this->getDisplayedData() !== '' && $this->getDisplayedData() !== 0),
), $attributes));
if ($label = $this->getOption('label')) {
if ($this->getOption('translate_label')) {
$label = $this->translate($label);
}
$html .= ' '.$this->generator->contentTag('label', $label, array(
'for' => $this->getId(),
));
}
return $html;
}
} }

View File

@ -11,10 +11,10 @@ use Symfony\Components\I18N\TranslatorInterface;
*/ */
interface Translatable interface Translatable
{ {
/** /**
* Sets the translator unit of the class. * Sets the translator unit of the class.
* *
* @param TranslatorInterface $translator * @param TranslatorInterface $translator
*/ */
public function setTranslator(TranslatorInterface $translator); public function setTranslator(TranslatorInterface $translator);
} }

View File

@ -4,41 +4,40 @@ namespace Symfony\Components\Form\ValueTransformer;
abstract class BaseDateTimeTransformer extends BaseValueTransformer abstract class BaseDateTimeTransformer extends BaseValueTransformer
{ {
const NONE = 'none'; const NONE = 'none';
const FULL = 'full'; const FULL = 'full';
const LONG = 'long'; const LONG = 'long';
const MEDIUM = 'medium'; const MEDIUM = 'medium';
const SHORT = 'short'; const SHORT = 'short';
protected static $formats = array( protected static $formats = array(
self::NONE, self::NONE,
self::FULL, self::FULL,
self::LONG, self::LONG,
self::MEDIUM, self::MEDIUM,
self::SHORT, self::SHORT,
); );
/** /**
* Returns the appropriate IntLDateFormatter constant for the given format * Returns the appropriate IntLDateFormatter constant for the given format
* *
* @param string $format One of "short", "medium", "long" and "full" * @param string $format One of "short", "medium", "long" and "full"
* @return integer * @return integer
*/ */
protected function getIntlFormatConstant($format) protected function getIntlFormatConstant($format)
{
switch ($format)
{ {
case self::FULL: switch ($format) {
return \IntlDateFormatter::FULL; case self::FULL:
case self::LONG: return \IntlDateFormatter::FULL;
return \IntlDateFormatter::LONG; case self::LONG:
case self::SHORT: return \IntlDateFormatter::LONG;
return \IntldateFormatter::SHORT; case self::SHORT:
case self::MEDIUM: return \IntldateFormatter::SHORT;
return \IntlDateFormatter::MEDIUM; case self::MEDIUM:
case self::NONE: return \IntlDateFormatter::MEDIUM;
default: case self::NONE:
return \IntlDateFormatter::NONE; default:
return \IntlDateFormatter::NONE;
}
} }
}
} }

View File

@ -11,32 +11,32 @@ use Symfony\Components\Form\Configurable;
*/ */
abstract class BaseValueTransformer extends Configurable implements ValueTransformerInterface abstract class BaseValueTransformer extends Configurable implements ValueTransformerInterface
{ {
/** /**
* The locale of this transformer as accepted by the class Locale * The locale of this transformer as accepted by the class Locale
* @var string * @var string
*/ */
protected $locale; protected $locale;
/** /**
* Constructor. * Constructor.
* *
* @param array $options An array of options * @param array $options An array of options
* *
* @throws \InvalidArgumentException when a option is not supported * @throws \InvalidArgumentException when a option is not supported
* @throws \RuntimeException when a required option is not given * @throws \RuntimeException when a required option is not given
*/ */
public function __construct(array $options = array()) public function __construct(array $options = array())
{ {
$this->locale = \Locale::getDefault(); $this->locale = \Locale::getDefault();
parent::__construct($options); parent::__construct($options);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function setLocale($locale) public function setLocale($locale)
{ {
$this->locale = $locale; $this->locale = $locale;
} }
} }

View File

@ -10,36 +10,34 @@ namespace Symfony\Components\Form\ValueTransformer;
*/ */
class BooleanToStringTransformer extends BaseValueTransformer class BooleanToStringTransformer extends BaseValueTransformer
{ {
/** /**
* Transforms a boolean into a string. * Transforms a boolean into a string.
* *
* @param boolean $value Boolean value. * @param boolean $value Boolean value.
* @return string String value. * @return string String value.
*/ */
public function transform($value) public function transform($value)
{
if (!is_bool($value))
{ {
throw new \InvalidArgumentException(sprintf('Expected argument of type boolean but got %s.', gettype($value))); if (!is_bool($value)) {
throw new \InvalidArgumentException(sprintf('Expected argument of type boolean but got %s.', gettype($value)));
}
return true === $value ? '1' : '';
} }
return true === $value ? '1' : ''; /**
} * Transforms a string into a boolean.
*
/** * @param string $value String value.
* Transforms a string into a boolean. * @return boolean Boolean value.
* */
* @param string $value String value. public function reverseTransform($value)
* @return boolean Boolean value.
*/
public function reverseTransform($value)
{
if (!is_string($value))
{ {
throw new \InvalidArgumentException(sprintf('Expected argument of type string but got %s.', gettype($value))); if (!is_string($value)) {
} throw new \InvalidArgumentException(sprintf('Expected argument of type string but got %s.', gettype($value)));
}
return $value !== ''; return $value !== '';
} }
} }

View File

@ -19,92 +19,86 @@ use \Symfony\Components\Form\ValueTransformer\ValueTransformerException;
*/ */
class DateTimeToArrayTransformer extends BaseDateTimeTransformer class DateTimeToArrayTransformer extends BaseDateTimeTransformer
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{
parent::configure();
$this->addOption('input_timezone', 'UTC');
$this->addOption('output_timezone', 'UTC');
$this->addOption('pad', false);
$this->addOption('fields', array('year', 'month', 'day', 'hour', 'minute', 'second'));
}
/**
* Transforms a normalized date into a localized date string/array.
*
* @param DateTime $dateTime Normalized date.
* @return string|array Localized date array.
*/
public function transform($dateTime)
{
if (!$dateTime instanceof \DateTime)
{ {
throw new \InvalidArgumentException('Expected value of type \DateTime'); parent::configure();
$this->addOption('input_timezone', 'UTC');
$this->addOption('output_timezone', 'UTC');
$this->addOption('pad', false);
$this->addOption('fields', array('year', 'month', 'day', 'hour', 'minute', 'second'));
} }
$inputTimezone = $this->getOption('input_timezone'); /**
$outputTimezone = $this->getOption('output_timezone'); * Transforms a normalized date into a localized date string/array.
*
if ($inputTimezone != $outputTimezone) * @param DateTime $dateTime Normalized date.
* @return string|array Localized date array.
*/
public function transform($dateTime)
{ {
$dateTime->setTimezone(new \DateTimeZone($outputTimezone)); if (!$dateTime instanceof \DateTime) {
throw new \InvalidArgumentException('Expected value of type \DateTime');
}
$inputTimezone = $this->getOption('input_timezone');
$outputTimezone = $this->getOption('output_timezone');
if ($inputTimezone != $outputTimezone) {
$dateTime->setTimezone(new \DateTimeZone($outputTimezone));
}
$result = array_intersect_key(array(
'year' => $dateTime->format('Y'),
'month' => $dateTime->format('m'),
'day' => $dateTime->format('d'),
'hour' => $dateTime->format('H'),
'minute' => $dateTime->format('i'),
'second' => $dateTime->format('s'),
), array_flip($this->getOption('fields')));
if (!$this->getOption('pad')) {
foreach ($result as &$entry) {
$entry = (int)$entry;
}
}
return $result;
} }
$result = array_intersect_key(array( /**
'year' => $dateTime->format('Y'), * Transforms a localized date string/array into a normalized date.
'month' => $dateTime->format('m'), *
'day' => $dateTime->format('d'), * @param array $value Localized date string/array
'hour' => $dateTime->format('H'), * @return DateTime Normalized date
'minute' => $dateTime->format('i'), */
'second' => $dateTime->format('s'), public function reverseTransform($value)
), array_flip($this->getOption('fields')));
if (!$this->getOption('pad'))
{ {
foreach ($result as &$entry) $inputTimezone = $this->getOption('input_timezone');
{ $outputTimezone = $this->getOption('output_timezone');
$entry = (int)$entry;
} if (!is_array($value)) {
throw new \InvalidArgumentException(sprintf('Expected argument of type array, %s given', gettype($value)));
}
$dateTime = new \DateTime(sprintf(
'%s-%s-%s %s:%s:%s %s',
isset($value['year']) ? $value['year'] : 1970,
isset($value['month']) ? $value['month'] : 1,
isset($value['day']) ? $value['day'] : 1,
isset($value['hour']) ? $value['hour'] : 0,
isset($value['minute']) ? $value['minute'] : 0,
isset($value['second']) ? $value['second'] : 0,
$outputTimezone
));
if ($inputTimezone != $outputTimezone) {
$dateTime->setTimezone(new \DateTimeZone($inputTimezone));
}
return $dateTime;
} }
return $result;
}
/**
* Transforms a localized date string/array into a normalized date.
*
* @param array $value Localized date string/array
* @return DateTime Normalized date
*/
public function reverseTransform($value)
{
$inputTimezone = $this->getOption('input_timezone');
$outputTimezone = $this->getOption('output_timezone');
if (!is_array($value))
{
throw new \InvalidArgumentException(sprintf('Expected argument of type array, %s given', gettype($value)));
}
$dateTime = new \DateTime(sprintf(
'%s-%s-%s %s:%s:%s %s',
isset($value['year']) ? $value['year'] : 1970,
isset($value['month']) ? $value['month'] : 1,
isset($value['day']) ? $value['day'] : 1,
isset($value['hour']) ? $value['hour'] : 0,
isset($value['minute']) ? $value['minute'] : 0,
isset($value['second']) ? $value['second'] : 0,
$outputTimezone
));
if ($inputTimezone != $outputTimezone)
{
$dateTime->setTimezone(new \DateTimeZone($inputTimezone));
}
return $dateTime;
}
} }

View File

@ -19,104 +19,96 @@ use \Symfony\Components\Form\ValueTransformer\ValueTransformerException;
*/ */
class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{
parent::configure();
$this->addOption('date_format', self::MEDIUM);
$this->addOption('time_format', self::SHORT);
$this->addOption('input_timezone', 'UTC');
$this->addOption('output_timezone', 'UTC');
if (!in_array($this->getOption('date_format'), self::$formats, true))
{ {
throw new \InvalidArgumentException(sprintf('The option "date_format" is expected to be one of "%s". Is "%s"', implode('", "', self::$formats), $this->getOption('time_format'))); parent::configure();
$this->addOption('date_format', self::MEDIUM);
$this->addOption('time_format', self::SHORT);
$this->addOption('input_timezone', 'UTC');
$this->addOption('output_timezone', 'UTC');
if (!in_array($this->getOption('date_format'), self::$formats, true)) {
throw new \InvalidArgumentException(sprintf('The option "date_format" is expected to be one of "%s". Is "%s"', implode('", "', self::$formats), $this->getOption('time_format')));
}
if (!in_array($this->getOption('time_format'), self::$formats, true)) {
throw new \InvalidArgumentException(sprintf('The option "time_format" is expected to be one of "%s". Is "%s"', implode('", "', self::$formats), $this->getOption('time_format')));
}
} }
if (!in_array($this->getOption('time_format'), self::$formats, true)) /**
* Transforms a normalized date into a localized date string/array.
*
* @param DateTime $dateTime Normalized date.
* @return string|array Localized date string/array.
*/
public function transform($dateTime)
{ {
throw new \InvalidArgumentException(sprintf('The option "time_format" is expected to be one of "%s". Is "%s"', implode('", "', self::$formats), $this->getOption('time_format'))); if (!$dateTime instanceof \DateTime) {
} throw new \InvalidArgumentException('Expected value of type \DateTime');
} }
/** $inputTimezone = $this->getOption('input_timezone');
* Transforms a normalized date into a localized date string/array.
* // convert time to UTC before passing it to the formatter
* @param DateTime $dateTime Normalized date. if ($inputTimezone != 'UTC') {
* @return string|array Localized date string/array. $dateTime->setTimezone(new \DateTimeZone('UTC'));
*/ }
public function transform($dateTime)
{ $value = $this->getIntlDateFormatter()->format((int)$dateTime->format('U'));
if (!$dateTime instanceof \DateTime)
{ if (intl_get_error_code() != 0) {
throw new \InvalidArgumentException('Expected value of type \DateTime'); throw new TransformationFailedException(intl_get_error_message());
}
return $value;
} }
$inputTimezone = $this->getOption('input_timezone'); /**
* Transforms a localized date string/array into a normalized date.
// convert time to UTC before passing it to the formatter *
if ($inputTimezone != 'UTC') * @param string|array $value Localized date string/array
* @return DateTime Normalized date
*/
public function reverseTransform($value)
{ {
$dateTime->setTimezone(new \DateTimeZone('UTC')); $inputTimezone = $this->getOption('input_timezone');
if (!is_string($value)) {
throw new \InvalidArgumentException(sprintf('Expected argument of type string, %s given', gettype($value)));
}
$timestamp = $this->getIntlDateFormatter()->parse($value);
if (intl_get_error_code() != 0) {
throw new TransformationFailedException(intl_get_error_message());
}
// read timestamp into DateTime object - the formatter delivers in UTC
$dateTime = new \DateTime(sprintf('@%s UTC', $timestamp));
if ($inputTimezone != 'UTC') {
$dateTime->setTimezone(new \DateTimeZone($inputTimezone));
}
return $dateTime;
} }
$value = $this->getIntlDateFormatter()->format((int)$dateTime->format('U')); /**
* Returns a preconfigured IntlDateFormatter instance
if (intl_get_error_code() != 0) *
* @return \IntlDateFormatter
*/
protected function getIntlDateFormatter()
{ {
throw new TransformationFailedException(intl_get_error_message()); $dateFormat = $this->getIntlFormatConstant($this->getOption('date_format'));
$timeFormat = $this->getIntlFormatConstant($this->getOption('time_format'));
$timezone = $this->getOption('output_timezone');
return new \IntlDateFormatter($this->locale, $dateFormat, $timeFormat, $timezone);
} }
return $value;
}
/**
* Transforms a localized date string/array into a normalized date.
*
* @param string|array $value Localized date string/array
* @return DateTime Normalized date
*/
public function reverseTransform($value)
{
$inputTimezone = $this->getOption('input_timezone');
if (!is_string($value))
{
throw new \InvalidArgumentException(sprintf('Expected argument of type string, %s given', gettype($value)));
}
$timestamp = $this->getIntlDateFormatter()->parse($value);
if (intl_get_error_code() != 0)
{
throw new TransformationFailedException(intl_get_error_message());
}
// read timestamp into DateTime object - the formatter delivers in UTC
$dateTime = new \DateTime(sprintf('@%s UTC', $timestamp));
if ($inputTimezone != 'UTC')
{
$dateTime->setTimezone(new \DateTimeZone($inputTimezone));
}
return $dateTime;
}
/**
* Returns a preconfigured IntlDateFormatter instance
*
* @return \IntlDateFormatter
*/
protected function getIntlDateFormatter()
{
$dateFormat = $this->getIntlFormatConstant($this->getOption('date_format'));
$timeFormat = $this->getIntlFormatConstant($this->getOption('time_format'));
$timezone = $this->getOption('output_timezone');
return new \IntlDateFormatter($this->locale, $dateFormat, $timeFormat, $timezone);
}
} }

View File

@ -12,43 +12,42 @@ use \Symfony\Components\Form\ValueTransformer\ValueTransformerException;
*/ */
class MoneyToLocalizedStringTransformer extends NumberToLocalizedStringTransformer class MoneyToLocalizedStringTransformer extends NumberToLocalizedStringTransformer
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{
$this->addOption('grouping', true);
$this->addOption('precision', 2);
$this->addOption('divisor', 1);
parent::configure();
}
/**
* Transforms a normalized format into a localized money string.
*
* @param number $value Normalized number
* @return string Localized money string.
*/
public function transform($value)
{
if (!is_numeric($value))
{ {
throw new \InvalidArgumentException(sprintf('Numeric argument expected, %s given', gettype($value))); $this->addOption('grouping', true);
$this->addOption('precision', 2);
$this->addOption('divisor', 1);
parent::configure();
} }
return parent::transform($value / $this->getOption('divisor')); /**
} * Transforms a normalized format into a localized money string.
*
* @param number $value Normalized number
* @return string Localized money string.
*/
public function transform($value)
{
if (!is_numeric($value)) {
throw new \InvalidArgumentException(sprintf('Numeric argument expected, %s given', gettype($value)));
}
/** return parent::transform($value / $this->getOption('divisor'));
* Transforms a localized money string into a normalized format. }
*
* @param string $value Localized money string /**
* @return number Normalized number * Transforms a localized money string into a normalized format.
*/ *
public function reverseTransform($value) * @param string $value Localized money string
{ * @return number Normalized number
return parent::reverseTransform($value) * $this->getOption('divisor'); */
} public function reverseTransform($value)
{
return parent::reverseTransform($value) * $this->getOption('divisor');
}
} }

View File

@ -13,80 +13,75 @@ use \Symfony\Components\Form\ValueTransformer\ValueTransformerException;
*/ */
class NumberToLocalizedStringTransformer extends BaseValueTransformer class NumberToLocalizedStringTransformer extends BaseValueTransformer
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{
$this->addOption('precision', null);
$this->addOption('grouping', false);
parent::configure();
}
/**
* Transforms a number type into localized number.
*
* @param number $value Number value.
* @return string Localized value.
*/
public function transform($value)
{
if (!is_numeric($value))
{ {
throw new \InvalidArgumentException(sprintf('Numeric argument expected, %s given', gettype($value))); $this->addOption('precision', null);
$this->addOption('grouping', false);
parent::configure();
} }
$formatter = $this->getNumberFormatter(); /**
$value = $formatter->format($value); * Transforms a number type into localized number.
*
if (intl_is_failure($formatter->getErrorCode())) * @param number $value Number value.
* @return string Localized value.
*/
public function transform($value)
{ {
throw new TransformationFailedException($formatter->getErrorMessage()); if (!is_numeric($value)) {
throw new \InvalidArgumentException(sprintf('Numeric argument expected, %s given', gettype($value)));
}
$formatter = $this->getNumberFormatter();
$value = $formatter->format($value);
if (intl_is_failure($formatter->getErrorCode())) {
throw new TransformationFailedException($formatter->getErrorMessage());
}
return $value;
} }
return $value; /**
} * Transforms a localized number into an integer or float
*
/** * @param string $value
* Transforms a localized number into an integer or float */
* public function reverseTransform($value)
* @param string $value
*/
public function reverseTransform($value)
{
if (!is_string($value))
{ {
throw new \InvalidArgumentException(sprintf('Expected argument of type string, %s given', gettype($value))); if (!is_string($value)) {
throw new \InvalidArgumentException(sprintf('Expected argument of type string, %s given', gettype($value)));
}
$formatter = $this->getNumberFormatter();
$value = $formatter->parse($value);
if (intl_is_failure($formatter->getErrorCode())) {
throw new TransformationFailedException($formatter->getErrorMessage());
}
return $value;
} }
$formatter = $this->getNumberFormatter(); /**
$value = $formatter->parse($value); * Returns a preconfigured \NumberFormatter instance
*
if (intl_is_failure($formatter->getErrorCode())) * @return \NumberFormatter
*/
protected function getNumberFormatter()
{ {
throw new TransformationFailedException($formatter->getErrorMessage()); $formatter = new \NumberFormatter($this->locale, \NumberFormatter::DECIMAL);
if ($this->getOption('precision') !== null) {
$formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->getOption('precision'));
}
$formatter->setAttribute(\NumberFormatter::GROUPING_USED, $this->getOption('grouping'));
return $formatter;
} }
return $value;
}
/**
* Returns a preconfigured \NumberFormatter instance
*
* @return \NumberFormatter
*/
protected function getNumberFormatter()
{
$formatter = new \NumberFormatter($this->locale, \NumberFormatter::DECIMAL);
if ($this->getOption('precision') !== null)
{
$formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->getOption('precision'));
}
$formatter->setAttribute(\NumberFormatter::GROUPING_USED, $this->getOption('grouping'));
return $formatter;
}
} }

View File

@ -12,101 +12,94 @@ use \Symfony\Components\Form\ValueTransformer\ValueTransformerException;
*/ */
class PercentToLocalizedStringTransformer extends BaseValueTransformer class PercentToLocalizedStringTransformer extends BaseValueTransformer
{ {
const FRACTIONAL = 'fractional'; const FRACTIONAL = 'fractional';
const INTEGER = 'integer'; const INTEGER = 'integer';
protected static $types = array( protected static $types = array(
self::FRACTIONAL, self::FRACTIONAL,
self::INTEGER, self::INTEGER,
); );
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{
$this->addOption('type', self::FRACTIONAL);
$this->addOption('precision', 0);
if (!in_array($this->getOption('type'), self::$types, true))
{ {
throw new \InvalidArgumentException(sprintf('The option "type" is expected to be one of "%s"', implode('", "', self::$types))); $this->addOption('type', self::FRACTIONAL);
$this->addOption('precision', 0);
if (!in_array($this->getOption('type'), self::$types, true)) {
throw new \InvalidArgumentException(sprintf('The option "type" is expected to be one of "%s"', implode('", "', self::$types)));
}
parent::configure();
} }
parent::configure(); /**
} * Transforms between a normalized format (integer or float) into a percentage value.
*
/** * @param number $value Normalized value.
* Transforms between a normalized format (integer or float) into a percentage value. * @return number Percentage value.
* */
* @param number $value Normalized value. public function transform($value)
* @return number Percentage value.
*/
public function transform($value)
{
if (!is_numeric($value))
{ {
throw new \InvalidArgumentException(sprintf('Numeric argument expected, %s given', gettype($value))); if (!is_numeric($value)) {
throw new \InvalidArgumentException(sprintf('Numeric argument expected, %s given', gettype($value)));
}
if (self::FRACTIONAL == $this->getOption('type')) {
$value *= 100;
}
$formatter = $this->getNumberFormatter();
$value = $formatter->format($value);
if (intl_is_failure($formatter->getErrorCode())) {
throw new TransformationFailedException($formatter->getErrorMessage());
}
// replace the UTF-8 non break spaces
return $value;
} }
if (self::FRACTIONAL == $this->getOption('type')) /**
* Transforms between a percentage value into a normalized format (integer or float).
*
* @param number $value Percentage value.
* @return number Normalized value.
*/
public function reverseTransform($value)
{ {
$value *= 100; if (!is_string($value)) {
throw new \InvalidArgumentException(sprintf('Expected argument of type string, %s given', gettype($value)));
}
$formatter = $this->getNumberFormatter();
// replace normal spaces so that the formatter can read them
$value = $formatter->parse(str_replace(' ', ' ', $value));
if (intl_is_failure($formatter->getErrorCode())) {
throw new TransformationFailedException($formatter->getErrorMessage());
}
if (self::FRACTIONAL == $this->getOption('type')) {
$value /= 100;
}
return $value;
} }
$formatter = $this->getNumberFormatter(); /**
$value = $formatter->format($value); * Returns a preconfigured \NumberFormatter instance
*
if (intl_is_failure($formatter->getErrorCode())) * @return \NumberFormatter
*/
protected function getNumberFormatter()
{ {
throw new TransformationFailedException($formatter->getErrorMessage()); $formatter = new \NumberFormatter($this->locale, \NumberFormatter::DECIMAL);
$formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->getOption('precision'));
return $formatter;
} }
// replace the UTF-8 non break spaces
return $value;
}
/**
* Transforms between a percentage value into a normalized format (integer or float).
*
* @param number $value Percentage value.
* @return number Normalized value.
*/
public function reverseTransform($value)
{
if (!is_string($value))
{
throw new \InvalidArgumentException(sprintf('Expected argument of type string, %s given', gettype($value)));
}
$formatter = $this->getNumberFormatter();
// replace normal spaces so that the formatter can read them
$value = $formatter->parse(str_replace(' ', ' ', $value));
if (intl_is_failure($formatter->getErrorCode()))
{
throw new TransformationFailedException($formatter->getErrorMessage());
}
if (self::FRACTIONAL == $this->getOption('type'))
{
$value /= 100;
}
return $value;
}
/**
* Returns a preconfigured \NumberFormatter instance
*
* @return \NumberFormatter
*/
protected function getNumberFormatter()
{
$formatter = new \NumberFormatter($this->locale, \NumberFormatter::DECIMAL);
$formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->getOption('precision'));
return $formatter;
}
} }

View File

@ -12,43 +12,43 @@ namespace Symfony\Components\Form\ValueTransformer;
*/ */
class ReversedTransformer implements ValueTransformerInterface class ReversedTransformer implements ValueTransformerInterface
{ {
/** /**
* The reversed transformer * The reversed transformer
* @var ValueTransformerInterface * @var ValueTransformerInterface
*/ */
protected $reversedTransformer; protected $reversedTransformer;
/** /**
* Reverses this transformer * Reverses this transformer
* *
* @param ValueTransformerInterface $innerTransformer * @param ValueTransformerInterface $innerTransformer
*/ */
public function __construct(ValueTransformerInterface $reversedTransformer) public function __construct(ValueTransformerInterface $reversedTransformer)
{ {
$this->reversedTransformer = $reversedTransformer; $this->reversedTransformer = $reversedTransformer;
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function transform($value) public function transform($value)
{ {
return $this->reversedTransformer->reverseTransform($value); return $this->reversedTransformer->reverseTransform($value);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function reverseTransform($value) public function reverseTransform($value)
{ {
return $this->reversedTransformer->transform($value); return $this->reversedTransformer->transform($value);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function setLocale($locale) public function setLocale($locale)
{ {
$this->reversedTransformer->setLocale($locale); $this->reversedTransformer->setLocale($locale);
} }
} }

View File

@ -12,60 +12,55 @@ use \Symfony\Components\Form\ValueTransformer\ValueTransformerException;
*/ */
class StringToDateTimeTransformer extends BaseValueTransformer class StringToDateTimeTransformer extends BaseValueTransformer
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{
$this->addOption('input_timezone', 'UTC');
$this->addOption('output_timezone', 'UTC');
$this->addOption('format', 'Y-m-d H:i:s');
}
/**
* Transforms a date string in the configured timezone into a DateTime object
*
* @param string $value A value as produced by PHP's date() function
* @return DateTime A DateTime object
*/
public function transform($value)
{
$inputTimezone = $this->getOption('input_timezone');
$outputTimezone = $this->getOption('output_timezone');
try
{ {
$dateTime = new \DateTime("$value $inputTimezone"); $this->addOption('input_timezone', 'UTC');
$this->addOption('output_timezone', 'UTC');
if ($inputTimezone != $outputTimezone) $this->addOption('format', 'Y-m-d H:i:s');
{
$dateTime->setTimeZone(new \DateTimeZone($outputTimezone));
}
return $dateTime;
}
catch (\Exception $e)
{
throw new \InvalidArgumentException('Expected a valid date string. ' . $e->getMessage(), 0, $e);
}
}
/**
* Transforms a DateTime object into a date string with the configured format
* and timezone
*
* @param DateTime $value A DateTime object
* @return string A value as produced by PHP's date() function
*/
public function reverseTransform($value)
{
if (!$value instanceof \DateTime)
{
throw new \InvalidArgumentException('Expected value of type \DateTime');
} }
$value->setTimezone(new \DateTimeZone($this->getOption('input_timezone'))); /**
* Transforms a date string in the configured timezone into a DateTime object
*
* @param string $value A value as produced by PHP's date() function
* @return DateTime A DateTime object
*/
public function transform($value)
{
$inputTimezone = $this->getOption('input_timezone');
$outputTimezone = $this->getOption('output_timezone');
return $value->format($this->getOption('format')); try {
} $dateTime = new \DateTime("$value $inputTimezone");
if ($inputTimezone != $outputTimezone) {
$dateTime->setTimeZone(new \DateTimeZone($outputTimezone));
}
return $dateTime;
} catch (\Exception $e) {
throw new \InvalidArgumentException('Expected a valid date string. ' . $e->getMessage(), 0, $e);
}
}
/**
* Transforms a DateTime object into a date string with the configured format
* and timezone
*
* @param DateTime $value A DateTime object
* @return string A value as produced by PHP's date() function
*/
public function reverseTransform($value)
{
if (!$value instanceof \DateTime) {
throw new \InvalidArgumentException('Expected value of type \DateTime');
}
$value->setTimezone(new \DateTimeZone($this->getOption('input_timezone')));
return $value->format($this->getOption('format'));
}
} }

View File

@ -12,58 +12,53 @@ use \Symfony\Components\Form\ValueTransformer\ValueTransformerException;
*/ */
class TimestampToDateTimeTransformer extends BaseValueTransformer class TimestampToDateTimeTransformer extends BaseValueTransformer
{ {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function configure() protected function configure()
{
$this->addOption('input_timezone', 'UTC');
$this->addOption('output_timezone', 'UTC');
}
/**
* Transforms a timestamp in the configured timezone into a DateTime object
*
* @param string $value A value as produced by PHP's date() function
* @return DateTime A DateTime object
*/
public function transform($value)
{
$inputTimezone = $this->getOption('input_timezone');
$outputTimezone = $this->getOption('output_timezone');
try
{ {
$dateTime = new \DateTime("@$value $inputTimezone"); $this->addOption('input_timezone', 'UTC');
$this->addOption('output_timezone', 'UTC');
if ($inputTimezone != $outputTimezone)
{
$dateTime->setTimezone(new \DateTimeZone($outputTimezone));
}
return $dateTime;
}
catch (\Exception $e)
{
throw new \InvalidArgumentException('Expected a valid timestamp. ' . $e->getMessage(), 0, $e);
}
}
/**
* Transforms a DateTime object into a timestamp in the configured timezone
*
* @param DateTime $value A DateTime object
* @return integer A timestamp
*/
public function reverseTransform($value)
{
if (!$value instanceof \DateTime)
{
throw new \InvalidArgumentException('Expected value of type \DateTime');
} }
$value->setTimezone(new \DateTimeZone($this->getOption('input_timezone'))); /**
* Transforms a timestamp in the configured timezone into a DateTime object
*
* @param string $value A value as produced by PHP's date() function
* @return DateTime A DateTime object
*/
public function transform($value)
{
$inputTimezone = $this->getOption('input_timezone');
$outputTimezone = $this->getOption('output_timezone');
return (int)$value->format('U'); try {
} $dateTime = new \DateTime("@$value $inputTimezone");
if ($inputTimezone != $outputTimezone) {
$dateTime->setTimezone(new \DateTimeZone($outputTimezone));
}
return $dateTime;
} catch (\Exception $e) {
throw new \InvalidArgumentException('Expected a valid timestamp. ' . $e->getMessage(), 0, $e);
}
}
/**
* Transforms a DateTime object into a timestamp in the configured timezone
*
* @param DateTime $value A DateTime object
* @return integer A timestamp
*/
public function reverseTransform($value)
{
if (!$value instanceof \DateTime) {
throw new \InvalidArgumentException('Expected value of type \DateTime');
}
$value->setTimezone(new \DateTimeZone($this->getOption('input_timezone')));
return (int)$value->format('U');
}
} }

View File

@ -9,73 +9,70 @@ namespace Symfony\Components\Form\ValueTransformer;
*/ */
class ValueTransformerChain implements ValueTransformerInterface class ValueTransformerChain implements ValueTransformerInterface
{ {
/** /**
* The value transformers * The value transformers
* @var array * @var array
*/ */
protected $transformers; protected $transformers;
/** /**
* Uses the given value transformers to transform values * Uses the given value transformers to transform values
* *
* @param array $transformers * @param array $transformers
*/ */
public function __construct(array $transformers) public function __construct(array $transformers)
{
$this->transformers = $transformers;
}
/**
* Passes the value through the transform() method of all nested transformers
*
* The transformers receive the value in the same order as they were passed
* to the constructor. Each transformer receives the result of the previous
* transformer as input. The output of the last transformer is returned
* by this method.
*
* @param mixed $value The original value
* @return mixed The transformed value
*/
public function transform($value)
{
foreach ($this->transformers as $transformer)
{ {
$value = $transformer->transform($value); $this->transformers = $transformers;
} }
return $value; /**
} * Passes the value through the transform() method of all nested transformers
*
/** * The transformers receive the value in the same order as they were passed
* Passes the value through the reverseTransform() method of all nested * to the constructor. Each transformer receives the result of the previous
* transformers * transformer as input. The output of the last transformer is returned
* * by this method.
* The transformers receive the value in the reverse order as they were passed *
* to the constructor. Each transformer receives the result of the previous * @param mixed $value The original value
* transformer as input. The output of the last transformer is returned * @return mixed The transformed value
* by this method. */
* public function transform($value)
* @param mixed $value The transformed value
* @return mixed The reverse-transformed value
*/
public function reverseTransform($value)
{
for ($i = count($this->transformers) - 1; $i >= 0; --$i)
{ {
$value = $this->transformers[$i]->reverseTransform($value); foreach ($this->transformers as $transformer) {
$value = $transformer->transform($value);
}
return $value;
} }
return $value; /**
} * Passes the value through the reverseTransform() method of all nested
* transformers
/** *
* {@inheritDoc} * The transformers receive the value in the reverse order as they were passed
*/ * to the constructor. Each transformer receives the result of the previous
public function setLocale($locale) * transformer as input. The output of the last transformer is returned
{ * by this method.
foreach ($this->transformers as $transformer) *
* @param mixed $value The transformed value
* @return mixed The reverse-transformed value
*/
public function reverseTransform($value)
{ {
$transformer->setLocale($locale); for ($i = count($this->transformers) - 1; $i >= 0; --$i) {
$value = $this->transformers[$i]->reverseTransform($value);
}
return $value;
}
/**
* {@inheritDoc}
*/
public function setLocale($locale)
{
foreach ($this->transformers as $transformer) {
$transformer->setLocale($locale);
}
} }
}
} }

View File

@ -11,28 +11,28 @@ use Symfony\Components\Form\Localizable;
*/ */
interface ValueTransformerInterface extends Localizable interface ValueTransformerInterface extends Localizable
{ {
/** /**
* Transforms a value from the original representation to a transformed * Transforms a value from the original representation to a transformed
* representation. * representation.
* *
* @param mixed $value The value in the original representation * @param mixed $value The value in the original representation
* @return mixed The value in the transformed representation * @return mixed The value in the transformed representation
* @throws InvalidArgument Exception when the argument is no string * @throws InvalidArgument Exception when the argument is no string
* @throws ValueTransformer Exception when the transformation fails * @throws ValueTransformer Exception when the transformation fails
*/ */
public function transform($value); public function transform($value);
/** /**
* Transforms a value from the transformed representation to its original * Transforms a value from the transformed representation to its original
* representation. * representation.
* *
* This method must be able to deal with null values. * This method must be able to deal with null values.
* *
* @param mixed $value The value in the transformed representation * @param mixed $value The value in the transformed representation
* @return mixed The value in the original representation * @return mixed The value in the original representation
* @throws InvalidArgument Exception when the argument is not of the * @throws InvalidArgument Exception when the argument is not of the
* expected type * expected type
* @throws ValueTransformer Exception when the transformation fails * @throws ValueTransformer Exception when the transformation fails
*/ */
public function reverseTransform($value); public function reverseTransform($value);
} }

View File

@ -9,14 +9,14 @@ namespace Symfony\Components\I18N;
*/ */
interface TranslatorInterface interface TranslatorInterface
{ {
/** /**
* Translates a given text string. * Translates a given text string.
* *
* @param string $text The text to translate * @param string $text The text to translate
* @param array $parameters The parameters to inject into the text * @param array $parameters The parameters to inject into the text
* @param string $locale The locale of the translated text. If null, * @param string $locale The locale of the translated text. If null,
* the preconfigured locale of the translator * the preconfigured locale of the translator
* or the system's default culture is used. * or the system's default culture is used.
*/ */
public function translate($text, array $parameters = array(), $locale = null); public function translate($text, array $parameters = array(), $locale = null);
} }

View File

@ -19,160 +19,145 @@ use Symfony\Components\Validator\Exception\ConstraintDefinitionException;
*/ */
class Constraint class Constraint
{ {
const DEFAULT_GROUP = 'Default'; const DEFAULT_GROUP = 'Default';
public $groups = self::DEFAULT_GROUP; public $groups = self::DEFAULT_GROUP;
/** /**
* Initializes the constraint with options * Initializes the constraint with options
* *
* You should pass an associative array. The keys should be the names of * You should pass an associative array. The keys should be the names of
* existing properties in this class. The values should be the value for these * existing properties in this class. The values should be the value for these
* properties. * properties.
* *
* Alternatively you can override the method defaultOption() to return the * Alternatively you can override the method defaultOption() to return the
* name of an existing property. If no associative array is passed, this * name of an existing property. If no associative array is passed, this
* property is set instead. * property is set instead.
* *
* You can force that certain options are set by overriding * You can force that certain options are set by overriding
* requiredOptions() to return the names of these options. If any * requiredOptions() to return the names of these options. If any
* option is not set here, an exception is thrown. * option is not set here, an exception is thrown.
* *
* @param mixed $options The options (as associative array) * @param mixed $options The options (as associative array)
* or the value for the default * or the value for the default
* option (any other type) * option (any other type)
* @throws InvalidOptionsException When you pass the names of non-existing * @throws InvalidOptionsException When you pass the names of non-existing
* options * options
* @throws MissingOptionsException When you don't pass any of the options * @throws MissingOptionsException When you don't pass any of the options
* returned by requiredOptions() * returned by requiredOptions()
* @throws ConstraintDefinitionException When you don't pass an associative * @throws ConstraintDefinitionException When you don't pass an associative
* array, but defaultOption() returns * array, but defaultOption() returns
* NULL * NULL
*/ */
public function __construct($options = null) public function __construct($options = null)
{
$invalidOptions = array();
$missingOptions = array_flip((array)$this->requiredOptions());
if (is_array($options) && count($options) == 1 && isset($options['value']))
{ {
$options = $options['value']; $invalidOptions = array();
} $missingOptions = array_flip((array)$this->requiredOptions());
if (is_array($options) && count($options) > 0 && is_string(key($options))) if (is_array($options) && count($options) == 1 && isset($options['value'])) {
{ $options = $options['value'];
foreach ($options as $option => $value)
{
if (property_exists($this, $option))
{
$this->$option = $value;
unset($missingOptions[$option]);
} }
else
{ if (is_array($options) && count($options) > 0 && is_string(key($options))) {
$invalidOptions[] = $option; foreach ($options as $option => $value) {
if (property_exists($this, $option)) {
$this->$option = $value;
unset($missingOptions[$option]);
} else {
$invalidOptions[] = $option;
}
}
} else if ($options) {
$option = $this->defaultOption();
if (is_null($option)) {
throw new ConstraintDefinitionException(
sprintf('No default option is configured for constraint %s', get_class($this))
);
}
if (property_exists($this, $option)) {
$this->$option = $options;
unset($missingOptions[$option]);
} else {
$invalidOptions[] = $option;
}
} }
}
if (count($invalidOptions) > 0) {
throw new InvalidOptionsException(
sprintf('The options "%s" do not exist in constraint %s', implode('", "', $invalidOptions), get_class($this)),
$invalidOptions
);
}
if (count($missingOptions) > 0) {
throw new MissingOptionsException(
sprintf('The options "%s" must be set for constraint %s', implode('", "', array_keys($missingOptions)), get_class($this)),
array_keys($missingOptions)
);
}
$this->groups = (array)$this->groups;
} }
else if ($options)
/**
* Unsupported operation.
*/
public function __set($option, $value)
{ {
$option = $this->defaultOption(); throw new InvalidOptionsException(sprintf('The option "%s" does not exist in constraint %s', $option, get_class($this)), array($option));
if (is_null($option))
{
throw new ConstraintDefinitionException(
sprintf('No default option is configured for constraint %s', get_class($this))
);
}
if (property_exists($this, $option))
{
$this->$option = $options;
unset($missingOptions[$option]);
}
else
{
$invalidOptions[] = $option;
}
} }
if (count($invalidOptions) > 0) /**
* Adds the given group if this constraint is in the Default group
*
* @param string $group
*/
public function addImplicitGroupName($group)
{ {
throw new InvalidOptionsException( if (in_array(Constraint::DEFAULT_GROUP, $this->groups) && !in_array($group, $this->groups)) {
sprintf('The options "%s" do not exist in constraint %s', implode('", "', $invalidOptions), get_class($this)), $this->groups[] = $group;
$invalidOptions }
);
} }
if (count($missingOptions) > 0) /**
* Returns the name of the default option
*
* Override this method to define a default option.
*
* @return string
* @see __construct()
*/
public function defaultOption()
{ {
throw new MissingOptionsException( return null;
sprintf('The options "%s" must be set for constraint %s', implode('", "', array_keys($missingOptions)), get_class($this)),
array_keys($missingOptions)
);
} }
$this->groups = (array)$this->groups; /**
} * Returns the name of the required options
*
/** * Override this method if you want to define required options.
* Unsupported operation. *
*/ * @return array
public function __set($option, $value) * @see __construct()
{ */
throw new InvalidOptionsException(sprintf('The option "%s" does not exist in constraint %s', $option, get_class($this)), array($option)); public function requiredOptions()
}
/**
* Adds the given group if this constraint is in the Default group
*
* @param string $group
*/
public function addImplicitGroupName($group)
{
if (in_array(Constraint::DEFAULT_GROUP, $this->groups) && !in_array($group, $this->groups))
{ {
$this->groups[] = $group; return array();
} }
}
/** /**
* Returns the name of the default option * Returns the name of the class that validates this constraint
* *
* Override this method to define a default option. * By default, this is the fully qualified name of the constraint class
* * suffixed with "Validator". You can override this method to change that
* @return string * behaviour.
* @see __construct() *
*/ * @return string
public function defaultOption() */
{ public function validatedBy()
return null; {
} return get_class($this) . 'Validator';
}
/**
* Returns the name of the required options
*
* Override this method if you want to define required options.
*
* @return array
* @see __construct()
*/
public function requiredOptions()
{
return array();
}
/**
* Returns the name of the class that validates this constraint
*
* By default, this is the fully qualified name of the constraint class
* suffixed with "Validator". You can override this method to change that
* behaviour.
*
* @return string
*/
public function validatedBy()
{
return get_class($this) . 'Validator';
}
} }

View File

@ -4,30 +4,30 @@ namespace Symfony\Components\Validator;
abstract class ConstraintValidator implements ConstraintValidatorInterface abstract class ConstraintValidator implements ConstraintValidatorInterface
{ {
protected $context; protected $context;
private $messageTemplate; private $messageTemplate;
private $messageParameters; private $messageParameters;
public function initialize(ValidationContext $context) public function initialize(ValidationContext $context)
{ {
$this->context = $context; $this->context = $context;
$this->messageTemplate = ''; $this->messageTemplate = '';
$this->messageParameters = array(); $this->messageParameters = array();
} }
public function getMessageTemplate() public function getMessageTemplate()
{ {
return $this->messageTemplate; return $this->messageTemplate;
} }
public function getMessageParameters() public function getMessageParameters()
{ {
return $this->messageParameters; return $this->messageParameters;
} }
protected function setMessage($template, array $parameters = array()) protected function setMessage($template, array $parameters = array())
{ {
$this->messageTemplate = $template; $this->messageTemplate = $template;
$this->messageParameters = $parameters; $this->messageParameters = $parameters;
} }
} }

View File

@ -7,17 +7,16 @@ use Symfony\Components\Validator\Constraint;
class ConstraintValidatorFactory implements ConstraintValidatorFactoryInterface class ConstraintValidatorFactory implements ConstraintValidatorFactoryInterface
{ {
protected $validators = array(); protected $validators = array();
public function getInstance(Constraint $constraint) public function getInstance(Constraint $constraint)
{
$className = $constraint->validatedBy();
if (!isset($this->validators[$className]))
{ {
$this->validators[$className] = new $className(); $className = $constraint->validatedBy();
}
return $this->validators[$className]; if (!isset($this->validators[$className])) {
} $this->validators[$className] = new $className();
}
return $this->validators[$className];
}
} }

View File

@ -6,5 +6,5 @@ use Symfony\Components\Validator\Constraint;
interface ConstraintValidatorFactoryInterface interface ConstraintValidatorFactoryInterface
{ {
public function getInstance(Constraint $constraint); public function getInstance(Constraint $constraint);
} }

View File

@ -4,11 +4,11 @@ namespace Symfony\Components\Validator;
interface ConstraintValidatorInterface interface ConstraintValidatorInterface
{ {
public function initialize(ValidationContext $context); public function initialize(ValidationContext $context);
public function isValid($value, Constraint $constraint); public function isValid($value, Constraint $constraint);
public function getMessageTemplate(); public function getMessageTemplate();
public function getMessageParameters(); public function getMessageParameters();
} }

View File

@ -4,36 +4,36 @@ namespace Symfony\Components\Validator;
class ConstraintViolation class ConstraintViolation
{ {
protected $message; protected $message;
protected $root; protected $root;
protected $propertyPath; protected $propertyPath;
protected $invalidValue; protected $invalidValue;
public function __construct($message, $root, $propertyPath, $invalidValue) public function __construct($message, $root, $propertyPath, $invalidValue)
{ {
$this->message = $message; $this->message = $message;
$this->root = $root; $this->root = $root;
$this->propertyPath = $propertyPath; $this->propertyPath = $propertyPath;
$this->invalidValue = $invalidValue; $this->invalidValue = $invalidValue;
} }
public function getMessage() public function getMessage()
{ {
return $this->message; return $this->message;
} }
public function getRoot() public function getRoot()
{ {
return $this->root; return $this->root;
} }
public function getPropertyPath() public function getPropertyPath()
{ {
return $this->propertyPath; return $this->propertyPath;
} }
public function getInvalidValue() public function getInvalidValue()
{ {
return $this->invalidValue; return $this->invalidValue;
} }
} }

View File

@ -4,46 +4,44 @@ namespace Symfony\Components\Validator;
class ConstraintViolationList implements \IteratorAggregate, \Countable class ConstraintViolationList implements \IteratorAggregate, \Countable
{ {
protected $violations = array(); protected $violations = array();
public function __toString() public function __toString()
{
$string = '';
foreach ($this->violations as $violation)
{ {
$param = $violation->getMessageParameters(); $string = '';
$message = str_replace(array_keys($param), $param, $violation->getMessageTemplate());
$string .= <<<EOF foreach ($this->violations as $violation) {
$param = $violation->getMessageParameters();
$message = str_replace(array_keys($param), $param, $violation->getMessageTemplate());
$string .= <<<EOF
{$violation->getRoot()}.{$violation->getPropertyPath()}: {$violation->getRoot()}.{$violation->getPropertyPath()}:
$message $message
EOF; EOF;
}
return $string;
} }
return $string; public function add(ConstraintViolation $violation)
}
public function add(ConstraintViolation $violation)
{
$this->violations[] = $violation;
}
public function addAll(ConstraintViolationList $violations)
{
foreach ($violations->violations as $violation)
{ {
$this->violations[] = $violation; $this->violations[] = $violation;
} }
}
public function getIterator() public function addAll(ConstraintViolationList $violations)
{ {
return new \ArrayIterator($this->violations); foreach ($violations->violations as $violation) {
} $this->violations[] = $violation;
}
}
public function count() public function getIterator()
{ {
return count($this->violations); return new \ArrayIterator($this->violations);
} }
public function count()
{
return count($this->violations);
}
} }

View File

@ -4,15 +4,15 @@ namespace Symfony\Components\Validator\Constraints;
class All extends \Symfony\Components\Validator\Constraint class All extends \Symfony\Components\Validator\Constraint
{ {
public $constraints = array(); public $constraints = array();
public function defaultOption() public function defaultOption()
{ {
return 'constraints'; return 'constraints';
} }
public function requiredOptions() public function requiredOptions()
{ {
return array('constraints'); return array('constraints');
} }
} }

View File

@ -8,34 +8,30 @@ use Symfony\Components\Validator\Exception\UnexpectedTypeException;
class AllValidator extends ConstraintValidator class AllValidator extends ConstraintValidator
{ {
public function isValid($value, Constraint $constraint) public function isValid($value, Constraint $constraint)
{
if ($value === null)
{ {
return true; if ($value === null) {
return true;
}
if (!is_array($value) && !$value instanceof \Traversable) {
throw new UnexpectedTypeException($value, 'array or Traversable');
}
$walker = $this->context->getGraphWalker();
$group = $this->context->getGroup();
$propertyPath = $this->context->getPropertyPath();
// cannot simply cast to array, because then the object is converted to an
// array instead of wrapped inside
$constraints = is_array($constraint->constraints) ? $constraint->constraints : array($constraint->constraints);
foreach ($value as $key => $element) {
foreach ($constraints as $constr) {
$walker->walkConstraint($constr, $element, $group, $propertyPath.'['.$key.']');
}
}
return true;
} }
if (!is_array($value) && !$value instanceof \Traversable)
{
throw new UnexpectedTypeException($value, 'array or Traversable');
}
$walker = $this->context->getGraphWalker();
$group = $this->context->getGroup();
$propertyPath = $this->context->getPropertyPath();
// cannot simply cast to array, because then the object is converted to an
// array instead of wrapped inside
$constraints = is_array($constraint->constraints) ? $constraint->constraints : array($constraint->constraints);
foreach ($value as $key => $element)
{
foreach ($constraints as $constr)
{
$walker->walkConstraint($constr, $element, $group, $propertyPath.'['.$key.']');
}
}
return true;
}
} }

View File

@ -4,5 +4,5 @@ namespace Symfony\Components\Validator\Constraints;
class AssertFalse extends \Symfony\Components\Validator\Constraint class AssertFalse extends \Symfony\Components\Validator\Constraint
{ {
public $message = 'Symfony.Validator.AssertFalse.message'; public $message = 'Symfony.Validator.AssertFalse.message';
} }

View File

@ -7,20 +7,18 @@ use Symfony\Components\Validator\ConstraintValidator;
class AssertFalseValidator extends ConstraintValidator class AssertFalseValidator extends ConstraintValidator
{ {
public function isValid($value, Constraint $constraint) public function isValid($value, Constraint $constraint)
{
if ($value === null)
{ {
return true; if ($value === null) {
return true;
}
if ($value) {
$this->setMessage($constraint->message);
return false;
}
return true;
} }
if ($value)
{
$this->setMessage($constraint->message);
return false;
}
return true;
}
} }

View File

@ -4,5 +4,5 @@ namespace Symfony\Components\Validator\Constraints;
class AssertTrue extends \Symfony\Components\Validator\Constraint class AssertTrue extends \Symfony\Components\Validator\Constraint
{ {
public $message = 'Symfony.Validator.AssertTrue.message'; public $message = 'Symfony.Validator.AssertTrue.message';
} }

View File

@ -7,20 +7,18 @@ use Symfony\Components\Validator\ConstraintValidator;
class AssertTrueValidator extends ConstraintValidator class AssertTrueValidator extends ConstraintValidator
{ {
public function isValid($value, Constraint $constraint) public function isValid($value, Constraint $constraint)
{
if ($value === null)
{ {
return true; if ($value === null) {
return true;
}
if (!$value) {
$this->setMessage($constraint->message);
return false;
}
return true;
} }
if (!$value)
{
$this->setMessage($constraint->message);
return false;
}
return true;
}
} }

View File

@ -4,22 +4,22 @@ namespace Symfony\Components\Validator\Constraints;
class AssertType extends \Symfony\Components\Validator\Constraint class AssertType extends \Symfony\Components\Validator\Constraint
{ {
public $message = 'Symfony.Validator.AssertType.message'; public $message = 'Symfony.Validator.AssertType.message';
public $type; public $type;
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function defaultOption() public function defaultOption()
{ {
return 'type'; return 'type';
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function requiredOptions() public function requiredOptions()
{ {
return array('type'); return array('type');
} }
} }

View File

@ -7,30 +7,26 @@ use Symfony\Components\Validator\ConstraintValidator;
class AssertTypeValidator extends ConstraintValidator class AssertTypeValidator extends ConstraintValidator
{ {
public function isValid($value, Constraint $constraint) public function isValid($value, Constraint $constraint)
{
if ($value === null)
{ {
return true; if ($value === null) {
return true;
}
$type = $constraint->type == 'boolean' ? 'bool' : $constraint->type;
$function = 'is_' . $type;
if (function_exists($function) && call_user_func($function, $value)) {
return true;
} else if ($value instanceof $constraint->type) {
return true;
}
$this->setMessage($constraint->message, array(
'value' => $value,
'type' => $constraint->type,
));
return false;
} }
$type = $constraint->type == 'boolean' ? 'bool' : $constraint->type;
$function = 'is_' . $type;
if (function_exists($function) && call_user_func($function, $value))
{
return true;
}
else if ($value instanceof $constraint->type)
{
return true;
}
$this->setMessage($constraint->message, array(
'value' => $value,
'type' => $constraint->type,
));
return false;
}
} }

View File

@ -4,5 +4,5 @@ namespace Symfony\Components\Validator\Constraints;
class Blank extends \Symfony\Components\Validator\Constraint class Blank extends \Symfony\Components\Validator\Constraint
{ {
public $message = 'Symfony.Validator.Blank.message'; public $message = 'Symfony.Validator.Blank.message';
} }

View File

@ -7,15 +7,14 @@ use Symfony\Components\Validator\ConstraintValidator;
class BlankValidator extends ConstraintValidator class BlankValidator extends ConstraintValidator
{ {
public function isValid($value, Constraint $constraint) public function isValid($value, Constraint $constraint)
{
if ($value !== '' && $value !== null)
{ {
$this->setMessage($constraint->message, array('value' => $value)); if ($value !== '' && $value !== null) {
$this->setMessage($constraint->message, array('value' => $value));
return false; return false;
}
return true;
} }
return true;
}
} }

View File

@ -12,20 +12,20 @@ namespace Symfony\Components\Validator\Constraints;
class Choice extends \Symfony\Components\Validator\Constraint class Choice extends \Symfony\Components\Validator\Constraint
{ {
public $choices; public $choices;
public $callback; public $callback;
public $multiple = false; public $multiple = false;
public $min = null; public $min = null;
public $max = null; public $max = null;
public $message = 'Symfony.Validator.Choice.message'; public $message = 'Symfony.Validator.Choice.message';
public $minMessage = 'Symfony.Validator.Choice.minMessage'; public $minMessage = 'Symfony.Validator.Choice.minMessage';
public $maxMessage = 'Symfony.Validator.Choice.maxMessage'; public $maxMessage = 'Symfony.Validator.Choice.maxMessage';
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function defaultOption() public function defaultOption()
{ {
return 'choices'; return 'choices';
} }
} }

View File

@ -24,78 +24,60 @@ use Symfony\Components\Validator\Exception\UnexpectedTypeException;
*/ */
class ChoiceValidator extends ConstraintValidator class ChoiceValidator extends ConstraintValidator
{ {
public function isValid($value, Constraint $constraint) public function isValid($value, Constraint $constraint)
{
if (!$constraint->choices && !$constraint->callback)
{ {
throw new ConstraintDefinitionException('Either "choices" or "callback" must be specified on constraint Choice'); if (!$constraint->choices && !$constraint->callback) {
} throw new ConstraintDefinitionException('Either "choices" or "callback" must be specified on constraint Choice');
if ($value === null)
{
return true;
}
if ($constraint->multiple && !is_array($value))
{
throw new UnexpectedTypeException($value, 'array');
}
if ($constraint->callback)
{
if (is_callable(array($this->context->getCurrentClass(), $constraint->callback)))
{
$choices = call_user_func(array($this->context->getCurrentClass(), $constraint->callback));
}
else if (is_callable($constraint->callback))
{
$choices = call_user_func($constraint->callback);
}
else
{
throw new ConstraintDefinitionException('The Choice constraint expects a valid callback');
}
}
else
{
$choices = $constraint->choices;
}
if ($constraint->multiple)
{
foreach ($value as $_value)
{
if (!in_array($_value, $choices, true))
{
$this->setMessage($constraint->message, array('value' => $_value));
return false;
} }
}
$count = count($value); if ($value === null) {
return true;
}
if ($constraint->min !== null && $count < $constraint->min) if ($constraint->multiple && !is_array($value)) {
{ throw new UnexpectedTypeException($value, 'array');
$this->setMessage($constraint->minMessage, array('limit' => $constraint->min)); }
return false; if ($constraint->callback) {
} if (is_callable(array($this->context->getCurrentClass(), $constraint->callback))) {
$choices = call_user_func(array($this->context->getCurrentClass(), $constraint->callback));
} else if (is_callable($constraint->callback)) {
$choices = call_user_func($constraint->callback);
} else {
throw new ConstraintDefinitionException('The Choice constraint expects a valid callback');
}
} else {
$choices = $constraint->choices;
}
if ($constraint->max !== null && $count > $constraint->max) if ($constraint->multiple) {
{ foreach ($value as $_value) {
$this->setMessage($constraint->maxMessage, array('limit' => $constraint->max)); if (!in_array($_value, $choices, true)) {
$this->setMessage($constraint->message, array('value' => $_value));
return false; return false;
} }
}
$count = count($value);
if ($constraint->min !== null && $count < $constraint->min) {
$this->setMessage($constraint->minMessage, array('limit' => $constraint->min));
return false;
}
if ($constraint->max !== null && $count > $constraint->max) {
$this->setMessage($constraint->maxMessage, array('limit' => $constraint->max));
return false;
}
} elseif (!in_array($value, $choices, true)) {
$this->setMessage($constraint->message, array('value' => $value));
return false;
}
return true;
} }
elseif (!in_array($value, $choices, true))
{
$this->setMessage($constraint->message, array('value' => $value));
return false;
}
return true;
}
} }

View File

@ -4,14 +4,14 @@ namespace Symfony\Components\Validator\Constraints;
class Collection extends \Symfony\Components\Validator\Constraint class Collection extends \Symfony\Components\Validator\Constraint
{ {
public $fields; public $fields;
public $allowExtraFields = false; public $allowExtraFields = false;
public $allowMissingFields = false; public $allowMissingFields = false;
public $extraFieldsMessage = 'Symfony.Validator.Collection.extraFieldsMessage'; public $extraFieldsMessage = 'Symfony.Validator.Collection.extraFieldsMessage';
public $missingFieldsMessage = 'Symfony.Validator.Collection.missingFieldsMessage'; public $missingFieldsMessage = 'Symfony.Validator.Collection.missingFieldsMessage';
public function requiredOptions() public function requiredOptions()
{ {
return array('fields'); return array('fields');
} }
} }

View File

@ -9,69 +9,59 @@ use Symfony\Components\Validator\Exception\UnexpectedTypeException;
class CollectionValidator extends ConstraintValidator class CollectionValidator extends ConstraintValidator
{ {
public function isValid($value, Constraint $constraint) public function isValid($value, Constraint $constraint)
{
if ($value === null)
{ {
return true; if ($value === null) {
} return true;
if (!is_array($value) && !($value instanceof \Traversable && $value instanceof \ArrayAccess))
{
throw new UnexpectedTypeException($value, 'array or Traversable and ArrayAccess');
}
$walker = $this->context->getGraphWalker();
$group = $this->context->getGroup();
$propertyPath = $this->context->getPropertyPath();
$missingFields = array();
$extraFields = array();
foreach ($value as $field => $fieldValue)
{
$extraFields[$field] = $fieldValue;
}
foreach ($constraint->fields as $field => $constraints)
{
if (array_key_exists($field, $value))
{
// cannot simply cast to array, because then the object is converted to an
// array instead of wrapped inside
$constraints = is_array($constraints) ? $constraints : array($constraints);
foreach ($constraints as $constr)
{
$walker->walkConstraint($constr, $value[$field], $group, $propertyPath.'['.$field.']');
} }
unset($extraFields[$field]); if (!is_array($value) && !($value instanceof \Traversable && $value instanceof \ArrayAccess)) {
} throw new UnexpectedTypeException($value, 'array or Traversable and ArrayAccess');
else }
{
$missingFields[] = $field; $walker = $this->context->getGraphWalker();
} $group = $this->context->getGroup();
$propertyPath = $this->context->getPropertyPath();
$missingFields = array();
$extraFields = array();
foreach ($value as $field => $fieldValue) {
$extraFields[$field] = $fieldValue;
}
foreach ($constraint->fields as $field => $constraints) {
if (array_key_exists($field, $value)) {
// cannot simply cast to array, because then the object is converted to an
// array instead of wrapped inside
$constraints = is_array($constraints) ? $constraints : array($constraints);
foreach ($constraints as $constr) {
$walker->walkConstraint($constr, $value[$field], $group, $propertyPath.'['.$field.']');
}
unset($extraFields[$field]);
} else {
$missingFields[] = $field;
}
}
if (count($extraFields) > 0 && !$constraint->allowExtraFields) {
$this->setMessage($constraint->extraFieldsMessage, array(
'fields' => '"'.implode('", "', array_keys($extraFields)).'"'
));
return false;
}
if (count($missingFields) > 0 && !$constraint->allowMissingFields) {
$this->setMessage($constraint->missingFieldsMessage, array(
'fields' => '"'.implode('", "', $missingFields).'"'
));
return false;
}
return true;
} }
if (count($extraFields) > 0 && !$constraint->allowExtraFields)
{
$this->setMessage($constraint->extraFieldsMessage, array(
'fields' => '"'.implode('", "', array_keys($extraFields)).'"'
));
return false;
}
if (count($missingFields) > 0 && !$constraint->allowMissingFields)
{
$this->setMessage($constraint->missingFieldsMessage, array(
'fields' => '"'.implode('", "', $missingFields).'"'
));
return false;
}
return true;
}
} }

View File

@ -4,5 +4,5 @@ namespace Symfony\Components\Validator\Constraints;
class Date extends \Symfony\Components\Validator\Constraint class Date extends \Symfony\Components\Validator\Constraint
{ {
public $message = 'Symfony.Validator.Date.message'; public $message = 'Symfony.Validator.Date.message';
} }

View File

@ -4,5 +4,5 @@ namespace Symfony\Components\Validator\Constraints;
class DateTime extends \Symfony\Components\Validator\Constraint class DateTime extends \Symfony\Components\Validator\Constraint
{ {
public $message = 'Symfony.Validator.DateTime.message'; public $message = 'Symfony.Validator.DateTime.message';
} }

View File

@ -8,29 +8,26 @@ use Symfony\Components\Validator\Exception\UnexpectedTypeException;
class DateTimeValidator extends ConstraintValidator class DateTimeValidator extends ConstraintValidator
{ {
const PATTERN = '/^(\d{4})-(\d{2})-(\d{2}) (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/'; const PATTERN = '/^(\d{4})-(\d{2})-(\d{2}) (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/';
public function isValid($value, Constraint $constraint) public function isValid($value, Constraint $constraint)
{
if ($value === null)
{ {
return true; if ($value === null) {
return true;
}
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString()'))) {
throw new UnexpectedTypeException($value, 'string');
}
$value = (string)$value;
if (!preg_match(self::PATTERN, $value, $matches)) {
$this->setMessage($constraint->message, array('value' => $value));
return false;
}
return checkdate($matches[2], $matches[3], $matches[1]);
} }
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString()')))
{
throw new UnexpectedTypeException($value, 'string');
}
$value = (string)$value;
if (!preg_match(self::PATTERN, $value, $matches))
{
$this->setMessage($constraint->message, array('value' => $value));
return false;
}
return checkdate($matches[2], $matches[3], $matches[1]);
}
} }

View File

@ -8,29 +8,26 @@ use Symfony\Components\Validator\Exception\UnexpectedTypeException;
class DateValidator extends ConstraintValidator class DateValidator extends ConstraintValidator
{ {
const PATTERN = '/^(\d{4})-(\d{2})-(\d{2})$/'; const PATTERN = '/^(\d{4})-(\d{2})-(\d{2})$/';
public function isValid($value, Constraint $constraint) public function isValid($value, Constraint $constraint)
{
if ($value === null)
{ {
return true; if ($value === null) {
return true;
}
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString()'))) {
throw new UnexpectedTypeException($value, 'string');
}
$value = (string)$value;
if (!preg_match(self::PATTERN, $value, $matches)) {
$this->setMessage($constraint->message, array('value' => $value));
return false;
}
return checkdate($matches[2], $matches[3], $matches[1]);
} }
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString()')))
{
throw new UnexpectedTypeException($value, 'string');
}
$value = (string)$value;
if (!preg_match(self::PATTERN, $value, $matches))
{
$this->setMessage($constraint->message, array('value' => $value));
return false;
}
return checkdate($matches[2], $matches[3], $matches[1]);
}
} }

View File

@ -4,6 +4,6 @@ namespace Symfony\Components\Validator\Constraints;
class Email extends \Symfony\Components\Validator\Constraint class Email extends \Symfony\Components\Validator\Constraint
{ {
public $message = 'Symfony.Validator.Email.message'; public $message = 'Symfony.Validator.Email.message';
public $checkMX = false; public $checkMX = false;
} }

View File

@ -8,81 +8,71 @@ use Symfony\Components\Validator\Exception\UnexpectedTypeException;
class EmailValidator extends ConstraintValidator class EmailValidator extends ConstraintValidator
{ {
const PATTERN = '/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i'; const PATTERN = '/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i';
public function isValid($value, Constraint $constraint) public function isValid($value, Constraint $constraint)
{
if ($value === null)
{ {
return true; if ($value === null) {
} return true;
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString()')))
{
throw new UnexpectedTypeException($value, 'string');
}
$value = (string)$value;
if (!preg_match(self::PATTERN, $value))
{
$this->setMessage($constraint->message, array('value' => $value));
return false;
}
if ($constraint->checkMX)
{
$host = substr($value, strpos($value, '@'));
if (!$this->checkMX($host))
{
$this->setMessage($constraint->message, array('value' => $value));
return false;
}
}
return true;
}
/**
* Check DNA Records for MX type (from Doctrine EmailValidator)
*
* @param string $host Host name
* @return boolean
* @licence This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.org>.
*/
private function checkMX($host)
{
// We have different behavior here depending of OS and PHP version
if (strtolower(substr(PHP_OS, 0, 3)) == 'win' && version_compare(PHP_VERSION, '5.3.0', '<')) {
$output = array();
@exec('nslookup -type=MX '.escapeshellcmd($host) . ' 2>&1', $output);
if (empty($output))
{
throw new ValidatorError('Unable to execute DNS lookup. Are you sure PHP can call exec()?');
}
foreach ($output as $line)
{
if (preg_match('/^'.$host.'/', $line))
{
return true;
} }
}
return false; if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString()'))) {
throw new UnexpectedTypeException($value, 'string');
}
$value = (string)$value;
if (!preg_match(self::PATTERN, $value)) {
$this->setMessage($constraint->message, array('value' => $value));
return false;
}
if ($constraint->checkMX) {
$host = substr($value, strpos($value, '@'));
if (!$this->checkMX($host)) {
$this->setMessage($constraint->message, array('value' => $value));
return false;
}
}
return true;
} }
else if (function_exists('checkdnsrr'))
/**
* Check DNA Records for MX type (from Doctrine EmailValidator)
*
* @param string $host Host name
* @return boolean
* @licence This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.phpdoctrine.org>.
*/
private function checkMX($host)
{ {
return checkdnsrr($host, 'MX'); // We have different behavior here depending of OS and PHP version
} if (strtolower(substr(PHP_OS, 0, 3)) == 'win' && version_compare(PHP_VERSION, '5.3.0', '<')) {
$output = array();
throw new ValidatorError('Could not retrieve DNS record information. Remove check_mx = true to prevent this warning'); @exec('nslookup -type=MX '.escapeshellcmd($host) . ' 2>&1', $output);
}
if (empty($output)) {
throw new ValidatorError('Unable to execute DNS lookup. Are you sure PHP can call exec()?');
}
foreach ($output as $line) {
if (preg_match('/^'.$host.'/', $line)) {
return true;
}
}
return false;
} else if (function_exists('checkdnsrr')) {
return checkdnsrr($host, 'MX');
}
throw new ValidatorError('Could not retrieve DNS record information. Remove check_mx = true to prevent this warning');
}
} }

View File

@ -4,10 +4,10 @@ namespace Symfony\Components\Validator\Constraints;
class File extends \Symfony\Components\Validator\Constraint class File extends \Symfony\Components\Validator\Constraint
{ {
public $maxSize = null; public $maxSize = null;
public $mimeTypes = array(); public $mimeTypes = array();
public $notFoundMessage = 'Symfony.Validator.File.notFoundMessage'; public $notFoundMessage = 'Symfony.Validator.File.notFoundMessage';
public $notReadableMessage = 'Symfony.Validator.File.notReadableMessage'; public $notReadableMessage = 'Symfony.Validator.File.notReadableMessage';
public $maxSizeMessage = 'Symfony.Validator.File.maxSizeMessage'; public $maxSizeMessage = 'Symfony.Validator.File.maxSizeMessage';
public $mimeTypesMessage = 'Symfony.Validator.File.mimeTypesMessage'; public $mimeTypesMessage = 'Symfony.Validator.File.mimeTypesMessage';
} }

View File

@ -10,90 +10,74 @@ use Symfony\Components\File\File;
class FileValidator extends ConstraintValidator class FileValidator extends ConstraintValidator
{ {
public function isValid($value, Constraint $constraint) public function isValid($value, Constraint $constraint)
{
if ($value === null)
{ {
return true; if ($value === null) {
return true;
}
if (!is_scalar($value) && !$value instanceof File && !(is_object($value) && method_exists($value, '__toString()'))) {
throw new UnexpectedTypeException($value, 'string');
}
$path = $value instanceof File ? $value->getPath() : (string)$value;
if (!file_exists($path)) {
$this->setMessage($constraint->notFoundMessage, array('file' => $path));
return false;
}
if (!is_readable($path)) {
$this->setMessage($constraint->notReadableMessage, array('file' => $path));
return false;
}
if ($constraint->maxSize) {
if (ctype_digit((string)$constraint->maxSize)) {
$size = filesize($path);
$limit = $constraint->maxSize;
$suffix = ' bytes';
} else if (preg_match('/^(\d)k$/', $constraint->maxSize, $matches)) {
$size = round(filesize($path) / 1000, 2);
$limit = $matches[1];
$suffix = ' kB';
} else if (preg_match('/^(\d)M$/', $constraint->maxSize, $matches)) {
$size = round(filesize($path) / 1000000, 2);
$limit = $matches[1];
$suffix = ' MB';
} else {
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size', $constraint->maxSize));
}
if ($size > $limit) {
$this->setMessage($constraint->maxSizeMessage, array(
'size' => $size . $suffix,
'limit' => $limit . $suffix,
'file' => $path,
));
return false;
}
}
if ($constraint->mimeTypes) {
if (!$value instanceof File) {
throw new ConstraintValidationException();
}
if (!in_array($value->getMimeType(), (array)$constraint->mimeTypes)) {
$this->setMessage($constraint->mimeTypesMessage, array(
'type' => '"'.$value->getMimeType().'"',
'types' => '"'.implode('", "', (array)$constraint->mimeTypes).'"',
'file' => $path,
));
return false;
}
}
return true;
} }
if (!is_scalar($value) && !$value instanceof File && !(is_object($value) && method_exists($value, '__toString()')))
{
throw new UnexpectedTypeException($value, 'string');
}
$path = $value instanceof File ? $value->getPath() : (string)$value;
if (!file_exists($path))
{
$this->setMessage($constraint->notFoundMessage, array('file' => $path));
return false;
}
if (!is_readable($path))
{
$this->setMessage($constraint->notReadableMessage, array('file' => $path));
return false;
}
if ($constraint->maxSize)
{
if (ctype_digit((string)$constraint->maxSize))
{
$size = filesize($path);
$limit = $constraint->maxSize;
$suffix = ' bytes';
}
else if (preg_match('/^(\d)k$/', $constraint->maxSize, $matches))
{
$size = round(filesize($path) / 1000, 2);
$limit = $matches[1];
$suffix = ' kB';
}
else if (preg_match('/^(\d)M$/', $constraint->maxSize, $matches))
{
$size = round(filesize($path) / 1000000, 2);
$limit = $matches[1];
$suffix = ' MB';
}
else
{
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size', $constraint->maxSize));
}
if ($size > $limit)
{
$this->setMessage($constraint->maxSizeMessage, array(
'size' => $size . $suffix,
'limit' => $limit . $suffix,
'file' => $path,
));
return false;
}
}
if ($constraint->mimeTypes)
{
if (!$value instanceof File)
{
throw new ConstraintValidationException();
}
if (!in_array($value->getMimeType(), (array)$constraint->mimeTypes))
{
$this->setMessage($constraint->mimeTypesMessage, array(
'type' => '"'.$value->getMimeType().'"',
'types' => '"'.implode('", "', (array)$constraint->mimeTypes).'"',
'file' => $path,
));
return false;
}
}
return true;
}
} }

View File

@ -4,22 +4,22 @@ namespace Symfony\Components\Validator\Constraints;
class Max extends \Symfony\Components\Validator\Constraint class Max extends \Symfony\Components\Validator\Constraint
{ {
public $message = 'Symfony.Validator.Max.message'; public $message = 'Symfony.Validator.Max.message';
public $limit; public $limit;
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function defaultOption() public function defaultOption()
{ {
return 'limit'; return 'limit';
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function requiredOptions() public function requiredOptions()
{ {
return array('limit'); return array('limit');
} }
} }

View File

@ -4,23 +4,23 @@ namespace Symfony\Components\Validator\Constraints;
class MaxLength extends \Symfony\Components\Validator\Constraint class MaxLength extends \Symfony\Components\Validator\Constraint
{ {
public $message = 'Symfony.Validator.MaxLength.message'; public $message = 'Symfony.Validator.MaxLength.message';
public $limit; public $limit;
public $charset = 'UTF-8'; public $charset = 'UTF-8';
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function defaultOption() public function defaultOption()
{ {
return 'limit'; return 'limit';
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function requiredOptions() public function requiredOptions()
{ {
return array('limit'); return array('limit');
} }
} }

View File

@ -8,32 +8,29 @@ use Symfony\Components\Validator\Exception\UnexpectedTypeException;
class MaxLengthValidator extends ConstraintValidator class MaxLengthValidator extends ConstraintValidator
{ {
public function isValid($value, Constraint $constraint) public function isValid($value, Constraint $constraint)
{
if ($value === null)
{ {
return true; if ($value === null) {
return true;
}
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString()'))) {
throw new UnexpectedTypeException($value, 'string');
}
$value = (string)$value;
$length = function_exists('mb_strlen') ? mb_strlen($value, $constraint->charset) : strlen($value);
if ($length > $constraint->limit) {
$this->setMessage($constraint->message, array(
'value' => $value,
'limit' => $constraint->limit,
));
return false;
}
return true;
} }
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString()')))
{
throw new UnexpectedTypeException($value, 'string');
}
$value = (string)$value;
$length = function_exists('mb_strlen') ? mb_strlen($value, $constraint->charset) : strlen($value);
if ($length > $constraint->limit)
{
$this->setMessage($constraint->message, array(
'value' => $value,
'limit' => $constraint->limit,
));
return false;
}
return true;
}
} }

View File

@ -8,28 +8,25 @@ use Symfony\Components\Validator\Exception\UnexpectedTypeException;
class MaxValidator extends ConstraintValidator class MaxValidator extends ConstraintValidator
{ {
public function isValid($value, Constraint $constraint) public function isValid($value, Constraint $constraint)
{
if ($value === null)
{ {
return true; if ($value === null) {
return true;
}
if (!is_numeric($value)) {
throw new UnexpectedTypeException($value, 'numeric');
}
if ($value > $constraint->limit) {
$this->setMessage($constraint->message, array(
'value' => $value,
'limit' => $constraint->limit,
));
return false;
}
return true;
} }
if (!is_numeric($value))
{
throw new UnexpectedTypeException($value, 'numeric');
}
if ($value > $constraint->limit)
{
$this->setMessage($constraint->message, array(
'value' => $value,
'limit' => $constraint->limit,
));
return false;
}
return true;
}
} }

View File

@ -4,22 +4,22 @@ namespace Symfony\Components\Validator\Constraints;
class Min extends \Symfony\Components\Validator\Constraint class Min extends \Symfony\Components\Validator\Constraint
{ {
public $message = 'Symfony.Validator.Min.message'; public $message = 'Symfony.Validator.Min.message';
public $limit; public $limit;
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function defaultOption() public function defaultOption()
{ {
return 'limit'; return 'limit';
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function requiredOptions() public function requiredOptions()
{ {
return array('limit'); return array('limit');
} }
} }

View File

@ -4,23 +4,23 @@ namespace Symfony\Components\Validator\Constraints;
class MinLength extends \Symfony\Components\Validator\Constraint class MinLength extends \Symfony\Components\Validator\Constraint
{ {
public $message = 'Symfony.Validator.MinLength.message'; public $message = 'Symfony.Validator.MinLength.message';
public $limit; public $limit;
public $charset = 'UTF-8'; public $charset = 'UTF-8';
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function defaultOption() public function defaultOption()
{ {
return 'limit'; return 'limit';
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function requiredOptions() public function requiredOptions()
{ {
return array('limit'); return array('limit');
} }
} }

View File

@ -8,32 +8,29 @@ use Symfony\Components\Validator\Exception\UnexpectedTypeException;
class MinLengthValidator extends ConstraintValidator class MinLengthValidator extends ConstraintValidator
{ {
public function isValid($value, Constraint $constraint) public function isValid($value, Constraint $constraint)
{
if ($value === null)
{ {
return true; if ($value === null) {
return true;
}
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString()'))) {
throw new UnexpectedTypeException($value, 'string');
}
$value = (string)$value;
$length = function_exists('mb_strlen') ? mb_strlen($value, $constraint->charset) : strlen($value);
if ($length < $constraint->limit) {
$this->setMessage($constraint->message, array(
'value' => $value,
'limit' => $constraint->limit,
));
return false;
}
return true;
} }
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString()')))
{
throw new UnexpectedTypeException($value, 'string');
}
$value = (string)$value;
$length = function_exists('mb_strlen') ? mb_strlen($value, $constraint->charset) : strlen($value);
if ($length < $constraint->limit)
{
$this->setMessage($constraint->message, array(
'value' => $value,
'limit' => $constraint->limit,
));
return false;
}
return true;
}
} }

View File

@ -8,28 +8,25 @@ use Symfony\Components\Validator\Exception\UnexpectedTypeException;
class MinValidator extends ConstraintValidator class MinValidator extends ConstraintValidator
{ {
public function isValid($value, Constraint $constraint) public function isValid($value, Constraint $constraint)
{
if ($value === null)
{ {
return true; if ($value === null) {
return true;
}
if (!is_numeric($value)) {
throw new UnexpectedTypeException($value, 'numeric');
}
if ($value < $constraint->limit) {
$this->setMessage($constraint->message, array(
'value' => $value,
'limit' => $constraint->limit,
));
return false;
}
return true;
} }
if (!is_numeric($value))
{
throw new UnexpectedTypeException($value, 'numeric');
}
if ($value < $constraint->limit)
{
$this->setMessage($constraint->message, array(
'value' => $value,
'limit' => $constraint->limit,
));
return false;
}
return true;
}
} }

View File

@ -4,5 +4,5 @@ namespace Symfony\Components\Validator\Constraints;
class NotBlank extends \Symfony\Components\Validator\Constraint class NotBlank extends \Symfony\Components\Validator\Constraint
{ {
public $message = 'Symfony.Validator.NotBlank.message'; public $message = 'Symfony.Validator.NotBlank.message';
} }

Some files were not shown because too many files have changed in this diff Show More