316 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
			
		
		
	
	
			316 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
| <?php
 | |
| 
 | |
| /*
 | |
|  * This file is part of the Symfony package.
 | |
|  *
 | |
|  * (c) Fabien Potencier <fabien@symfony.com>
 | |
|  *
 | |
|  * For the full copyright and license information, please view the LICENSE
 | |
|  * file that was distributed with this source code.
 | |
|  */
 | |
| 
 | |
| namespace Symfony\Component\Validator;
 | |
| 
 | |
| use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
 | |
| use Symfony\Component\Validator\Exception\InvalidArgumentException;
 | |
| use Symfony\Component\Validator\Exception\InvalidOptionsException;
 | |
| use Symfony\Component\Validator\Exception\MissingOptionsException;
 | |
| 
 | |
| /**
 | |
|  * Contains the properties of a constraint definition.
 | |
|  *
 | |
|  * A constraint can be defined on a class, a property or a getter method.
 | |
|  * The Constraint class encapsulates all the configuration required for
 | |
|  * validating this class, property or getter result successfully.
 | |
|  *
 | |
|  * Constraint instances are immutable and serializable.
 | |
|  *
 | |
|  * @author Bernhard Schussek <bschussek@gmail.com>
 | |
|  */
 | |
| abstract class Constraint
 | |
| {
 | |
|     /**
 | |
|      * The name of the group given to all constraints with no explicit group.
 | |
|      */
 | |
|     public const DEFAULT_GROUP = 'Default';
 | |
| 
 | |
|     /**
 | |
|      * Marks a constraint that can be put onto classes.
 | |
|      */
 | |
|     public const CLASS_CONSTRAINT = 'class';
 | |
| 
 | |
|     /**
 | |
|      * Marks a constraint that can be put onto properties.
 | |
|      */
 | |
|     public const PROPERTY_CONSTRAINT = 'property';
 | |
| 
 | |
|     /**
 | |
|      * Maps error codes to the names of their constants.
 | |
|      */
 | |
|     protected const ERROR_NAMES = [];
 | |
| 
 | |
|     /**
 | |
|      * @deprecated since Symfony 6.1, use protected const ERROR_NAMES instead
 | |
|      */
 | |
|     protected static $errorNames = [];
 | |
| 
 | |
|     /**
 | |
|      * Domain-specific data attached to a constraint.
 | |
|      *
 | |
|      * @var mixed
 | |
|      */
 | |
|     public $payload;
 | |
| 
 | |
|     /**
 | |
|      * The groups that the constraint belongs to.
 | |
|      *
 | |
|      * @var string[]
 | |
|      */
 | |
|     public $groups;
 | |
| 
 | |
|     /**
 | |
|      * Returns the name of the given error code.
 | |
|      *
 | |
|      * @throws InvalidArgumentException If the error code does not exist
 | |
|      */
 | |
|     public static function getErrorName(string $errorCode): string
 | |
|     {
 | |
|         if (isset(static::ERROR_NAMES[$errorCode])) {
 | |
|             return static::ERROR_NAMES[$errorCode];
 | |
|         }
 | |
| 
 | |
|         if (!isset(static::$errorNames[$errorCode])) {
 | |
|             throw new InvalidArgumentException(sprintf('The error code "%s" does not exist for constraint of type "%s".', $errorCode, static::class));
 | |
|         }
 | |
| 
 | |
|         trigger_deprecation('symfony/validator', '6.1', 'The "%s::$errorNames" property is deprecated, use protected const ERROR_NAMES instead.', static::class);
 | |
| 
 | |
|         return static::$errorNames[$errorCode];
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Initializes the constraint with options.
 | |
|      *
 | |
|      * 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
 | |
|      * properties.
 | |
|      *
 | |
|      * Alternatively you can override the method getDefaultOption() to return the
 | |
|      * name of an existing property. If no associative array is passed, this
 | |
|      * property is set instead.
 | |
|      *
 | |
|      * You can force that certain options are set by overriding
 | |
|      * getRequiredOptions() to return the names of these options. If any
 | |
|      * option is not set here, an exception is thrown.
 | |
|      *
 | |
|      * @param mixed    $options The options (as associative array)
 | |
|      *                          or the value for the default
 | |
|      *                          option (any other type)
 | |
|      * @param string[] $groups  An array of validation groups
 | |
|      * @param mixed    $payload Domain-specific data attached to a constraint
 | |
|      *
 | |
|      * @throws InvalidOptionsException       When you pass the names of non-existing
 | |
|      *                                       options
 | |
|      * @throws MissingOptionsException       When you don't pass any of the options
 | |
|      *                                       returned by getRequiredOptions()
 | |
|      * @throws ConstraintDefinitionException When you don't pass an associative
 | |
|      *                                       array, but getDefaultOption() returns
 | |
|      *                                       null
 | |
|      */
 | |
|     public function __construct(mixed $options = null, array $groups = null, mixed $payload = null)
 | |
|     {
 | |
|         unset($this->groups); // enable lazy initialization
 | |
| 
 | |
|         $options = $this->normalizeOptions($options);
 | |
|         if (null !== $groups) {
 | |
|             $options['groups'] = $groups;
 | |
|         }
 | |
|         $options['payload'] = $payload ?? $options['payload'] ?? null;
 | |
| 
 | |
|         foreach ($options as $name => $value) {
 | |
|             $this->$name = $value;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     protected function normalizeOptions(mixed $options): array
 | |
|     {
 | |
|         $normalizedOptions = [];
 | |
|         $defaultOption = $this->getDefaultOption();
 | |
|         $invalidOptions = [];
 | |
|         $missingOptions = array_flip((array) $this->getRequiredOptions());
 | |
|         $knownOptions = get_class_vars(static::class);
 | |
| 
 | |
|         if (\is_array($options) && isset($options['value']) && !property_exists($this, 'value')) {
 | |
|             if (null === $defaultOption) {
 | |
|                 throw new ConstraintDefinitionException(sprintf('No default option is configured for constraint "%s".', static::class));
 | |
|             }
 | |
| 
 | |
|             $options[$defaultOption] = $options['value'];
 | |
|             unset($options['value']);
 | |
|         }
 | |
| 
 | |
|         if (\is_array($options)) {
 | |
|             reset($options);
 | |
|         }
 | |
|         if ($options && \is_array($options) && \is_string(key($options))) {
 | |
|             foreach ($options as $option => $value) {
 | |
|                 if (\array_key_exists($option, $knownOptions)) {
 | |
|                     $normalizedOptions[$option] = $value;
 | |
|                     unset($missingOptions[$option]);
 | |
|                 } else {
 | |
|                     $invalidOptions[] = $option;
 | |
|                 }
 | |
|             }
 | |
|         } elseif (null !== $options && !(\is_array($options) && 0 === \count($options))) {
 | |
|             if (null === $defaultOption) {
 | |
|                 throw new ConstraintDefinitionException(sprintf('No default option is configured for constraint "%s".', static::class));
 | |
|             }
 | |
| 
 | |
|             if (\array_key_exists($defaultOption, $knownOptions)) {
 | |
|                 $normalizedOptions[$defaultOption] = $options;
 | |
|                 unset($missingOptions[$defaultOption]);
 | |
|             } else {
 | |
|                 $invalidOptions[] = $defaultOption;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (\count($invalidOptions) > 0) {
 | |
|             throw new InvalidOptionsException(sprintf('The options "%s" do not exist in constraint "%s".', implode('", "', $invalidOptions), static::class), $invalidOptions);
 | |
|         }
 | |
| 
 | |
|         if (\count($missingOptions) > 0) {
 | |
|             throw new MissingOptionsException(sprintf('The options "%s" must be set for constraint "%s".', implode('", "', array_keys($missingOptions)), static::class), array_keys($missingOptions));
 | |
|         }
 | |
| 
 | |
|         return $normalizedOptions;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the value of a lazily initialized option.
 | |
|      *
 | |
|      * Corresponding properties are added to the object on first access. Hence
 | |
|      * this method will be called at most once per constraint instance and
 | |
|      * option name.
 | |
|      *
 | |
|      * @throws InvalidOptionsException If an invalid option name is given
 | |
|      */
 | |
|     public function __set(string $option, mixed $value)
 | |
|     {
 | |
|         if ('groups' === $option) {
 | |
|             $this->groups = (array) $value;
 | |
| 
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         throw new InvalidOptionsException(sprintf('The option "%s" does not exist in constraint "%s".', $option, static::class), [$option]);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the value of a lazily initialized option.
 | |
|      *
 | |
|      * Corresponding properties are added to the object on first access. Hence
 | |
|      * this method will be called at most once per constraint instance and
 | |
|      * option name.
 | |
|      *
 | |
|      * @throws InvalidOptionsException If an invalid option name is given
 | |
|      */
 | |
|     public function __get(string $option): mixed
 | |
|     {
 | |
|         if ('groups' === $option) {
 | |
|             $this->groups = [self::DEFAULT_GROUP];
 | |
| 
 | |
|             return $this->groups;
 | |
|         }
 | |
| 
 | |
|         throw new InvalidOptionsException(sprintf('The option "%s" does not exist in constraint "%s".', $option, static::class), [$option]);
 | |
|     }
 | |
| 
 | |
|     public function __isset(string $option): bool
 | |
|     {
 | |
|         return 'groups' === $option;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Adds the given group if this constraint is in the Default group.
 | |
|      */
 | |
|     public function addImplicitGroupName(string $group)
 | |
|     {
 | |
|         if (null === $this->groups && \array_key_exists('groups', (array) $this)) {
 | |
|             throw new \LogicException(sprintf('"%s::$groups" is set to null. Did you forget to call "%s::__construct()"?', static::class, self::class));
 | |
|         }
 | |
| 
 | |
|         if (\in_array(self::DEFAULT_GROUP, $this->groups) && !\in_array($group, $this->groups)) {
 | |
|             $this->groups[] = $group;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the name of the default option.
 | |
|      *
 | |
|      * Override this method to define a default option.
 | |
|      *
 | |
|      * @return string|null
 | |
|      *
 | |
|      * @see __construct()
 | |
|      */
 | |
|     public function getDefaultOption()
 | |
|     {
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the name of the required options.
 | |
|      *
 | |
|      * Override this method if you want to define required options.
 | |
|      *
 | |
|      * @return string[]
 | |
|      *
 | |
|      * @see __construct()
 | |
|      */
 | |
|     public function getRequiredOptions()
 | |
|     {
 | |
|         return [];
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * 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
 | |
|      * behavior.
 | |
|      *
 | |
|      * @return string
 | |
|      */
 | |
|     public function validatedBy()
 | |
|     {
 | |
|         return static::class.'Validator';
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns whether the constraint can be put onto classes, properties or
 | |
|      * both.
 | |
|      *
 | |
|      * This method should return one or more of the constants
 | |
|      * Constraint::CLASS_CONSTRAINT and Constraint::PROPERTY_CONSTRAINT.
 | |
|      *
 | |
|      * @return string|string[] One or more constant values
 | |
|      */
 | |
|     public function getTargets()
 | |
|     {
 | |
|         return self::PROPERTY_CONSTRAINT;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Optimizes the serialized value to minimize storage space.
 | |
|      *
 | |
|      * @internal
 | |
|      */
 | |
|     public function __sleep(): array
 | |
|     {
 | |
|         // Initialize "groups" option if it is not set
 | |
|         $this->groups;
 | |
| 
 | |
|         return array_keys(get_object_vars($this));
 | |
|     }
 | |
| }
 |