vendor/symfony/validator/Constraint.php line 120

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Validator;
  11. use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
  12. use Symfony\Component\Validator\Exception\InvalidArgumentException;
  13. use Symfony\Component\Validator\Exception\InvalidOptionsException;
  14. use Symfony\Component\Validator\Exception\MissingOptionsException;
  15. /**
  16.  * Contains the properties of a constraint definition.
  17.  *
  18.  * A constraint can be defined on a class, a property or a getter method.
  19.  * The Constraint class encapsulates all the configuration required for
  20.  * validating this class, property or getter result successfully.
  21.  *
  22.  * Constraint instances are immutable and serializable.
  23.  *
  24.  * @author Bernhard Schussek <bschussek@gmail.com>
  25.  */
  26. abstract class Constraint
  27. {
  28.     /**
  29.      * The name of the group given to all constraints with no explicit group.
  30.      */
  31.     public const DEFAULT_GROUP 'Default';
  32.     /**
  33.      * Marks a constraint that can be put onto classes.
  34.      */
  35.     public const CLASS_CONSTRAINT 'class';
  36.     /**
  37.      * Marks a constraint that can be put onto properties.
  38.      */
  39.     public const PROPERTY_CONSTRAINT 'property';
  40.     /**
  41.      * Maps error codes to the names of their constants.
  42.      */
  43.     protected const ERROR_NAMES = [];
  44.     /**
  45.      * @deprecated since Symfony 6.1, use protected const ERROR_NAMES instead
  46.      */
  47.     protected static $errorNames = [];
  48.     /**
  49.      * Domain-specific data attached to a constraint.
  50.      *
  51.      * @var mixed
  52.      */
  53.     public $payload;
  54.     /**
  55.      * The groups that the constraint belongs to.
  56.      *
  57.      * @var string[]
  58.      */
  59.     public $groups;
  60.     /**
  61.      * Returns the name of the given error code.
  62.      *
  63.      * @throws InvalidArgumentException If the error code does not exist
  64.      */
  65.     public static function getErrorName(string $errorCode): string
  66.     {
  67.         if (isset(static::ERROR_NAMES[$errorCode])) {
  68.             return static::ERROR_NAMES[$errorCode];
  69.         }
  70.         if (!isset(static::$errorNames[$errorCode])) {
  71.             throw new InvalidArgumentException(sprintf('The error code "%s" does not exist for constraint of type "%s".'$errorCode, static::class));
  72.         }
  73.         trigger_deprecation('symfony/validator''6.1''The "%s::$errorNames" property is deprecated, use protected const ERROR_NAMES instead.', static::class);
  74.         return static::$errorNames[$errorCode];
  75.     }
  76.     /**
  77.      * Initializes the constraint with options.
  78.      *
  79.      * You should pass an associative array. The keys should be the names of
  80.      * existing properties in this class. The values should be the value for these
  81.      * properties.
  82.      *
  83.      * Alternatively you can override the method getDefaultOption() to return the
  84.      * name of an existing property. If no associative array is passed, this
  85.      * property is set instead.
  86.      *
  87.      * You can force that certain options are set by overriding
  88.      * getRequiredOptions() to return the names of these options. If any
  89.      * option is not set here, an exception is thrown.
  90.      *
  91.      * @param mixed    $options The options (as associative array)
  92.      *                          or the value for the default
  93.      *                          option (any other type)
  94.      * @param string[] $groups  An array of validation groups
  95.      * @param mixed    $payload Domain-specific data attached to a constraint
  96.      *
  97.      * @throws InvalidOptionsException       When you pass the names of non-existing
  98.      *                                       options
  99.      * @throws MissingOptionsException       When you don't pass any of the options
  100.      *                                       returned by getRequiredOptions()
  101.      * @throws ConstraintDefinitionException When you don't pass an associative
  102.      *                                       array, but getDefaultOption() returns
  103.      *                                       null
  104.      */
  105.     public function __construct(mixed $options null, array $groups nullmixed $payload null)
  106.     {
  107.         unset($this->groups); // enable lazy initialization
  108.         $options $this->normalizeOptions($options);
  109.         if (null !== $groups) {
  110.             $options['groups'] = $groups;
  111.         }
  112.         $options['payload'] = $payload ?? $options['payload'] ?? null;
  113.         foreach ($options as $name => $value) {
  114.             $this->$name $value;
  115.         }
  116.     }
  117.     protected function normalizeOptions(mixed $options): array
  118.     {
  119.         $normalizedOptions = [];
  120.         $defaultOption $this->getDefaultOption();
  121.         $invalidOptions = [];
  122.         $missingOptions array_flip((array) $this->getRequiredOptions());
  123.         $knownOptions get_class_vars(static::class);
  124.         if (\is_array($options) && isset($options['value']) && !property_exists($this'value')) {
  125.             if (null === $defaultOption) {
  126.                 throw new ConstraintDefinitionException(sprintf('No default option is configured for constraint "%s".', static::class));
  127.             }
  128.             $options[$defaultOption] = $options['value'];
  129.             unset($options['value']);
  130.         }
  131.         if (\is_array($options)) {
  132.             reset($options);
  133.         }
  134.         if ($options && \is_array($options) && \is_string(key($options))) {
  135.             foreach ($options as $option => $value) {
  136.                 if (\array_key_exists($option$knownOptions)) {
  137.                     $normalizedOptions[$option] = $value;
  138.                     unset($missingOptions[$option]);
  139.                 } else {
  140.                     $invalidOptions[] = $option;
  141.                 }
  142.             }
  143.         } elseif (null !== $options && !(\is_array($options) && === \count($options))) {
  144.             if (null === $defaultOption) {
  145.                 throw new ConstraintDefinitionException(sprintf('No default option is configured for constraint "%s".', static::class));
  146.             }
  147.             if (\array_key_exists($defaultOption$knownOptions)) {
  148.                 $normalizedOptions[$defaultOption] = $options;
  149.                 unset($missingOptions[$defaultOption]);
  150.             } else {
  151.                 $invalidOptions[] = $defaultOption;
  152.             }
  153.         }
  154.         if (\count($invalidOptions) > 0) {
  155.             throw new InvalidOptionsException(sprintf('The options "%s" do not exist in constraint "%s".'implode('", "'$invalidOptions), static::class), $invalidOptions);
  156.         }
  157.         if (\count($missingOptions) > 0) {
  158.             throw new MissingOptionsException(sprintf('The options "%s" must be set for constraint "%s".'implode('", "'array_keys($missingOptions)), static::class), array_keys($missingOptions));
  159.         }
  160.         return $normalizedOptions;
  161.     }
  162.     /**
  163.      * Sets the value of a lazily initialized option.
  164.      *
  165.      * Corresponding properties are added to the object on first access. Hence
  166.      * this method will be called at most once per constraint instance and
  167.      * option name.
  168.      *
  169.      * @throws InvalidOptionsException If an invalid option name is given
  170.      */
  171.     public function __set(string $optionmixed $value)
  172.     {
  173.         if ('groups' === $option) {
  174.             $this->groups = (array) $value;
  175.             return;
  176.         }
  177.         throw new InvalidOptionsException(sprintf('The option "%s" does not exist in constraint "%s".'$option, static::class), [$option]);
  178.     }
  179.     /**
  180.      * Returns the value of a lazily initialized option.
  181.      *
  182.      * Corresponding properties are added to the object on first access. Hence
  183.      * this method will be called at most once per constraint instance and
  184.      * option name.
  185.      *
  186.      * @throws InvalidOptionsException If an invalid option name is given
  187.      */
  188.     public function __get(string $option): mixed
  189.     {
  190.         if ('groups' === $option) {
  191.             $this->groups = [self::DEFAULT_GROUP];
  192.             return $this->groups;
  193.         }
  194.         throw new InvalidOptionsException(sprintf('The option "%s" does not exist in constraint "%s".'$option, static::class), [$option]);
  195.     }
  196.     public function __isset(string $option): bool
  197.     {
  198.         return 'groups' === $option;
  199.     }
  200.     /**
  201.      * Adds the given group if this constraint is in the Default group.
  202.      */
  203.     public function addImplicitGroupName(string $group)
  204.     {
  205.         if (null === $this->groups && \array_key_exists('groups', (array) $this)) {
  206.             throw new \LogicException(sprintf('"%s::$groups" is set to null. Did you forget to call "%s::__construct()"?', static::class, self::class));
  207.         }
  208.         if (\in_array(self::DEFAULT_GROUP$this->groups) && !\in_array($group$this->groups)) {
  209.             $this->groups[] = $group;
  210.         }
  211.     }
  212.     /**
  213.      * Returns the name of the default option.
  214.      *
  215.      * Override this method to define a default option.
  216.      *
  217.      * @return string|null
  218.      *
  219.      * @see __construct()
  220.      */
  221.     public function getDefaultOption()
  222.     {
  223.         return null;
  224.     }
  225.     /**
  226.      * Returns the name of the required options.
  227.      *
  228.      * Override this method if you want to define required options.
  229.      *
  230.      * @return string[]
  231.      *
  232.      * @see __construct()
  233.      */
  234.     public function getRequiredOptions()
  235.     {
  236.         return [];
  237.     }
  238.     /**
  239.      * Returns the name of the class that validates this constraint.
  240.      *
  241.      * By default, this is the fully qualified name of the constraint class
  242.      * suffixed with "Validator". You can override this method to change that
  243.      * behavior.
  244.      *
  245.      * @return string
  246.      */
  247.     public function validatedBy()
  248.     {
  249.         return static::class.'Validator';
  250.     }
  251.     /**
  252.      * Returns whether the constraint can be put onto classes, properties or
  253.      * both.
  254.      *
  255.      * This method should return one or more of the constants
  256.      * Constraint::CLASS_CONSTRAINT and Constraint::PROPERTY_CONSTRAINT.
  257.      *
  258.      * @return string|string[] One or more constant values
  259.      */
  260.     public function getTargets()
  261.     {
  262.         return self::PROPERTY_CONSTRAINT;
  263.     }
  264.     /**
  265.      * Optimizes the serialized value to minimize storage space.
  266.      *
  267.      * @internal
  268.      */
  269.     public function __sleep(): array
  270.     {
  271.         // Initialize "groups" option if it is not set
  272.         $this->groups;
  273.         return array_keys(get_object_vars($this));
  274.     }
  275. }