From 4f5b44bd3bd490309eb2ba7b44df4769816ba729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= Date: Sat, 3 Aug 2013 19:26:54 +0200 Subject: twig implementation --- .../Form/Extension/Validator/Constraints/Form.php | 33 +++ .../Validator/Constraints/FormValidator.php | 236 ++++++++++++++++ .../Validator/EventListener/ValidationListener.php | 68 +++++ .../Validator/Type/BaseValidatorExtension.php | 56 ++++ .../Validator/Type/FormTypeValidatorExtension.php | 84 ++++++ .../Type/RepeatedTypeValidatorExtension.php | 45 ++++ .../Type/SubmitTypeValidatorExtension.php | 28 ++ .../Form/Extension/Validator/Util/ServerParams.php | 64 +++++ .../Extension/Validator/ValidatorExtension.php | 57 ++++ .../Extension/Validator/ValidatorTypeGuesser.php | 286 ++++++++++++++++++++ .../Validator/ViolationMapper/MappingRule.php | 106 ++++++++ .../Validator/ViolationMapper/RelativePath.php | 45 ++++ .../Validator/ViolationMapper/ViolationMapper.php | 299 +++++++++++++++++++++ .../ViolationMapper/ViolationMapperInterface.php | 33 +++ .../Validator/ViolationMapper/ViolationPath.php | 250 +++++++++++++++++ .../ViolationMapper/ViolationPathIterator.php | 30 +++ 16 files changed, 1720 insertions(+) create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/Form.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Util/ServerParams.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/RelativePath.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php (limited to 'vendor/symfony/form/Symfony/Component/Form/Extension/Validator') diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/Form.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/Form.php new file mode 100644 index 00000000..87e33297 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/Form.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @author Bernhard Schussek + */ +class Form extends Constraint +{ + /** + * Violation code marking an invalid form. + */ + const ERR_INVALID = 1; + + /** + * {@inheritdoc} + */ + public function getTargets() + { + return self::CLASS_CONSTRAINT; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php new file mode 100644 index 00000000..bad5a007 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -0,0 +1,236 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Constraints; + +use Symfony\Component\Form\ClickableInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\Extension\Validator\Util\ServerParams; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; + +/** + * @author Bernhard Schussek + */ +class FormValidator extends ConstraintValidator +{ + /** + * @var ServerParams + */ + private $serverParams; + + /** + * Creates a validator with the given server parameters. + * + * @param ServerParams $params The server parameters. Default + * parameters are created if null. + */ + public function __construct(ServerParams $params = null) + { + $this->serverParams = $params ?: new ServerParams(); + } + + /** + * {@inheritdoc} + */ + public function validate($form, Constraint $constraint) + { + if (!$form instanceof FormInterface) { + return; + } + + /* @var FormInterface $form */ + $config = $form->getConfig(); + + if ($form->isSynchronized()) { + // Validate the form data only if transformation succeeded + $groups = self::getValidationGroups($form); + + // Validate the data against its own constraints + if (self::allowDataWalking($form)) { + foreach ($groups as $group) { + $this->context->validate($form->getData(), 'data', $group, true); + } + } + + // Validate the data against the constraints defined + // in the form + $constraints = $config->getOption('constraints'); + foreach ($constraints as $constraint) { + foreach ($groups as $group) { + if (in_array($group, $constraint->groups)) { + $this->context->validateValue($form->getData(), $constraint, 'data', $group); + + // Prevent duplicate validation + continue 2; + } + } + } + } else { + $childrenSynchronized = true; + + foreach ($form as $child) { + if (!$child->isSynchronized()) { + $childrenSynchronized = false; + break; + } + } + + // Mark the form with an error if it is not synchronized BUT all + // of its children are synchronized. If any child is not + // synchronized, an error is displayed there already and showing + // a second error in its parent form is pointless, or worse, may + // lead to duplicate errors if error bubbling is enabled on the + // child. + // See also https://github.com/symfony/symfony/issues/4359 + if ($childrenSynchronized) { + $clientDataAsString = is_scalar($form->getViewData()) + ? (string) $form->getViewData() + : gettype($form->getViewData()); + + $this->context->addViolation( + $config->getOption('invalid_message'), + array_replace(array('{{ value }}' => $clientDataAsString), $config->getOption('invalid_message_parameters')), + $form->getViewData(), + null, + Form::ERR_INVALID + ); + } + } + + // Mark the form with an error if it contains extra fields + if (count($form->getExtraData()) > 0) { + $this->context->addViolation( + $config->getOption('extra_fields_message'), + array('{{ extra_fields }}' => implode('", "', array_keys($form->getExtraData()))), + $form->getExtraData() + ); + } + + // Mark the form with an error if the uploaded size was too large + $length = $this->serverParams->getContentLength(); + + if ($form->isRoot() && null !== $length) { + $max = $this->serverParams->getPostMaxSize(); + + if (!empty($max) && $length > $max) { + $this->context->addViolation( + $config->getOption('post_max_size_message'), + array('{{ max }}' => $this->serverParams->getNormalizedIniPostMaxSize()), + $length + ); + } + } + } + + /** + * Returns whether the data of a form may be walked. + * + * @param FormInterface $form The form to test. + * + * @return Boolean Whether the graph walker may walk the data. + */ + private static function allowDataWalking(FormInterface $form) + { + $data = $form->getData(); + + // Scalar values cannot have mapped constraints + if (!is_object($data) && !is_array($data)) { + return false; + } + + // Root forms are always validated + if ($form->isRoot()) { + return true; + } + + // Non-root forms are validated if validation cascading + // is enabled in all ancestor forms + while (null !== ($form = $form->getParent())) { + if (!$form->getConfig()->getOption('cascade_validation')) { + return false; + } + } + + return true; + } + + /** + * Returns the validation groups of the given form. + * + * @param FormInterface $form The form. + * + * @return array The validation groups. + */ + private static function getValidationGroups(FormInterface $form) + { + $button = self::findClickedButton($form->getRoot()); + + if (null !== $button) { + $groups = $button->getConfig()->getOption('validation_groups'); + + if (null !== $groups) { + return self::resolveValidationGroups($groups, $form); + } + } + + do { + $groups = $form->getConfig()->getOption('validation_groups'); + + if (null !== $groups) { + return self::resolveValidationGroups($groups, $form); + } + + $form = $form->getParent(); + } while (null !== $form); + + return array(Constraint::DEFAULT_GROUP); + } + + /** + * Extracts a clicked button from a form tree, if one exists. + * + * @param FormInterface $form The root form. + * + * @return ClickableInterface|null The clicked button or null. + */ + private static function findClickedButton(FormInterface $form) + { + if ($form instanceof ClickableInterface && $form->isClicked()) { + return $form; + } + + foreach ($form as $child) { + if (null !== ($button = self::findClickedButton($child))) { + return $button; + } + } + + return null; + } + + /** + * Post-processes the validation groups option for a given form. + * + * @param array|callable $groups The validation groups. + * @param FormInterface $form The validated form. + * + * @return array The validation groups. + */ + private static function resolveValidationGroups($groups, FormInterface $form) + { + if (!is_string($groups) && is_callable($groups)) { + $groups = call_user_func($groups, $form); + } + + return (array) $groups; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php new file mode 100644 index 00000000..14147531 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapperInterface; +use Symfony\Component\Validator\ValidatorInterface; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\Extension\Validator\Constraints\Form; + +/** + * @author Bernhard Schussek + */ +class ValidationListener implements EventSubscriberInterface +{ + private $validator; + + private $violationMapper; + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array(FormEvents::POST_SUBMIT => 'validateForm'); + } + + public function __construct(ValidatorInterface $validator, ViolationMapperInterface $violationMapper) + { + $this->validator = $validator; + $this->violationMapper = $violationMapper; + } + + /** + * Validates the form and its domain object. + * + * @param FormEvent $event The event object + */ + public function validateForm(FormEvent $event) + { + $form = $event->getForm(); + + if ($form->isRoot()) { + // Validate the form in group "Default" + $violations = $this->validator->validate($form); + + if (count($violations) > 0) { + foreach ($violations as $violation) { + // Allow the "invalid" constraint to be put onto + // non-synchronized forms + $allowNonSynchronized = Form::ERR_INVALID === $violation->getCode(); + + $this->violationMapper->mapViolation($violation, $form, $allowNonSynchronized); + } + } + } + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php new file mode 100644 index 00000000..7c5e6784 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Type; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolverInterface; + +/** + * Encapsulates common logic of {@link FormTypeValidatorExtension} and + * {@link SubmitTypeValidatorExtension}. + * + * @author Bernhard Schussek + */ +abstract class BaseValidatorExtension extends AbstractTypeExtension +{ + /** + * {@inheritdoc} + */ + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + // Make sure that validation groups end up as null, closure or array + $validationGroupsNormalizer = function (Options $options, $groups) { + if (false === $groups) { + return array(); + } + + if (empty($groups)) { + return null; + } + + if (is_callable($groups)) { + return $groups; + } + + return (array) $groups; + }; + + $resolver->setDefaults(array( + 'validation_groups' => null, + )); + + $resolver->setNormalizers(array( + 'validation_groups' => $validationGroupsNormalizer, + )); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php new file mode 100644 index 00000000..344bddad --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Type; + +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapper; +use Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener; +use Symfony\Component\Validator\ValidatorInterface; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolverInterface; + +/** + * @author Bernhard Schussek + */ +class FormTypeValidatorExtension extends BaseValidatorExtension +{ + /** + * @var ValidatorInterface + */ + private $validator; + + /** + * @var ViolationMapper + */ + private $violationMapper; + + public function __construct(ValidatorInterface $validator) + { + $this->validator = $validator; + $this->violationMapper = new ViolationMapper(); + } + + /** + * {@inheritdoc} + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->addEventSubscriber(new ValidationListener($this->validator, $this->violationMapper)); + } + + /** + * {@inheritdoc} + */ + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + parent::setDefaultOptions($resolver); + + // Constraint should always be converted to an array + $constraintsNormalizer = function (Options $options, $constraints) { + return is_object($constraints) ? array($constraints) : (array) $constraints; + }; + + $resolver->setDefaults(array( + 'error_mapping' => array(), + 'constraints' => array(), + 'cascade_validation' => false, + 'invalid_message' => 'This value is not valid.', + 'invalid_message_parameters' => array(), + 'extra_fields_message' => 'This form should not contain extra fields.', + 'post_max_size_message' => 'The uploaded file was too large. Please try to upload a smaller file.', + )); + + $resolver->setNormalizers(array( + 'constraints' => $constraintsNormalizer, + )); + } + + /** + * {@inheritdoc} + */ + public function getExtendedType() + { + return 'form'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php new file mode 100644 index 00000000..858ff0fa --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Type; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolverInterface; + +/** + * @author Bernhard Schussek + */ +class RepeatedTypeValidatorExtension extends AbstractTypeExtension +{ + /** + * {@inheritdoc} + */ + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + // Map errors to the first field + $errorMapping = function (Options $options) { + return array('.' => $options['first_name']); + }; + + $resolver->setDefaults(array( + 'error_mapping' => $errorMapping, + )); + } + + /** + * {@inheritdoc} + */ + public function getExtendedType() + { + return 'repeated'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php new file mode 100644 index 00000000..5aad67fb --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Type; + +use Symfony\Component\Form\AbstractTypeExtension; + +/** + * @author Bernhard Schussek + */ +class SubmitTypeValidatorExtension extends AbstractTypeExtension +{ + /** + * {@inheritdoc} + */ + public function getExtendedType() + { + return 'submit'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Util/ServerParams.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Util/ServerParams.php new file mode 100644 index 00000000..fab6ac2a --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Util/ServerParams.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\Util; + +/** + * @author Bernhard Schussek + */ +class ServerParams +{ + /** + * Returns maximum post size in bytes. + * + * @return null|integer The maximum post size in bytes + */ + public function getPostMaxSize() + { + $iniMax = $this->getNormalizedIniPostMaxSize(); + + if ('' === $iniMax) { + return null; + } + + if (preg_match('#^\+?(0X?)?(.*?)([KMG]?)$#', $iniMax, $match)) { + $shifts = array('' => 0, 'K' => 10, 'M' => 20, 'G' => 30); + $bases = array('' => 10, '0' => 8, '0X' => 16); + + return intval($match[2], $bases[$match[1]]) << $shifts[$match[3]]; + } + + return 0; + } + + /** + * Returns the normalized "post_max_size" ini setting. + * + * @return string + */ + public function getNormalizedIniPostMaxSize() + { + return strtoupper(trim(ini_get('post_max_size'))); + } + + /** + * Returns the content length of the request. + * + * @return mixed The request content length. + */ + public function getContentLength() + { + return isset($_SERVER['CONTENT_LENGTH']) + ? (int) $_SERVER['CONTENT_LENGTH'] + : null; + } + +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php new file mode 100644 index 00000000..9cff22a2 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator; + +use Symfony\Component\Form\Extension\Validator\Type; +use Symfony\Component\Form\Extension\Validator\Constraints\Form; +use Symfony\Component\Form\AbstractExtension; +use Symfony\Component\Validator\ValidatorInterface; +use Symfony\Component\Validator\Constraints\Valid; + +/** + * Extension supporting the Symfony2 Validator component in forms. + * + * @author Bernhard Schussek + */ +class ValidatorExtension extends AbstractExtension +{ + private $validator; + + public function __construct(ValidatorInterface $validator) + { + $this->validator = $validator; + + // Register the form constraints in the validator programmatically. + // This functionality is required when using the Form component without + // the DIC, where the XML file is loaded automatically. Thus the following + // code must be kept synchronized with validation.xml + + /** @var \Symfony\Component\Validator\Mapping\ClassMetadata $metadata */ + $metadata = $this->validator->getMetadataFactory()->getMetadataFor('Symfony\Component\Form\Form'); + $metadata->addConstraint(new Form()); + $metadata->addPropertyConstraint('children', new Valid()); + } + + public function loadTypeGuesser() + { + return new ValidatorTypeGuesser($this->validator->getMetadataFactory()); + } + + protected function loadTypeExtensions() + { + return array( + new Type\FormTypeValidatorExtension($this->validator), + new Type\RepeatedTypeValidatorExtension(), + new Type\SubmitTypeValidatorExtension(), + ); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php new file mode 100644 index 00000000..dcd9cc55 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php @@ -0,0 +1,286 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator; + +use Symfony\Component\Form\FormTypeGuesserInterface; +use Symfony\Component\Form\Guess\Guess; +use Symfony\Component\Form\Guess\TypeGuess; +use Symfony\Component\Form\Guess\ValueGuess; +use Symfony\Component\Validator\MetadataFactoryInterface; +use Symfony\Component\Validator\Constraint; + +class ValidatorTypeGuesser implements FormTypeGuesserInterface +{ + private $metadataFactory; + + public function __construct(MetadataFactoryInterface $metadataFactory) + { + $this->metadataFactory = $metadataFactory; + } + + /** + * {@inheritDoc} + */ + public function guessType($class, $property) + { + $guesser = $this; + + return $this->guess($class, $property, function (Constraint $constraint) use ($guesser) { + return $guesser->guessTypeForConstraint($constraint); + }); + } + + /** + * {@inheritDoc} + */ + public function guessRequired($class, $property) + { + $guesser = $this; + + return $this->guess($class, $property, function (Constraint $constraint) use ($guesser) { + return $guesser->guessRequiredForConstraint($constraint); + // If we don't find any constraint telling otherwise, we can assume + // that a field is not required (with LOW_CONFIDENCE) + }, false); + } + + /** + * {@inheritDoc} + */ + public function guessMaxLength($class, $property) + { + $guesser = $this; + + return $this->guess($class, $property, function (Constraint $constraint) use ($guesser) { + return $guesser->guessMaxLengthForConstraint($constraint); + }); + } + + /** + * {@inheritDoc} + */ + public function guessPattern($class, $property) + { + $guesser = $this; + + return $this->guess($class, $property, function (Constraint $constraint) use ($guesser) { + return $guesser->guessPatternForConstraint($constraint); + }); + } + + /** + * Guesses a field class name for a given constraint + * + * @param Constraint $constraint The constraint to guess for + * + * @return TypeGuess The guessed field class and options + */ + public function guessTypeForConstraint(Constraint $constraint) + { + switch (get_class($constraint)) { + case 'Symfony\Component\Validator\Constraints\Type': + switch ($constraint->type) { + case 'array': + return new TypeGuess('collection', array(), Guess::MEDIUM_CONFIDENCE); + case 'boolean': + case 'bool': + return new TypeGuess('checkbox', array(), Guess::MEDIUM_CONFIDENCE); + + case 'double': + case 'float': + case 'numeric': + case 'real': + return new TypeGuess('number', array(), Guess::MEDIUM_CONFIDENCE); + + case 'integer': + case 'int': + case 'long': + return new TypeGuess('integer', array(), Guess::MEDIUM_CONFIDENCE); + + case '\DateTime': + return new TypeGuess('date', array(), Guess::MEDIUM_CONFIDENCE); + + case 'string': + return new TypeGuess('text', array(), Guess::LOW_CONFIDENCE); + } + break; + + case 'Symfony\Component\Validator\Constraints\Country': + return new TypeGuess('country', array(), Guess::HIGH_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\Date': + return new TypeGuess('date', array('input' => 'string'), Guess::HIGH_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\DateTime': + return new TypeGuess('datetime', array('input' => 'string'), Guess::HIGH_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\Email': + return new TypeGuess('email', array(), Guess::HIGH_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\File': + case 'Symfony\Component\Validator\Constraints\Image': + return new TypeGuess('file', array(), Guess::HIGH_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\Language': + return new TypeGuess('language', array(), Guess::HIGH_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\Locale': + return new TypeGuess('locale', array(), Guess::HIGH_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\Time': + return new TypeGuess('time', array('input' => 'string'), Guess::HIGH_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\Url': + return new TypeGuess('url', array(), Guess::HIGH_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\Ip': + return new TypeGuess('text', array(), Guess::MEDIUM_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\MaxLength': + case 'Symfony\Component\Validator\Constraints\MinLength': + case 'Symfony\Component\Validator\Constraints\Regex': + return new TypeGuess('text', array(), Guess::LOW_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\Min': + case 'Symfony\Component\Validator\Constraints\Max': + return new TypeGuess('number', array(), Guess::LOW_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\MinCount': + case 'Symfony\Component\Validator\Constraints\MaxCount': + return new TypeGuess('collection', array(), Guess::LOW_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\True': + case 'Symfony\Component\Validator\Constraints\False': + return new TypeGuess('checkbox', array(), Guess::MEDIUM_CONFIDENCE); + } + + return null; + } + + /** + * Guesses whether a field is required based on the given constraint + * + * @param Constraint $constraint The constraint to guess for + * + * @return Guess The guess whether the field is required + */ + public function guessRequiredForConstraint(Constraint $constraint) + { + switch (get_class($constraint)) { + case 'Symfony\Component\Validator\Constraints\NotNull': + case 'Symfony\Component\Validator\Constraints\NotBlank': + case 'Symfony\Component\Validator\Constraints\True': + return new ValueGuess(true, Guess::HIGH_CONFIDENCE); + } + + return null; + } + + /** + * Guesses a field's maximum length based on the given constraint + * + * @param Constraint $constraint The constraint to guess for + * + * @return Guess The guess for the maximum length + */ + public function guessMaxLengthForConstraint(Constraint $constraint) + { + switch (get_class($constraint)) { + case 'Symfony\Component\Validator\Constraints\MaxLength': + return new ValueGuess($constraint->limit, Guess::HIGH_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\Type': + if (in_array($constraint->type, array('double', 'float', 'numeric', 'real'))) { + return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE); + } + break; + + case 'Symfony\Component\Validator\Constraints\Max': + return new ValueGuess(strlen((string) $constraint->limit), Guess::LOW_CONFIDENCE); + } + + return null; + } + + /** + * Guesses a field's pattern based on the given constraint + * + * @param Constraint $constraint The constraint to guess for + * + * @return Guess The guess for the pattern + */ + public function guessPatternForConstraint(Constraint $constraint) + { + switch (get_class($constraint)) { + case 'Symfony\Component\Validator\Constraints\MinLength': + return new ValueGuess(sprintf('.{%s,}', (string) $constraint->limit), Guess::LOW_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\Regex': + $htmlPattern = $constraint->getHtmlPattern(); + + if (null !== $htmlPattern) { + return new ValueGuess($htmlPattern, Guess::HIGH_CONFIDENCE); + } + break; + + case 'Symfony\Component\Validator\Constraints\Min': + return new ValueGuess(sprintf('.{%s,}', strlen((string) $constraint->limit)), Guess::LOW_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\Type': + if (in_array($constraint->type, array('double', 'float', 'numeric', 'real'))) { + return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE); + } + break; + } + + return null; + } + + /** + * Iterates over the constraints of a property, executes a constraints on + * them and returns the best guess + * + * @param string $class The class to read the constraints from + * @param string $property The property for which to find constraints + * @param \Closure $closure The closure that returns a guess + * for a given constraint + * @param mixed $defaultValue The default value assumed if no other value + * can be guessed. + * + * @return Guess The guessed value with the highest confidence + */ + protected function guess($class, $property, \Closure $closure, $defaultValue = null) + { + $guesses = array(); + $classMetadata = $this->metadataFactory->getMetadataFor($class); + + if ($classMetadata->hasMemberMetadatas($property)) { + $memberMetadatas = $classMetadata->getMemberMetadatas($property); + + foreach ($memberMetadatas as $memberMetadata) { + $constraints = $memberMetadata->getConstraints(); + + foreach ($constraints as $constraint) { + if ($guess = $closure($constraint)) { + $guesses[] = $guess; + } + } + } + + if (null !== $defaultValue) { + $guesses[] = new ValueGuess($defaultValue, Guess::LOW_CONFIDENCE); + } + } + + return Guess::getBestGuess($guesses); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php new file mode 100644 index 00000000..7b96efb4 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; + +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\Exception\ErrorMappingException; + +/** + * @author Bernhard Schussek + */ +class MappingRule +{ + /** + * @var FormInterface + */ + private $origin; + + /** + * @var string + */ + private $propertyPath; + + /** + * @var string + */ + private $targetPath; + + public function __construct(FormInterface $origin, $propertyPath, $targetPath) + { + $this->origin = $origin; + $this->propertyPath = $propertyPath; + $this->targetPath = $targetPath; + } + + /** + * @return FormInterface + */ + public function getOrigin() + { + return $this->origin; + } + + /** + * Matches a property path against the rule path. + * + * If the rule matches, the form mapped by the rule is returned. + * Otherwise this method returns false. + * + * @param string $propertyPath The property path to match against the rule. + * + * @return null|FormInterface The mapped form or null. + */ + public function match($propertyPath) + { + if ($propertyPath === (string) $this->propertyPath) { + return $this->getTarget(); + } + + return null; + } + + /** + * Matches a property path against a prefix of the rule path. + * + * @param string $propertyPath The property path to match against the rule. + * + * @return Boolean Whether the property path is a prefix of the rule or not. + */ + public function isPrefix($propertyPath) + { + $length = strlen($propertyPath); + $prefix = substr($this->propertyPath, 0, $length); + $next = isset($this->propertyPath[$length]) ? $this->propertyPath[$length] : null; + + return $prefix === $propertyPath && ('[' === $next || '.' === $next); + } + + /** + * @return FormInterface + * + * @throws ErrorMappingException + */ + public function getTarget() + { + $childNames = explode('.', $this->targetPath); + $target = $this->origin; + + foreach ($childNames as $childName) { + if (!$target->has($childName)) { + throw new ErrorMappingException(sprintf('The child "%s" of "%s" mapped by the rule "%s" in "%s" does not exist.', $childName, $target->getName(), $this->targetPath, $this->origin->getName())); + } + $target = $target->get($childName); + } + + return $target; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/RelativePath.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/RelativePath.php new file mode 100644 index 00000000..ef5c9fad --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/RelativePath.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; + +use Symfony\Component\Form\FormInterface; +use Symfony\Component\PropertyAccess\PropertyPath; + +/** + * @author Bernhard Schussek + */ +class RelativePath extends PropertyPath +{ + /** + * @var FormInterface + */ + private $root; + + /** + * @param FormInterface $root + * @param string $propertyPath + */ + public function __construct(FormInterface $root, $propertyPath) + { + parent::__construct($propertyPath); + + $this->root = $root; + } + + /** + * @return FormInterface + */ + public function getRoot() + { + return $this->root; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php new file mode 100644 index 00000000..8a7636c7 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php @@ -0,0 +1,299 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; + +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\Util\InheritDataAwareIterator; +use Symfony\Component\PropertyAccess\PropertyPathIterator; +use Symfony\Component\PropertyAccess\PropertyPathBuilder; +use Symfony\Component\PropertyAccess\PropertyPathIteratorInterface; +use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationPathIterator; +use Symfony\Component\Form\FormError; +use Symfony\Component\Validator\ConstraintViolation; + +/** + * @author Bernhard Schussek + */ +class ViolationMapper implements ViolationMapperInterface +{ + /** + * @var Boolean + */ + private $allowNonSynchronized; + + /** + * {@inheritdoc} + */ + public function mapViolation(ConstraintViolation $violation, FormInterface $form, $allowNonSynchronized = false) + { + $this->allowNonSynchronized = $allowNonSynchronized; + + // The scope is the currently found most specific form that + // an error should be mapped to. After setting the scope, the + // mapper will try to continue to find more specific matches in + // the children of scope. If it cannot, the error will be + // mapped to this scope. + $scope = null; + + $violationPath = null; + $relativePath = null; + $match = false; + + // Don't create a ViolationPath instance for empty property paths + if (strlen($violation->getPropertyPath()) > 0) { + $violationPath = new ViolationPath($violation->getPropertyPath()); + $relativePath = $this->reconstructPath($violationPath, $form); + } + + // This case happens if the violation path is empty and thus + // the violation should be mapped to the root form + if (null === $violationPath) { + $scope = $form; + } + + // In general, mapping happens from the root form to the leaf forms + // First, the rules of the root form are applied to determine + // the subsequent descendant. The rules of this descendant are then + // applied to find the next and so on, until we have found the + // most specific form that matches the violation. + + // If any of the forms found in this process is not synchronized, + // mapping is aborted. Non-synchronized forms could not reverse + // transform the value entered by the user, thus any further violations + // caused by the (invalid) reverse transformed value should be + // ignored. + + if (null !== $relativePath) { + // Set the scope to the root of the relative path + // This root will usually be $form. If the path contains + // an unmapped form though, the last unmapped form found + // will be the root of the path. + $scope = $relativePath->getRoot(); + $it = new PropertyPathIterator($relativePath); + + while ($this->acceptsErrors($scope) && null !== ($child = $this->matchChild($scope, $it))) { + $scope = $child; + $it->next(); + $match = true; + } + } + + // This case happens if an error happened in the data under a + // form inheriting its parent data that does not match any of the + // children of that form. + if (null !== $violationPath && !$match) { + // If we could not map the error to anything more specific + // than the root element, map it to the innermost directly + // mapped form of the violation path + // e.g. "children[foo].children[bar].data.baz" + // Here the innermost directly mapped child is "bar" + + $scope = $form; + $it = new ViolationPathIterator($violationPath); + + // Note: acceptsErrors() will always return true for forms inheriting + // their parent data, because these forms can never be non-synchronized + // (they don't do any data transformation on their own) + while ($this->acceptsErrors($scope) && $it->valid() && $it->mapsForm()) { + if (!$scope->has($it->current())) { + // Break if we find a reference to a non-existing child + break; + } + + $scope = $scope->get($it->current()); + $it->next(); + } + } + + // Follow dot rules until we have the final target + $mapping = $scope->getConfig()->getOption('error_mapping'); + + while ($this->acceptsErrors($scope) && isset($mapping['.'])) { + $dotRule = new MappingRule($scope, '.', $mapping['.']); + $scope = $dotRule->getTarget(); + $mapping = $scope->getConfig()->getOption('error_mapping'); + } + + // Only add the error if the form is synchronized + if ($this->acceptsErrors($scope)) { + $scope->addError(new FormError( + $violation->getMessage(), + $violation->getMessageTemplate(), + $violation->getMessageParameters(), + $violation->getMessagePluralization() + )); + } + } + + /** + * Tries to match the beginning of the property path at the + * current position against the children of the scope. + * + * If a matching child is found, it is returned. Otherwise + * null is returned. + * + * @param FormInterface $form The form to search. + * @param PropertyPathIteratorInterface $it The iterator at its current position. + * + * @return null|FormInterface The found match or null. + */ + private function matchChild(FormInterface $form, PropertyPathIteratorInterface $it) + { + // Remember at what property path underneath "data" + // we are looking. Check if there is a child with that + // path, otherwise increase path by one more piece + $chunk = ''; + $foundChild = null; + $foundAtIndex = 0; + + // Construct mapping rules for the given form + $rules = array(); + + foreach ($form->getConfig()->getOption('error_mapping') as $propertyPath => $targetPath) { + // Dot rules are considered at the very end + if ('.' !== $propertyPath) { + $rules[] = new MappingRule($form, $propertyPath, $targetPath); + } + } + + // Skip forms inheriting their parent data when iterating the children + $childIterator = new \RecursiveIteratorIterator( + new InheritDataAwareIterator($form->all()) + ); + + // Make the path longer until we find a matching child + while (true) { + if (!$it->valid()) { + return null; + } + + if ($it->isIndex()) { + $chunk .= '['.$it->current().']'; + } else { + $chunk .= ('' === $chunk ? '' : '.').$it->current(); + } + + // Test mapping rules as long as we have any + foreach ($rules as $key => $rule) { + /* @var MappingRule $rule */ + + // Mapping rule matches completely, terminate. + if (null !== ($form = $rule->match($chunk))) { + return $form; + } + + // Keep only rules that have $chunk as prefix + if (!$rule->isPrefix($chunk)) { + unset($rules[$key]); + } + } + + // Test children unless we already found one + if (null === $foundChild) { + foreach ($childIterator as $child) { + /* @var FormInterface $child */ + $childPath = (string) $child->getPropertyPath(); + + // Child found, mark as return value + if ($chunk === $childPath) { + $foundChild = $child; + $foundAtIndex = $it->key(); + } + } + } + + // Add element to the chunk + $it->next(); + + // If we reached the end of the path or if there are no + // more matching mapping rules, return the found child + if (null !== $foundChild && (!$it->valid() || count($rules) === 0)) { + // Reset index in case we tried to find mapping + // rules further down the path + $it->seek($foundAtIndex); + + return $foundChild; + } + } + + return null; + } + + /** + * Reconstructs a property path from a violation path and a form tree. + * + * @param ViolationPath $violationPath The violation path. + * @param FormInterface $origin The root form of the tree. + * + * @return RelativePath The reconstructed path. + */ + private function reconstructPath(ViolationPath $violationPath, FormInterface $origin) + { + $propertyPathBuilder = new PropertyPathBuilder($violationPath); + $it = $violationPath->getIterator(); + $scope = $origin; + + // Remember the current index in the builder + $i = 0; + + // Expand elements that map to a form (like "children[address]") + for ($it->rewind(); $it->valid() && $it->mapsForm(); $it->next()) { + if (!$scope->has($it->current())) { + // Scope relates to a form that does not exist + // Bail out + break; + } + + // Process child form + $scope = $scope->get($it->current()); + + if ($scope->getConfig()->getInheritData()) { + // Form inherits its parent data + // Cut the piece out of the property path and proceed + $propertyPathBuilder->remove($i); + } elseif (!$scope->getConfig()->getMapped()) { + // Form is not mapped + // Set the form as new origin and strip everything + // we have so far in the path + $origin = $scope; + $propertyPathBuilder->remove(0, $i + 1); + $i = 0; + } else { + /* @var \Symfony\Component\PropertyAccess\PropertyPathInterface $propertyPath */ + $propertyPath = $scope->getPropertyPath(); + + if (null === $propertyPath) { + // Property path of a mapped form is null + // Should not happen, bail out + break; + } + + $propertyPathBuilder->replace($i, 1, $propertyPath); + $i += $propertyPath->getLength(); + } + } + + $finalPath = $propertyPathBuilder->getPropertyPath(); + + return null !== $finalPath ? new RelativePath($origin, $finalPath) : null; + } + + /** + * @param FormInterface $form + * + * @return Boolean + */ + private function acceptsErrors(FormInterface $form) + { + return $this->allowNonSynchronized || $form->isSynchronized(); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php new file mode 100644 index 00000000..eb8907f1 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; + +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Validator\ConstraintViolation; + +/** + * @author Bernhard Schussek + */ +interface ViolationMapperInterface +{ + /** + * Maps a constraint violation to a form in the form tree under + * the given form. + * + * @param ConstraintViolation $violation The violation to map. + * @param FormInterface $form The root form of the tree + * to map it to. + * @param Boolean $allowNonSynchronized Whether to allow + * mapping to non-synchronized forms. + */ + public function mapViolation(ConstraintViolation $violation, FormInterface $form, $allowNonSynchronized = false); +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php new file mode 100644 index 00000000..06d09195 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php @@ -0,0 +1,250 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; + +use Symfony\Component\Form\Exception\OutOfBoundsException; +use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Component\PropertyAccess\PropertyPathInterface; + +/** + * @author Bernhard Schussek + */ +class ViolationPath implements \IteratorAggregate, PropertyPathInterface +{ + /** + * @var array + */ + private $elements = array(); + + /** + * @var array + */ + private $isIndex = array(); + + /** + * @var array + */ + private $mapsForm = array(); + + /** + * @var string + */ + private $pathAsString = ''; + + /** + * @var integer + */ + private $length = 0; + + /** + * Creates a new violation path from a string. + * + * @param string $violationPath The property path of a {@link ConstraintViolation} + * object. + */ + public function __construct($violationPath) + { + $path = new PropertyPath($violationPath); + $elements = $path->getElements(); + $data = false; + + for ($i = 0, $l = count($elements); $i < $l; ++$i) { + if (!$data) { + // The element "data" has not yet been passed + if ('children' === $elements[$i] && $path->isProperty($i)) { + // Skip element "children" + ++$i; + + // Next element must exist and must be an index + // Otherwise consider this the end of the path + if ($i >= $l || !$path->isIndex($i)) { + break; + } + + $this->elements[] = $elements[$i]; + $this->isIndex[] = true; + $this->mapsForm[] = true; + } elseif ('data' === $elements[$i] && $path->isProperty($i)) { + // Skip element "data" + ++$i; + + // End of path + if ($i >= $l) { + break; + } + + $this->elements[] = $elements[$i]; + $this->isIndex[] = $path->isIndex($i); + $this->mapsForm[] = false; + $data = true; + } else { + // Neither "children" nor "data" property found + // Consider this the end of the path + break; + } + } else { + // Already after the "data" element + // Pick everything as is + $this->elements[] = $elements[$i]; + $this->isIndex[] = $path->isIndex($i); + $this->mapsForm[] = false; + } + } + + $this->length = count($this->elements); + + $this->buildString(); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->pathAsString; + } + + /** + * {@inheritdoc} + */ + public function getLength() + { + return $this->length; + } + + /** + * {@inheritdoc} + */ + public function getParent() + { + if ($this->length <= 1) { + return null; + } + + $parent = clone $this; + + --$parent->length; + array_pop($parent->elements); + array_pop($parent->isIndex); + array_pop($parent->mapsForm); + + $parent->buildString(); + + return $parent; + } + + /** + * {@inheritdoc} + */ + public function getElements() + { + return $this->elements; + } + + /** + * {@inheritdoc} + */ + public function getElement($index) + { + if (!isset($this->elements[$index])) { + throw new OutOfBoundsException(sprintf('The index %s is not within the violation path', $index)); + } + + return $this->elements[$index]; + } + + /** + * {@inheritdoc} + */ + public function isProperty($index) + { + if (!isset($this->isIndex[$index])) { + throw new OutOfBoundsException(sprintf('The index %s is not within the violation path', $index)); + } + + return !$this->isIndex[$index]; + } + + /** + * {@inheritdoc} + */ + public function isIndex($index) + { + if (!isset($this->isIndex[$index])) { + throw new OutOfBoundsException(sprintf('The index %s is not within the violation path', $index)); + } + + return $this->isIndex[$index]; + } + + /** + * Returns whether an element maps directly to a form. + * + * Consider the following violation path: + * + * + * children[address].children[office].data.street + * + * + * In this example, "address" and "office" map to forms, while + * "street does not. + * + * @param integer $index The element index. + * + * @return Boolean Whether the element maps to a form. + * + * @throws OutOfBoundsException If the offset is invalid. + */ + public function mapsForm($index) + { + if (!isset($this->mapsForm[$index])) { + throw new OutOfBoundsException(sprintf('The index %s is not within the violation path', $index)); + } + + return $this->mapsForm[$index]; + } + + /** + * Returns a new iterator for this path + * + * @return ViolationPathIterator + */ + public function getIterator() + { + return new ViolationPathIterator($this); + } + + /** + * Builds the string representation from the elements. + */ + private function buildString() + { + $this->pathAsString = ''; + $data = false; + + foreach ($this->elements as $index => $element) { + if ($this->mapsForm[$index]) { + $this->pathAsString .= ".children[$element]"; + } elseif (!$data) { + $this->pathAsString .= '.data'.($this->isIndex[$index] ? "[$element]" : ".$element"); + $data = true; + } else { + $this->pathAsString .= $this->isIndex[$index] ? "[$element]" : ".$element"; + } + } + + if ('' !== $this->pathAsString) { + // remove leading dot + $this->pathAsString = substr($this->pathAsString, 1); + } + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php new file mode 100644 index 00000000..50baa453 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; + +use Symfony\Component\PropertyAccess\PropertyPathIterator; + +/** + * @author Bernhard Schussek + */ +class ViolationPathIterator extends PropertyPathIterator +{ + public function __construct(ViolationPath $violationPath) + { + parent::__construct($violationPath); + } + + public function mapsForm() + { + return $this->path->mapsForm($this->key()); + } +} -- cgit v1.2.3