Skip to content

[Form] Refactor guessing of form options and added min and max guessing #9759

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,24 @@ public function guessType($class, $property)
}
}

/**
* {@inheritDoc}
*/
public function guessAttributes($class, $property)
{
$attributes = array();

if ($guess = $this->guessMaxLength($class, $property)) {
$attributes['maxlength'] = $guess;
}

if ($guess = $this->guessPattern($class, $property)) {
$attributes['pattern'] = $guess;
}

return $attributes;
}

/**
* {@inheritDoc}
*/
Expand Down
18 changes: 18 additions & 0 deletions src/Symfony/Bridge/Propel1/Form/PropelTypeGuesser.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,24 @@ public function guessType($class, $property)
}
}

/**
* {@inheritDoc}
*/
public function guessAttributes($class, $property)
{
$attributes = array();

if ($guess = $this->guessMaxLength($class, $property)) {
$attributes['maxlength'] = $guess;
}

if ($guess = $this->guessPattern($class, $property)) {
$attributes['pattern'] = $guess;
}

return $attributes;
}

/**
* {@inheritDoc}
*/
Expand Down
23 changes: 12 additions & 11 deletions src/Symfony/Bridge/Propel1/Tests/Form/PropelTypeGuesserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,33 +30,34 @@ public function setUp()

public function testGuessMaxLengthWithText()
{
$value = $this->guesser->guessMaxLength(self::CLASS_NAME, 'value');
$attributes = $this->guesser->guessAttributes(self::CLASS_NAME, 'value');

$this->assertNotNull($value);
$this->assertEquals(255, $value->getValue());
$this->assertArrayHasKey('maxlength', $attributes);
$this->assertEquals(255, $attributes['maxlength']->getValue());
}

public function testGuessMaxLengthWithFloat()
{
$value = $this->guesser->guessMaxLength(self::CLASS_NAME, 'price');
$attributes = $this->guesser->guessAttributes(self::CLASS_NAME, 'price');

$this->assertNotNull($value);
$this->assertNull($value->getValue());
$this->assertArrayHasKey('maxlength', $attributes);
$this->assertNull($attributes['maxlength']->getValue());
}

public function testGuessMinLengthWithText()
{
$value = $this->guesser->guessPattern(self::CLASS_NAME, 'value');
$attributes = $this->guesser->guessAttributes(self::CLASS_NAME, 'price');

$this->assertNull($value);
$this->assertArrayHasKey('maxlength', $attributes);
$this->assertNull($attributes['maxlength']->getValue());
}

public function testGuessMinLengthWithFloat()
{
$value = $this->guesser->guessPattern(self::CLASS_NAME, 'price');
$attributes = $this->guesser->guessAttributes(self::CLASS_NAME, 'price');

$this->assertNotNull($value);
$this->assertNull($value->getValue());
$this->assertArrayHasKey('maxlength', $attributes);
$this->assertNull($attributes['maxlength']->getValue());
}

public function testGuessRequired()
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Form/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ CHANGELOG
* [BC BREAK] added two optional parameters to FormInterface::getErrors() and
changed the method to return a Symfony\Component\Form\FormErrorIterator
instance instead of an array
* added the guessing of "min" and "max" attributes

2.4.0
-----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,9 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
'empty_data' => $emptyData,
'trim' => true,
'required' => true,
'read_only' => false,
'max_length' => null,
'pattern' => null,
'read_only' => false,
'property_path' => null,
'mapped' => true,
'by_reference' => true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Symfony\Component\Form\Guess\ValueGuess;
use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Range;

class ValidatorTypeGuesser implements FormTypeGuesserInterface
{
Expand All @@ -39,6 +40,32 @@ public function guessType($class, $property)
});
}

/**
* {@inheritDoc}
*/
public function guessAttributes($class, $property)
{
$attributes = array();

if ($guess = $this->guessMaxLength($class, $property)) {
$attributes['maxlength'] = $guess;
}

if ($guess = $this->guessMinValue($class, $property)) {
$attributes['min'] = $guess;
}

if ($guess = $this->guessMaxValue($class, $property)) {
$attributes['max'] = $guess;
}

if ($guess = $this->guessPattern($class, $property)) {
$attributes['pattern'] = $guess;
}

return $attributes;
}

/**
* {@inheritDoc}
*/
Expand All @@ -65,6 +92,40 @@ public function guessMaxLength($class, $property)
});
}

/**
* Returns a guess about the field's maximum value
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
*
* @return Guess\ValueGuess|null A guess for the field's maximum value
*/
public function guessMaxValue($class, $property)
{
$guesser = $this;

return $this->guess($class, $property, function (Constraint $constraint) use ($guesser) {
return $guesser->guessMaxValueForConstraint($constraint);
});
}

/**
* Returns a guess about the field's minimum value
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
*
* @return Guess\ValueGuess|null A guess for the field's minimum value
*/
public function guessMinValue($class, $property)
{
$guesser = $this;

return $this->guess($class, $property, function (Constraint $constraint) use ($guesser) {
return $guesser->guessMinValueForConstraint($constraint);
});
}

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -214,6 +275,38 @@ public function guessMaxLengthForConstraint(Constraint $constraint)
return null;
}

/**
* Guesses a field's maximum value based on the given constraint
*
* @param Constraint $constraint The constraint to guess for
*
* @return ValueGuess|null The guess for the maximum value
*/
public function guessMaxValueForConstraint(Constraint $constraint)
{
if ($constraint instanceof Range && is_numeric($constraint->max)) {
return new ValueGuess($constraint->max, Guess::HIGH_CONFIDENCE);
}

return null;
}

/**
* Guesses a field's minimum value based on the given constraint
*
* @param Constraint $constraint The constraint to guess for
*
* @return ValueGuess|null The guess for the minimum value
*/
public function guessMinValueForConstraint(Constraint $constraint)
{
if ($constraint instanceof Range && is_numeric($constraint->min)) {
return new ValueGuess($constraint->min, Guess::HIGH_CONFIDENCE);
}

return null;
}

/**
* Guesses a field's pattern based on the given constraint
*
Expand Down
50 changes: 35 additions & 15 deletions src/Symfony/Component/Form/FormFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,25 @@ class FormFactory implements FormFactoryInterface
*/
private $resolvedTypeFactory;

protected $supportedAttributes = array(
// rendered as input[type=text]
'text' => array('maxlength', 'pattern'),
'email' => array('maxlength', 'pattern'),
'money' => array('maxlength', 'pattern'),
'number' => array('maxlength', 'pattern'),
'percent' => array('maxlength', 'pattern'),
// rendered as input[type=number]
'integer' => array('max', 'min'),
// rendered as input[type=password]
'password' => array('maxlength', 'pattern'),
// rendered as input[type=search]
'search' => array('maxlength', 'pattern'),
// rendered as input[type=url]
'url' => array('maxlength', 'pattern'),
// rendered as textarea
'textarea' => array('maxlength'),
);

public function __construct(FormRegistryInterface $registry, ResolvedFormTypeFactoryInterface $resolvedTypeFactory)
{
$this->registry = $registry;
Expand Down Expand Up @@ -103,24 +122,11 @@ public function createBuilderForProperty($class, $property, $data = null, array
}

$typeGuess = $guesser->guessType($class, $property);
$maxLengthGuess = $guesser->guessMaxLength($class, $property);
$requiredGuess = $guesser->guessRequired($class, $property);
$patternGuess = $guesser->guessPattern($class, $property);
$guessedAttributes = $guesser->guessAttributes($class, $property);

$type = $typeGuess ? $typeGuess->getType() : 'text';

$maxLength = $maxLengthGuess ? $maxLengthGuess->getValue() : null;
$pattern = $patternGuess ? $patternGuess->getValue() : null;

if (null !== $pattern) {
$options = array_merge(array('attr' => array('pattern' => $pattern)), $options);
}

if (null !== $maxLength) {
$options = array_merge(array('attr' => array('maxlength' => $maxLength)), $options);
}

if ($requiredGuess) {
if ($requiredGuess = $guesser->guessRequired($class, $property)) {
$options = array_merge(array('required' => $requiredGuess->getValue()), $options);
}

Expand All @@ -129,6 +135,20 @@ public function createBuilderForProperty($class, $property, $data = null, array
$options = array_merge($typeGuess->getOptions(), $options);
}

$filteredAttributes = array();

foreach ($guessedAttributes as $key => $value) {
if (null !== $value->getValue() && isset($this->supportedAttributes[$type]) && in_array($key, $this->supportedAttributes[$type])) {
$filteredAttributes[$key] = $value->getValue();
}
}

if (!empty($filteredAttributes)) {
$options = array_merge(array(
'attr' => $filteredAttributes
), $options);
}

return $this->createNamedBuilder($property, $type, $data, $options);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/Form/FormFactoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public function createNamedBuilder($name, $type = 'form', $data = null, array $o
/**
* Returns a form builder for a property of a class.
*
* If any of the 'max_length', 'required' and type options can be guessed,
* If any of the 'required' and type options can be guessed,
* and are not provided in the options argument, the guessed value is used.
*
* @param string $class The fully qualified class name
Expand Down
27 changes: 27 additions & 0 deletions src/Symfony/Component/Form/FormTypeGuesserChain.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,33 @@ public function guessPattern($class, $property)
});
}

/**
* {@inheritDoc}
*/
public function guessAttributes($class, $property)
{
$attributes = array();

// Cleanup attributes if I get the same attribute from different guessers.
foreach ($this->guessers as $guesser) {
$guessedAttributes = $guesser->guessAttributes($class, $property);

if (!is_array($guessedAttributes)) {
continue;
}

foreach ($guessedAttributes as $key => $value) {
if (isset($attributes[$key])) {
$attributes[$key] = Guess::getBestGuess(array($attributes[$key], $value));
} else {
$attributes[$key] = $value;
}
}
}

return $attributes;
}

/**
* Executes a closure for each guesser and returns the best guess from the
* return values
Expand Down
15 changes: 15 additions & 0 deletions src/Symfony/Component/Form/FormTypeGuesserInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public function guessRequired($class, $property);
* @param string $property The name of the property to guess for
*
* @return Guess\ValueGuess|null A guess for the field's maximum length
*
* @deprecated since 2.5, to be removed in 3.0. Use guessAttributes() instead.
*/
public function guessMaxLength($class, $property);

Expand All @@ -59,6 +61,19 @@ public function guessMaxLength($class, $property);
* @param string $property The name of the property to guess for
*
* @return Guess\ValueGuess|null A guess for the field's required pattern
*
* @deprecated since 2.5, to be removed in 3.0. Use guessAttributes() instead.
*/
public function guessPattern($class, $property);

/**
* Returns an array of guessed attributes
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @param ResolvedFormTypeInterface $type Field's type
*
* @return Guess\ValueGuess[] An array of guesses for the field's attributes
*/
public function guessAttributes($class, $property);
}
Loading