login consent app sql

This commit is contained in:
2022-05-03 08:54:45 +02:00
parent e7253acfd8
commit f9a6535906
1652 changed files with 187600 additions and 45 deletions

View File

@ -0,0 +1,69 @@
<?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\Constraints;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\LogicException;
/**
* Used for the comparison of values.
*
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
abstract class AbstractComparison extends Constraint
{
public $message;
public $value;
public $propertyPath;
/**
* {@inheritdoc}
*
* @param mixed $value the value to compare or a set of options
*/
public function __construct($value = null, $propertyPath = null, string $message = null, array $groups = null, $payload = null, array $options = [])
{
if (\is_array($value)) {
$options = array_merge($value, $options);
} elseif (null !== $value) {
$options['value'] = $value;
}
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
$this->propertyPath = $propertyPath ?? $this->propertyPath;
if (null === $this->value && null === $this->propertyPath) {
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires either the "value" or "propertyPath" option to be set.', static::class));
}
if (null !== $this->value && null !== $this->propertyPath) {
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "value" or "propertyPath" options to be set, not both.', static::class));
}
if (null !== $this->propertyPath && !class_exists(PropertyAccess::class)) {
throw new LogicException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "propertyPath" option.', static::class));
}
}
/**
* {@inheritdoc}
*/
public function getDefaultOption()
{
return 'value';
}
}

View File

@ -0,0 +1,122 @@
<?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\Constraints;
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Provides a base class for the validation of property comparisons.
*
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
abstract class AbstractComparisonValidator extends ConstraintValidator
{
private $propertyAccessor;
public function __construct(PropertyAccessorInterface $propertyAccessor = null)
{
$this->propertyAccessor = $propertyAccessor;
}
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof AbstractComparison) {
throw new UnexpectedTypeException($constraint, AbstractComparison::class);
}
if (null === $value) {
return;
}
if ($path = $constraint->propertyPath) {
if (null === $object = $this->context->getObject()) {
return;
}
try {
$comparedValue = $this->getPropertyAccessor()->getValue($object, $path);
} catch (NoSuchPropertyException $e) {
throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: ', $path, get_debug_type($constraint)).$e->getMessage(), 0, $e);
}
} else {
$comparedValue = $constraint->value;
}
// Convert strings to DateTimes if comparing another DateTime
// This allows to compare with any date/time value supported by
// the DateTime constructor:
// https://php.net/datetime.formats
if (\is_string($comparedValue) && $value instanceof \DateTimeInterface) {
// If $value is immutable, convert the compared value to a DateTimeImmutable too, otherwise use DateTime
$dateTimeClass = $value instanceof \DateTimeImmutable ? \DateTimeImmutable::class : \DateTime::class;
try {
$comparedValue = new $dateTimeClass($comparedValue);
} catch (\Exception $e) {
throw new ConstraintDefinitionException(sprintf('The compared value "%s" could not be converted to a "%s" instance in the "%s" constraint.', $comparedValue, $dateTimeClass, get_debug_type($constraint)));
}
}
if (!$this->compareValues($value, $comparedValue)) {
$violationBuilder = $this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value, self::OBJECT_TO_STRING | self::PRETTY_DATE))
->setParameter('{{ compared_value }}', $this->formatValue($comparedValue, self::OBJECT_TO_STRING | self::PRETTY_DATE))
->setParameter('{{ compared_value_type }}', $this->formatTypeOf($comparedValue))
->setCode($this->getErrorCode());
if (null !== $path) {
$violationBuilder->setParameter('{{ compared_value_path }}', $path);
}
$violationBuilder->addViolation();
}
}
private function getPropertyAccessor(): PropertyAccessorInterface
{
if (null === $this->propertyAccessor) {
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
}
return $this->propertyAccessor;
}
/**
* Compares the two given values to find if their relationship is valid.
*
* @param mixed $value1 The first value to compare
* @param mixed $value2 The second value to compare
*
* @return bool
*/
abstract protected function compareValues($value1, $value2);
/**
* Returns the error code used if the comparison fails.
*
* @return string|null
*/
protected function getErrorCode()
{
return null;
}
}

View File

@ -0,0 +1,44 @@
<?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\Constraints;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class All extends Composite
{
public $constraints = [];
public function __construct($constraints = null, array $groups = null, $payload = null)
{
parent::__construct($constraints ?? [], $groups, $payload);
}
public function getDefaultOption()
{
return 'constraints';
}
public function getRequiredOptions()
{
return ['constraints'];
}
protected function getCompositeOption()
{
return 'constraints';
}
}

View File

@ -0,0 +1,49 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class AllValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof All) {
throw new UnexpectedTypeException($constraint, All::class);
}
if (null === $value) {
return;
}
if (!\is_array($value) && !$value instanceof \Traversable) {
throw new UnexpectedValueException($value, 'iterable');
}
$context = $this->context;
$validator = $context->getValidator()->inContext($context);
foreach ($value as $key => $element) {
$validator->atPath('['.$key.']')->validate($element, $constraint->constraints);
}
}
}

View File

@ -0,0 +1,57 @@
<?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\Constraints;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Przemysław Bogusz <przemyslaw.bogusz@tubotax.pl>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class AtLeastOneOf extends Composite
{
public const AT_LEAST_ONE_OF_ERROR = 'f27e6d6c-261a-4056-b391-6673a623531c';
protected static $errorNames = [
self::AT_LEAST_ONE_OF_ERROR => 'AT_LEAST_ONE_OF_ERROR',
];
public $constraints = [];
public $message = 'This value should satisfy at least one of the following constraints:';
public $messageCollection = 'Each element of this collection should satisfy its own set of constraints.';
public $includeInternalMessages = true;
public function __construct($constraints = null, array $groups = null, $payload = null, string $message = null, string $messageCollection = null, bool $includeInternalMessages = null)
{
parent::__construct($constraints ?? [], $groups, $payload);
$this->message = $message ?? $this->message;
$this->messageCollection = $messageCollection ?? $this->messageCollection;
$this->includeInternalMessages = $includeInternalMessages ?? $this->includeInternalMessages;
}
public function getDefaultOption()
{
return 'constraints';
}
public function getRequiredOptions()
{
return ['constraints'];
}
protected function getCompositeOption()
{
return 'constraints';
}
}

View File

@ -0,0 +1,63 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* @author Przemysław Bogusz <przemyslaw.bogusz@tubotax.pl>
*/
class AtLeastOneOfValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof AtLeastOneOf) {
throw new UnexpectedTypeException($constraint, AtLeastOneOf::class);
}
$validator = $this->context->getValidator();
$messages = [$constraint->message];
foreach ($constraint->constraints as $key => $item) {
$executionContext = clone $this->context;
$executionContext->setNode($value, $this->context->getObject(), $this->context->getMetadata(), $this->context->getPropertyPath());
$violations = $validator->inContext($executionContext)->validate($value, $item, $this->context->getGroup())->getViolations();
if (\count($this->context->getViolations()) === \count($violations)) {
return;
}
if ($constraint->includeInternalMessages) {
$message = ' ['.($key + 1).'] ';
if ($item instanceof All || $item instanceof Collection) {
$message .= $constraint->messageCollection;
} else {
$message .= $violations->get(\count($violations) - 1)->getMessage();
}
$messages[] = $message;
}
}
$this->context->buildViolation(implode('', $messages))
->setCode(AtLeastOneOf::AT_LEAST_ONE_OF_ERROR)
->addViolation()
;
}
}

View File

@ -0,0 +1,79 @@
<?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\Constraints;
use Symfony\Component\Intl\Countries;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyPathInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\LogicException;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Michael Hirschler <michael.vhirsch@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Bic extends Constraint
{
public const INVALID_LENGTH_ERROR = '66dad313-af0b-4214-8566-6c799be9789c';
public const INVALID_CHARACTERS_ERROR = 'f424c529-7add-4417-8f2d-4b656e4833e2';
public const INVALID_BANK_CODE_ERROR = '00559357-6170-4f29-aebd-d19330aa19cf';
public const INVALID_COUNTRY_CODE_ERROR = '1ce76f8d-3c1f-451c-9e62-fe9c3ed486ae';
public const INVALID_CASE_ERROR = '11884038-3312-4ae5-9d04-699f782130c7';
public const INVALID_IBAN_COUNTRY_CODE_ERROR = '29a2c3bb-587b-4996-b6f5-53081364cea5';
protected static $errorNames = [
self::INVALID_LENGTH_ERROR => 'INVALID_LENGTH_ERROR',
self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR',
self::INVALID_BANK_CODE_ERROR => 'INVALID_BANK_CODE_ERROR',
self::INVALID_COUNTRY_CODE_ERROR => 'INVALID_COUNTRY_CODE_ERROR',
self::INVALID_CASE_ERROR => 'INVALID_CASE_ERROR',
];
public $message = 'This is not a valid Business Identifier Code (BIC).';
public $ibanMessage = 'This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}.';
public $iban;
public $ibanPropertyPath;
/**
* {@inheritdoc}
*
* @param string|PropertyPathInterface|null $ibanPropertyPath
*/
public function __construct(array $options = null, string $message = null, string $iban = null, $ibanPropertyPath = null, string $ibanMessage = null, array $groups = null, $payload = null)
{
if (!class_exists(Countries::class)) {
throw new LogicException('The Intl component is required to use the Bic constraint. Try running "composer require symfony/intl".');
}
if (null !== $ibanPropertyPath && !\is_string($ibanPropertyPath) && !$ibanPropertyPath instanceof PropertyPathInterface) {
throw new \TypeError(sprintf('"%s": Expected argument $ibanPropertyPath to be either null, a string or an instance of "%s", got "%s".', __METHOD__, PropertyPathInterface::class, get_debug_type($ibanPropertyPath)));
}
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
$this->ibanMessage = $ibanMessage ?? $this->ibanMessage;
$this->iban = $iban ?? $this->iban;
$this->ibanPropertyPath = $ibanPropertyPath ?? $this->ibanPropertyPath;
if (null !== $this->iban && null !== $this->ibanPropertyPath) {
throw new ConstraintDefinitionException('The "iban" and "ibanPropertyPath" options of the Iban constraint cannot be used at the same time.');
}
if (null !== $this->ibanPropertyPath && !class_exists(PropertyAccess::class)) {
throw new LogicException(sprintf('The "symfony/property-access" component is required to use the "%s" constraint with the "ibanPropertyPath" option.', self::class));
}
}
}

View File

@ -0,0 +1,165 @@
<?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\Constraints;
use Symfony\Component\Intl\Countries;
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\LogicException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* @author Michael Hirschler <michael.vhirsch@gmail.com>
*
* @see https://en.wikipedia.org/wiki/ISO_9362#Structure
*/
class BicValidator extends ConstraintValidator
{
private const BIC_COUNTRY_TO_IBAN_COUNTRY_MAP = [
// Reference: https://www.ecbs.org/iban/france-bank-account-number.html
'GF' => 'FR', // French Guiana
'PF' => 'FR', // French Polynesia
'TF' => 'FR', // French Southern Territories
'GP' => 'FR', // Guadeloupe
'MQ' => 'FR', // Martinique
'YT' => 'FR', // Mayotte
'NC' => 'FR', // New Caledonia
'RE' => 'FR', // Reunion
'PM' => 'FR', // Saint Pierre and Miquelon
'WF' => 'FR', // Wallis and Futuna Islands
// Reference: https://www.ecbs.org/iban/united-kingdom-uk-bank-account-number.html
'JE' => 'GB', // Jersey
'IM' => 'GB', // Isle of Man
'GG' => 'GB', // Guernsey
'VG' => 'GB', // British Virgin Islands
];
private $propertyAccessor;
public function __construct(PropertyAccessor $propertyAccessor = null)
{
$this->propertyAccessor = $propertyAccessor;
}
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Bic) {
throw new UnexpectedTypeException($constraint, Bic::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$canonicalize = str_replace(' ', '', $value);
// the bic must be either 8 or 11 characters long
if (!\in_array(\strlen($canonicalize), [8, 11])) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Bic::INVALID_LENGTH_ERROR)
->addViolation();
return;
}
// must contain alphanumeric values only
if (!ctype_alnum($canonicalize)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Bic::INVALID_CHARACTERS_ERROR)
->addViolation();
return;
}
// first 4 letters must be alphabetic (bank code)
if (!ctype_alpha(substr($canonicalize, 0, 4))) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Bic::INVALID_BANK_CODE_ERROR)
->addViolation();
return;
}
if (!Countries::exists(substr($canonicalize, 4, 2))) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Bic::INVALID_COUNTRY_CODE_ERROR)
->addViolation();
return;
}
// should contain uppercase characters only
if (strtoupper($canonicalize) !== $canonicalize) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Bic::INVALID_CASE_ERROR)
->addViolation();
return;
}
// check against an IBAN
$iban = $constraint->iban;
$path = $constraint->ibanPropertyPath;
if ($path && null !== $object = $this->context->getObject()) {
try {
$iban = $this->getPropertyAccessor()->getValue($object, $path);
} catch (NoSuchPropertyException $e) {
throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: ', $path, get_debug_type($constraint)).$e->getMessage(), 0, $e);
}
}
if (!$iban) {
return;
}
$ibanCountryCode = substr($iban, 0, 2);
if (ctype_alpha($ibanCountryCode) && !$this->bicAndIbanCountriesMatch(substr($canonicalize, 4, 2), $ibanCountryCode)) {
$this->context->buildViolation($constraint->ibanMessage)
->setParameter('{{ value }}', $this->formatValue($value))
->setParameter('{{ iban }}', $iban)
->setCode(Bic::INVALID_IBAN_COUNTRY_CODE_ERROR)
->addViolation();
}
}
private function getPropertyAccessor(): PropertyAccessor
{
if (null === $this->propertyAccessor) {
if (!class_exists(PropertyAccess::class)) {
throw new LogicException('Unable to use property path as the Symfony PropertyAccess component is not installed.');
}
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
}
return $this->propertyAccessor;
}
private function bicAndIbanCountriesMatch(string $bicCountryCode, string $ibanCountryCode): bool
{
return $ibanCountryCode === $bicCountryCode || $ibanCountryCode === (self::BIC_COUNTRY_TO_IBAN_COUNTRY_MAP[$bicCountryCode] ?? null);
}
}

View File

@ -0,0 +1,39 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Blank extends Constraint
{
public const NOT_BLANK_ERROR = '183ad2de-533d-4796-a439-6d3c3852b549';
protected static $errorNames = [
self::NOT_BLANK_ERROR => 'NOT_BLANK_ERROR',
];
public $message = 'This value should be blank.';
public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null)
{
parent::__construct($options ?? [], $groups, $payload);
$this->message = $message ?? $this->message;
}
}

View File

@ -0,0 +1,39 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class BlankValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Blank) {
throw new UnexpectedTypeException($constraint, Blank::class);
}
if ('' !== $value && null !== $value) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Blank::NOT_BLANK_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,66 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
* @Target({"CLASS", "PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Callback extends Constraint
{
/**
* @var string|callable
*/
public $callback;
/**
* {@inheritdoc}
*
* @param array|string|callable $callback The callback or a set of options
*/
public function __construct($callback = null, array $groups = null, $payload = null, array $options = [])
{
// Invocation through annotations with an array parameter only
if (\is_array($callback) && 1 === \count($callback) && isset($callback['value'])) {
$callback = $callback['value'];
}
if (!\is_array($callback) || (!isset($callback['callback']) && !isset($callback['groups']) && !isset($callback['payload']))) {
$options['callback'] = $callback;
} else {
$options = array_merge($callback, $options);
}
parent::__construct($options, $groups, $payload);
}
/**
* {@inheritdoc}
*/
public function getDefaultOption()
{
return 'callback';
}
/**
* {@inheritdoc}
*/
public function getTargets()
{
return [self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT];
}
}

View File

@ -0,0 +1,61 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Validator for Callback constraint.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class CallbackValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($object, Constraint $constraint)
{
if (!$constraint instanceof Callback) {
throw new UnexpectedTypeException($constraint, Callback::class);
}
$method = $constraint->callback;
if ($method instanceof \Closure) {
$method($object, $this->context, $constraint->payload);
} elseif (\is_array($method)) {
if (!\is_callable($method)) {
if (isset($method[0]) && \is_object($method[0])) {
$method[0] = \get_class($method[0]);
}
throw new ConstraintDefinitionException(json_encode($method).' targeted by Callback constraint is not a valid callable.');
}
$method($object, $this->context, $constraint->payload);
} elseif (null !== $object) {
if (!method_exists($object, $method)) {
throw new ConstraintDefinitionException(sprintf('Method "%s" targeted by Callback constraint does not exist in class "%s".', $method, get_debug_type($object)));
}
$reflMethod = new \ReflectionMethod($object, $method);
if ($reflMethod->isStatic()) {
$reflMethod->invoke(null, $object, $this->context, $constraint->payload);
} else {
$reflMethod->invoke($object, $this->context, $constraint->payload);
}
}
}
}

View File

@ -0,0 +1,79 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* Metadata for the CardSchemeValidator.
*
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Tim Nagel <t.nagel@infinite.net.au>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class CardScheme extends Constraint
{
public const AMEX = 'AMEX';
public const CHINA_UNIONPAY = 'CHINA_UNIONPAY';
public const DINERS = 'DINERS';
public const DISCOVER = 'DISCOVER';
public const INSTAPAYMENT = 'INSTAPAYMENT';
public const JCB = 'JCB';
public const LASER = 'LASER';
public const MAESTRO = 'MAESTRO';
public const MASTERCARD = 'MASTERCARD';
public const MIR = 'MIR';
public const UATP = 'UATP';
public const VISA = 'VISA';
public const NOT_NUMERIC_ERROR = 'a2ad9231-e827-485f-8a1e-ef4d9a6d5c2e';
public const INVALID_FORMAT_ERROR = 'a8faedbf-1c2f-4695-8d22-55783be8efed';
protected static $errorNames = [
self::NOT_NUMERIC_ERROR => 'NOT_NUMERIC_ERROR',
self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR',
];
public $message = 'Unsupported card type or invalid card number.';
public $schemes;
/**
* {@inheritdoc}
*
* @param array|string $schemes The schemes to validate against or a set of options
*/
public function __construct($schemes, string $message = null, array $groups = null, $payload = null, array $options = [])
{
if (\is_array($schemes) && \is_string(key($schemes))) {
$options = array_merge($schemes, $options);
} else {
$options['value'] = $schemes;
}
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
}
public function getDefaultOption()
{
return 'schemes';
}
public function getRequiredOptions()
{
return ['schemes'];
}
}

View File

@ -0,0 +1,134 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Validates that a card number belongs to a specified scheme.
*
* @author Tim Nagel <t.nagel@infinite.net.au>
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @see https://en.wikipedia.org/wiki/Payment_card_number
* @see https://www.regular-expressions.info/creditcard.html
*/
class CardSchemeValidator extends ConstraintValidator
{
protected $schemes = [
// American Express card numbers start with 34 or 37 and have 15 digits.
CardScheme::AMEX => [
'/^3[47][0-9]{13}$/',
],
// China UnionPay cards start with 62 and have between 16 and 19 digits.
// Please note that these cards do not follow Luhn Algorithm as a checksum.
CardScheme::CHINA_UNIONPAY => [
'/^62[0-9]{14,17}$/',
],
// Diners Club card numbers begin with 300 through 305, 36 or 38. All have 14 digits.
// There are Diners Club cards that begin with 5 and have 16 digits.
// These are a joint venture between Diners Club and MasterCard, and should be processed like a MasterCard.
CardScheme::DINERS => [
'/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/',
],
// Discover card numbers begin with 6011, 622126 through 622925, 644 through 649 or 65.
// All have 16 digits.
CardScheme::DISCOVER => [
'/^6011[0-9]{12}$/',
'/^64[4-9][0-9]{13}$/',
'/^65[0-9]{14}$/',
'/^622(12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|91[0-9]|92[0-5])[0-9]{10}$/',
],
// InstaPayment cards begin with 637 through 639 and have 16 digits.
CardScheme::INSTAPAYMENT => [
'/^63[7-9][0-9]{13}$/',
],
// JCB cards beginning with 2131 or 1800 have 15 digits.
// JCB cards beginning with 35 have 16 digits.
CardScheme::JCB => [
'/^(?:2131|1800|35[0-9]{3})[0-9]{11}$/',
],
// Laser cards begin with either 6304, 6706, 6709 or 6771 and have between 16 and 19 digits.
CardScheme::LASER => [
'/^(6304|670[69]|6771)[0-9]{12,15}$/',
],
// Maestro international cards begin with 675900..675999 and have between 12 and 19 digits.
// Maestro UK cards begin with either 500000..509999 or 560000..699999 and have between 12 and 19 digits.
CardScheme::MAESTRO => [
'/^(6759[0-9]{2})[0-9]{6,13}$/',
'/^(50[0-9]{4})[0-9]{6,13}$/',
'/^5[6-9][0-9]{10,17}$/',
'/^6[0-9]{11,18}$/',
],
// All MasterCard numbers start with the numbers 51 through 55. All have 16 digits.
// October 2016 MasterCard numbers can also start with 222100 through 272099.
CardScheme::MASTERCARD => [
'/^5[1-5][0-9]{14}$/',
'/^2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12})$/',
],
// Payment system MIR numbers start with 220, then 1 digit from 0 to 4, then between 12 and 15 digits
CardScheme::MIR => [
'/^220[0-4][0-9]{12,15}$/',
],
// All UATP card numbers start with a 1 and have a length of 15 digits.
CardScheme::UATP => [
'/^1[0-9]{14}$/',
],
// All Visa card numbers start with a 4 and have a length of 13, 16, or 19 digits.
CardScheme::VISA => [
'/^4([0-9]{12}|[0-9]{15}|[0-9]{18})$/',
],
];
/**
* Validates a creditcard belongs to a specified scheme.
*
* @param mixed $value
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof CardScheme) {
throw new UnexpectedTypeException($constraint, CardScheme::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_numeric($value)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(CardScheme::NOT_NUMERIC_ERROR)
->addViolation();
return;
}
$schemes = array_flip((array) $constraint->schemes);
$schemeRegexes = array_intersect_key($this->schemes, $schemes);
foreach ($schemeRegexes as $regexes) {
foreach ($regexes as $regex) {
if (preg_match($regex, $value)) {
return;
}
}
}
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(CardScheme::INVALID_FORMAT_ERROR)
->addViolation();
}
}

View File

@ -0,0 +1,42 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* @Annotation
* @Target({"CLASS"})
*
* @author Jules Pietri <jules@heahprod.com>
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
class Cascade extends Constraint
{
public function __construct(array $options = null)
{
if (\is_array($options) && \array_key_exists('groups', $options)) {
throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__));
}
parent::__construct($options);
}
/**
* {@inheritdoc}
*/
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}

View File

@ -0,0 +1,89 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Choice extends Constraint
{
public const NO_SUCH_CHOICE_ERROR = '8e179f1b-97aa-4560-a02f-2a8b42e49df7';
public const TOO_FEW_ERROR = '11edd7eb-5872-4b6e-9f12-89923999fd0e';
public const TOO_MANY_ERROR = '9bd98e49-211c-433f-8630-fd1c2d0f08c3';
protected static $errorNames = [
self::NO_SUCH_CHOICE_ERROR => 'NO_SUCH_CHOICE_ERROR',
self::TOO_FEW_ERROR => 'TOO_FEW_ERROR',
self::TOO_MANY_ERROR => 'TOO_MANY_ERROR',
];
public $choices;
public $callback;
public $multiple = false;
public $strict = true;
public $min;
public $max;
public $message = 'The value you selected is not a valid choice.';
public $multipleMessage = 'One or more of the given values is invalid.';
public $minMessage = 'You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices.';
public $maxMessage = 'You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices.';
/**
* {@inheritdoc}
*/
public function getDefaultOption()
{
return 'choices';
}
public function __construct(
$options = [],
array $choices = null,
$callback = null,
bool $multiple = null,
bool $strict = null,
int $min = null,
int $max = null,
string $message = null,
string $multipleMessage = null,
string $minMessage = null,
string $maxMessage = null,
$groups = null,
$payload = null
) {
if (\is_array($options) && $options && array_is_list($options)) {
$choices = $choices ?? $options;
$options = [];
}
if (null !== $choices) {
$options['value'] = $choices;
}
parent::__construct($options, $groups, $payload);
$this->callback = $callback ?? $this->callback;
$this->multiple = $multiple ?? $this->multiple;
$this->strict = $strict ?? $this->strict;
$this->min = $min ?? $this->min;
$this->max = $max ?? $this->max;
$this->message = $message ?? $this->message;
$this->multipleMessage = $multipleMessage ?? $this->multipleMessage;
$this->minMessage = $minMessage ?? $this->minMessage;
$this->maxMessage = $maxMessage ?? $this->maxMessage;
}
}

View File

@ -0,0 +1,109 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* ChoiceValidator validates that the value is one of the expected values.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Florian Eckerstorfer <florian@eckerstorfer.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ChoiceValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Choice) {
throw new UnexpectedTypeException($constraint, Choice::class);
}
if (!\is_array($constraint->choices) && !$constraint->callback) {
throw new ConstraintDefinitionException('Either "choices" or "callback" must be specified on constraint Choice.');
}
if (null === $value) {
return;
}
if ($constraint->multiple && !\is_array($value)) {
throw new UnexpectedValueException($value, 'array');
}
if ($constraint->callback) {
if (!\is_callable($choices = [$this->context->getObject(), $constraint->callback])
&& !\is_callable($choices = [$this->context->getClassName(), $constraint->callback])
&& !\is_callable($choices = $constraint->callback)
) {
throw new ConstraintDefinitionException('The Choice constraint expects a valid callback.');
}
$choices = $choices();
} else {
$choices = $constraint->choices;
}
if (true !== $constraint->strict) {
throw new \RuntimeException('The "strict" option of the Choice constraint should not be used.');
}
if ($constraint->multiple) {
foreach ($value as $_value) {
if (!\in_array($_value, $choices, true)) {
$this->context->buildViolation($constraint->multipleMessage)
->setParameter('{{ value }}', $this->formatValue($_value))
->setParameter('{{ choices }}', $this->formatValues($choices))
->setCode(Choice::NO_SUCH_CHOICE_ERROR)
->setInvalidValue($_value)
->addViolation();
return;
}
}
$count = \count($value);
if (null !== $constraint->min && $count < $constraint->min) {
$this->context->buildViolation($constraint->minMessage)
->setParameter('{{ limit }}', $constraint->min)
->setPlural((int) $constraint->min)
->setCode(Choice::TOO_FEW_ERROR)
->addViolation();
return;
}
if (null !== $constraint->max && $count > $constraint->max) {
$this->context->buildViolation($constraint->maxMessage)
->setParameter('{{ limit }}', $constraint->max)
->setPlural((int) $constraint->max)
->setCode(Choice::TOO_MANY_ERROR)
->addViolation();
return;
}
} elseif (!\in_array($value, $choices, true)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setParameter('{{ choices }}', $this->formatValues($choices))
->setCode(Choice::NO_SUCH_CHOICE_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,80 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* Validates that a value is a valid CIDR notation.
*
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Sorin Pop <popsorin15@gmail.com>
* @author Calin Bolea <calin.bolea@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Cidr extends Constraint
{
public const INVALID_CIDR_ERROR = '5649e53a-5afb-47c5-a360-ffbab3be8567';
public const OUT_OF_RANGE_ERROR = 'b9f14a51-acbd-401a-a078-8c6b204ab32f';
protected static $errorNames = [
self::INVALID_CIDR_ERROR => 'INVALID_CIDR_ERROR',
self::OUT_OF_RANGE_ERROR => 'OUT_OF_RANGE_VIOLATION',
];
private const NET_MAXES = [
Ip::ALL => 128,
Ip::V4 => 32,
Ip::V6 => 128,
];
public $version = Ip::ALL;
public $message = 'This value is not a valid CIDR notation.';
public $netmaskRangeViolationMessage = 'The value of the netmask should be between {{ min }} and {{ max }}.';
public $netmaskMin = 0;
public $netmaskMax;
public function __construct(
array $options = null,
string $version = null,
int $netmaskMin = null,
int $netmaskMax = null,
string $message = null,
array $groups = null,
$payload = null
) {
$this->version = $version ?? $options['version'] ?? $this->version;
if (!\in_array($this->version, array_keys(self::NET_MAXES))) {
throw new ConstraintDefinitionException(sprintf('The option "version" must be one of "%s".', implode('", "', array_keys(self::NET_MAXES))));
}
$this->netmaskMin = $netmaskMin ?? $options['netmaskMin'] ?? $this->netmaskMin;
$this->netmaskMax = $netmaskMax ?? $options['netmaskMax'] ?? self::NET_MAXES[$this->version];
$this->message = $message ?? $this->message;
unset($options['netmaskMin'], $options['netmaskMax'], $options['version']);
if ($this->netmaskMin < 0 || $this->netmaskMax > self::NET_MAXES[$this->version] || $this->netmaskMin > $this->netmaskMax) {
throw new ConstraintDefinitionException(sprintf('The netmask range must be between 0 and %d.', self::NET_MAXES[$this->version]));
}
parent::__construct($options, $groups, $payload);
}
}

View File

@ -0,0 +1,77 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
class CidrValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint): void
{
if (!$constraint instanceof Cidr) {
throw new UnexpectedTypeException($constraint, Cidr::class);
}
if (null === $value || '' === $value) {
return;
}
if (!\is_string($value)) {
throw new UnexpectedValueException($value, 'string');
}
$cidrParts = explode('/', $value, 2);
if (!isset($cidrParts[1])
|| !ctype_digit($cidrParts[1])
|| '' === $cidrParts[0]
) {
$this->context
->buildViolation($constraint->message)
->setCode(Cidr::INVALID_CIDR_ERROR)
->addViolation();
return;
}
$ipAddress = $cidrParts[0];
$netmask = (int) $cidrParts[1];
$validV4 = Ip::V6 !== $constraint->version
&& filter_var($ipAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)
&& $netmask <= 32;
$validV6 = Ip::V4 !== $constraint->version
&& filter_var($ipAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6);
if (!$validV4 && !$validV6) {
$this->context
->buildViolation($constraint->message)
->setCode(Cidr::INVALID_CIDR_ERROR)
->addViolation();
return;
}
if ($netmask < $constraint->netmaskMin || $netmask > $constraint->netmaskMax) {
$this->context
->buildViolation($constraint->netmaskRangeViolationMessage)
->setParameter('{{ min }}', $constraint->netmaskMin)
->setParameter('{{ max }}', $constraint->netmaskMax)
->setCode(Cidr::OUT_OF_RANGE_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,91 @@
<?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\Constraints;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Collection extends Composite
{
public const MISSING_FIELD_ERROR = '2fa2158c-2a7f-484b-98aa-975522539ff8';
public const NO_SUCH_FIELD_ERROR = '7703c766-b5d5-4cef-ace7-ae0dd82304e9';
protected static $errorNames = [
self::MISSING_FIELD_ERROR => 'MISSING_FIELD_ERROR',
self::NO_SUCH_FIELD_ERROR => 'NO_SUCH_FIELD_ERROR',
];
public $fields = [];
public $allowExtraFields = false;
public $allowMissingFields = false;
public $extraFieldsMessage = 'This field was not expected.';
public $missingFieldsMessage = 'This field is missing.';
/**
* {@inheritdoc}
*/
public function __construct($fields = null, array $groups = null, $payload = null, bool $allowExtraFields = null, bool $allowMissingFields = null, string $extraFieldsMessage = null, string $missingFieldsMessage = null)
{
// no known options set? $fields is the fields array
if (\is_array($fields)
&& !array_intersect(array_keys($fields), ['groups', 'fields', 'allowExtraFields', 'allowMissingFields', 'extraFieldsMessage', 'missingFieldsMessage'])) {
$fields = ['fields' => $fields];
}
parent::__construct($fields, $groups, $payload);
$this->allowExtraFields = $allowExtraFields ?? $this->allowExtraFields;
$this->allowMissingFields = $allowMissingFields ?? $this->allowMissingFields;
$this->extraFieldsMessage = $extraFieldsMessage ?? $this->extraFieldsMessage;
$this->missingFieldsMessage = $missingFieldsMessage ?? $this->missingFieldsMessage;
}
/**
* {@inheritdoc}
*/
protected function initializeNestedConstraints()
{
parent::initializeNestedConstraints();
if (!\is_array($this->fields)) {
throw new ConstraintDefinitionException(sprintf('The option "fields" is expected to be an array in constraint "%s".', __CLASS__));
}
foreach ($this->fields as $fieldName => $field) {
// the XmlFileLoader and YamlFileLoader pass the field Optional
// and Required constraint as an array with exactly one element
if (\is_array($field) && 1 == \count($field)) {
$this->fields[$fieldName] = $field = $field[0];
}
if (!$field instanceof Optional && !$field instanceof Required) {
$this->fields[$fieldName] = new Required($field);
}
}
}
public function getRequiredOptions()
{
return ['fields'];
}
protected function getCompositeOption()
{
return 'fields';
}
}

View File

@ -0,0 +1,87 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class CollectionValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Collection) {
throw new UnexpectedTypeException($constraint, Collection::class);
}
if (null === $value) {
return;
}
if (!\is_array($value) && !($value instanceof \Traversable && $value instanceof \ArrayAccess)) {
throw new UnexpectedValueException($value, 'array|(Traversable&ArrayAccess)');
}
// We need to keep the initialized context when CollectionValidator
// calls itself recursively (Collection constraints can be nested).
// Since the context of the validator is overwritten when initialize()
// is called for the nested constraint, the outer validator is
// acting on the wrong context when the nested validation terminates.
//
// A better solution - which should be approached in Symfony 3.0 - is to
// remove the initialize() method and pass the context as last argument
// to validate() instead.
$context = $this->context;
foreach ($constraint->fields as $field => $fieldConstraint) {
// bug fix issue #2779
$existsInArray = \is_array($value) && \array_key_exists($field, $value);
$existsInArrayAccess = $value instanceof \ArrayAccess && $value->offsetExists($field);
if ($existsInArray || $existsInArrayAccess) {
if (\count($fieldConstraint->constraints) > 0) {
$context->getValidator()
->inContext($context)
->atPath('['.$field.']')
->validate($value[$field], $fieldConstraint->constraints);
}
} elseif (!$fieldConstraint instanceof Optional && !$constraint->allowMissingFields) {
$context->buildViolation($constraint->missingFieldsMessage)
->atPath('['.$field.']')
->setParameter('{{ field }}', $this->formatValue($field))
->setInvalidValue(null)
->setCode(Collection::MISSING_FIELD_ERROR)
->addViolation();
}
}
if (!$constraint->allowExtraFields) {
foreach ($value as $field => $fieldValue) {
if (!isset($constraint->fields[$field])) {
$context->buildViolation($constraint->extraFieldsMessage)
->atPath('['.$field.']')
->setParameter('{{ field }}', $this->formatValue($field))
->setInvalidValue($fieldValue)
->setCode(Collection::NO_SUCH_FIELD_ERROR)
->addViolation();
}
}
}
}
}

View File

@ -0,0 +1,159 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* A constraint that is composed of other constraints.
*
* You should never use the nested constraint instances anywhere else, because
* their groups are adapted when passed to the constructor of this class.
*
* If you want to create your own composite constraint, extend this class and
* let {@link getCompositeOption()} return the name of the property which
* contains the nested constraints.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
abstract class Composite extends Constraint
{
/**
* {@inheritdoc}
*
* The groups of the composite and its nested constraints are made
* consistent using the following strategy:
*
* - If groups are passed explicitly to the composite constraint, but
* not to the nested constraints, the options of the composite
* constraint are copied to the nested constraints;
*
* - If groups are passed explicitly to the nested constraints, but not
* to the composite constraint, the groups of all nested constraints
* are merged and used as groups for the composite constraint;
*
* - If groups are passed explicitly to both the composite and its nested
* constraints, the groups of the nested constraints must be a subset
* of the groups of the composite constraint. If not, a
* {@link ConstraintDefinitionException} is thrown.
*
* All this is done in the constructor, because constraints can then be
* cached. When constraints are loaded from the cache, no more group
* checks need to be done.
*/
public function __construct($options = null, array $groups = null, $payload = null)
{
parent::__construct($options, $groups, $payload);
$this->initializeNestedConstraints();
/* @var Constraint[] $nestedConstraints */
$compositeOption = $this->getCompositeOption();
$nestedConstraints = $this->$compositeOption;
if (!\is_array($nestedConstraints)) {
$nestedConstraints = [$nestedConstraints];
}
foreach ($nestedConstraints as $constraint) {
if (!$constraint instanceof Constraint) {
if (\is_object($constraint)) {
$constraint = \get_class($constraint);
}
throw new ConstraintDefinitionException(sprintf('The value "%s" is not an instance of Constraint in constraint "%s".', $constraint, static::class));
}
if ($constraint instanceof Valid) {
throw new ConstraintDefinitionException(sprintf('The constraint Valid cannot be nested inside constraint "%s". You can only declare the Valid constraint directly on a field or method.', static::class));
}
}
if (!isset(((array) $this)['groups'])) {
$mergedGroups = [];
foreach ($nestedConstraints as $constraint) {
foreach ($constraint->groups as $group) {
$mergedGroups[$group] = true;
}
}
// prevent empty composite constraint to have empty groups
$this->groups = array_keys($mergedGroups) ?: [self::DEFAULT_GROUP];
$this->$compositeOption = $nestedConstraints;
return;
}
foreach ($nestedConstraints as $constraint) {
if (isset(((array) $constraint)['groups'])) {
$excessGroups = array_diff($constraint->groups, $this->groups);
if (\count($excessGroups) > 0) {
throw new ConstraintDefinitionException(sprintf('The group(s) "%s" passed to the constraint "%s" should also be passed to its containing constraint "%s".', implode('", "', $excessGroups), get_debug_type($constraint), static::class));
}
} else {
$constraint->groups = $this->groups;
}
}
$this->$compositeOption = $nestedConstraints;
}
/**
* {@inheritdoc}
*
* Implicit group names are forwarded to nested constraints.
*/
public function addImplicitGroupName(string $group)
{
parent::addImplicitGroupName($group);
/** @var Constraint[] $nestedConstraints */
$nestedConstraints = $this->{$this->getCompositeOption()};
foreach ($nestedConstraints as $constraint) {
$constraint->addImplicitGroupName($group);
}
}
/**
* Returns the name of the property that contains the nested constraints.
*
* @return string
*/
abstract protected function getCompositeOption();
/**
* @internal Used by metadata
*
* @return Constraint[]
*/
public function getNestedConstraints()
{
/* @var Constraint[] $nestedConstraints */
return $this->{$this->getCompositeOption()};
}
/**
* Initializes the nested constraints.
*
* This method can be overwritten in subclasses to clean up the nested
* constraints passed to the constructor.
*
* @see Collection::initializeNestedConstraints()
*/
protected function initializeNestedConstraints()
{
}
}

View File

@ -0,0 +1,52 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* Extend this class to create a reusable set of constraints.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
abstract class Compound extends Composite
{
/** @var Constraint[] */
public $constraints = [];
public function __construct($options = null)
{
if (isset($options[$this->getCompositeOption()])) {
throw new ConstraintDefinitionException(sprintf('You can\'t redefine the "%s" option. Use the "%s::getConstraints()" method instead.', $this->getCompositeOption(), __CLASS__));
}
$this->constraints = $this->getConstraints($this->normalizeOptions($options));
parent::__construct($options);
}
final protected function getCompositeOption(): string
{
return 'constraints';
}
final public function validatedBy(): string
{
return CompoundValidator::class;
}
/**
* @return Constraint[]
*/
abstract protected function getConstraints(array $options): array;
}

View File

@ -0,0 +1,35 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class CompoundValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Compound) {
throw new UnexpectedTypeException($constraint, Compound::class);
}
$context = $this->context;
$validator = $context->getValidator()->inContext($context);
$validator->validate($value, $constraint->constraints);
}
}

View File

@ -0,0 +1,92 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\MissingOptionsException;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Count extends Constraint
{
public const TOO_FEW_ERROR = 'bef8e338-6ae5-4caf-b8e2-50e7b0579e69';
public const TOO_MANY_ERROR = '756b1212-697c-468d-a9ad-50dd783bb169';
public const NOT_EQUAL_COUNT_ERROR = '9fe5d43f-3784-4ece-a0e1-473fc02dadbc';
public const NOT_DIVISIBLE_BY_ERROR = DivisibleBy::NOT_DIVISIBLE_BY;
protected static $errorNames = [
self::TOO_FEW_ERROR => 'TOO_FEW_ERROR',
self::TOO_MANY_ERROR => 'TOO_MANY_ERROR',
self::NOT_EQUAL_COUNT_ERROR => 'NOT_EQUAL_COUNT_ERROR',
self::NOT_DIVISIBLE_BY_ERROR => 'NOT_DIVISIBLE_BY_ERROR',
];
public $minMessage = 'This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more.';
public $maxMessage = 'This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less.';
public $exactMessage = 'This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements.';
public $divisibleByMessage = 'The number of elements in this collection should be a multiple of {{ compared_value }}.';
public $min;
public $max;
public $divisibleBy;
/**
* {@inheritdoc}
*
* @param int|array|null $exactly The expected exact count or a set of options
*/
public function __construct(
$exactly = null,
int $min = null,
int $max = null,
int $divisibleBy = null,
string $exactMessage = null,
string $minMessage = null,
string $maxMessage = null,
string $divisibleByMessage = null,
array $groups = null,
$payload = null,
array $options = []
) {
if (\is_array($exactly)) {
$options = array_merge($exactly, $options);
$exactly = $options['value'] ?? null;
}
$min = $min ?? $options['min'] ?? null;
$max = $max ?? $options['max'] ?? null;
unset($options['value'], $options['min'], $options['max']);
if (null !== $exactly && null === $min && null === $max) {
$min = $max = $exactly;
}
parent::__construct($options, $groups, $payload);
$this->min = $min;
$this->max = $max;
$this->divisibleBy = $divisibleBy ?? $this->divisibleBy;
$this->exactMessage = $exactMessage ?? $this->exactMessage;
$this->minMessage = $minMessage ?? $this->minMessage;
$this->maxMessage = $maxMessage ?? $this->maxMessage;
$this->divisibleByMessage = $divisibleByMessage ?? $this->divisibleByMessage;
if (null === $this->min && null === $this->max && null === $this->divisibleBy) {
throw new MissingOptionsException(sprintf('Either option "min", "max" or "divisibleBy" must be given for constraint "%s".', __CLASS__), ['min', 'max', 'divisibleBy']);
}
}
}

View File

@ -0,0 +1,83 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class CountValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Count) {
throw new UnexpectedTypeException($constraint, Count::class);
}
if (null === $value) {
return;
}
if (!\is_array($value) && !$value instanceof \Countable) {
throw new UnexpectedValueException($value, 'array|\Countable');
}
$count = \count($value);
if (null !== $constraint->max && $count > $constraint->max) {
$exactlyOptionEnabled = $constraint->min == $constraint->max;
$this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->maxMessage)
->setParameter('{{ count }}', $count)
->setParameter('{{ limit }}', $constraint->max)
->setInvalidValue($value)
->setPlural((int) $constraint->max)
->setCode($exactlyOptionEnabled ? Count::NOT_EQUAL_COUNT_ERROR : Count::TOO_MANY_ERROR)
->addViolation();
return;
}
if (null !== $constraint->min && $count < $constraint->min) {
$exactlyOptionEnabled = $constraint->min == $constraint->max;
$this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->minMessage)
->setParameter('{{ count }}', $count)
->setParameter('{{ limit }}', $constraint->min)
->setInvalidValue($value)
->setPlural((int) $constraint->min)
->setCode($exactlyOptionEnabled ? Count::NOT_EQUAL_COUNT_ERROR : Count::TOO_FEW_ERROR)
->addViolation();
return;
}
if (null !== $constraint->divisibleBy) {
$this->context
->getValidator()
->inContext($this->context)
->validate($count, [
new DivisibleBy([
'value' => $constraint->divisibleBy,
'message' => $constraint->divisibleByMessage,
]),
], $this->context->getGroup());
}
}
}

View File

@ -0,0 +1,52 @@
<?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\Constraints;
use Symfony\Component\Intl\Countries;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\LogicException;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Country extends Constraint
{
public const NO_SUCH_COUNTRY_ERROR = '8f900c12-61bd-455d-9398-996cd040f7f0';
protected static $errorNames = [
self::NO_SUCH_COUNTRY_ERROR => 'NO_SUCH_COUNTRY_ERROR',
];
public $message = 'This value is not a valid country.';
public $alpha3 = false;
public function __construct(
array $options = null,
string $message = null,
bool $alpha3 = null,
array $groups = null,
$payload = null
) {
if (!class_exists(Countries::class)) {
throw new LogicException('The Intl component is required to use the Country constraint. Try running "composer require symfony/intl".');
}
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
$this->alpha3 = $alpha3 ?? $this->alpha3;
}
}

View File

@ -0,0 +1,53 @@
<?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\Constraints;
use Symfony\Component\Intl\Countries;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* Validates whether a value is a valid country code.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class CountryValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Country) {
throw new UnexpectedTypeException($constraint, Country::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$value = (string) $value;
if ($constraint->alpha3 ? !Countries::alpha3CodeExists($value) : !Countries::exists($value)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Country::NO_SUCH_COUNTRY_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,106 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\InvalidArgumentException;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class CssColor extends Constraint
{
public const HEX_LONG = 'hex_long';
public const HEX_LONG_WITH_ALPHA = 'hex_long_with_alpha';
public const HEX_SHORT = 'hex_short';
public const HEX_SHORT_WITH_ALPHA = 'hex_short_with_alpha';
public const BASIC_NAMED_COLORS = 'basic_named_colors';
public const EXTENDED_NAMED_COLORS = 'extended_named_colors';
public const SYSTEM_COLORS = 'system_colors';
public const KEYWORDS = 'keywords';
public const RGB = 'rgb';
public const RGBA = 'rgba';
public const HSL = 'hsl';
public const HSLA = 'hsla';
public const INVALID_FORMAT_ERROR = '454ab47b-aacf-4059-8f26-184b2dc9d48d';
protected static $errorNames = [
self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR',
];
/**
* @var string[]
*/
private static $validationModes = [
self::HEX_LONG,
self::HEX_LONG_WITH_ALPHA,
self::HEX_SHORT,
self::HEX_SHORT_WITH_ALPHA,
self::BASIC_NAMED_COLORS,
self::EXTENDED_NAMED_COLORS,
self::SYSTEM_COLORS,
self::KEYWORDS,
self::RGB,
self::RGBA,
self::HSL,
self::HSLA,
];
public $message = 'This value is not a valid CSS color.';
public $formats;
/**
* @param array|string $formats The types of CSS colors allowed (e.g. hexadecimal only, RGB and HSL only, etc.).
*/
public function __construct($formats = [], string $message = null, array $groups = null, $payload = null, array $options = null)
{
$validationModesAsString = implode(', ', self::$validationModes);
if (!$formats) {
$options['value'] = self::$validationModes;
} elseif (\is_array($formats) && \is_string(key($formats))) {
$options = array_merge($formats, $options ?? []);
} elseif (\is_array($formats)) {
if ([] === array_intersect(self::$validationModes, $formats)) {
throw new InvalidArgumentException(sprintf('The "formats" parameter value is not valid. It must contain one or more of the following values: "%s".', $validationModesAsString));
}
$options['value'] = $formats;
} elseif (\is_string($formats)) {
if (!\in_array($formats, self::$validationModes)) {
throw new InvalidArgumentException(sprintf('The "formats" parameter value is not valid. It must contain one or more of the following values: "%s".', $validationModesAsString));
}
$options['value'] = [$formats];
} else {
throw new InvalidArgumentException('The "formats" parameter type is not valid. It should be a string or an array.');
}
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
}
public function getDefaultOption(): string
{
return 'formats';
}
public function getRequiredOptions(): array
{
return ['formats'];
}
}

View File

@ -0,0 +1,86 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*/
class CssColorValidator extends ConstraintValidator
{
private const PATTERN_HEX_LONG = '/^#[0-9a-f]{6}$/i';
private const PATTERN_HEX_LONG_WITH_ALPHA = '/^#[0-9a-f]{8}$/i';
private const PATTERN_HEX_SHORT = '/^#[0-9a-f]{3}$/i';
private const PATTERN_HEX_SHORT_WITH_ALPHA = '/^#[0-9a-f]{4}$/i';
// List comes from https://www.w3.org/wiki/CSS/Properties/color/keywords#Basic_Colors
private const PATTERN_BASIC_NAMED_COLORS = '/^(black|silver|gray|white|maroon|red|purple|fuchsia|green|lime|olive|yellow|navy|blue|teal|aqua)$/i';
// List comes from https://www.w3.org/wiki/CSS/Properties/color/keywords#Extended_colors
private const PATTERN_EXTENDED_NAMED_COLORS = '/^(aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen)$/i';
// List comes from https://drafts.csswg.org/css-color/#css-system-colors
private const PATTERN_SYSTEM_COLORS = '/^(Canvas|CanvasText|LinkText|VisitedText|ActiveText|ButtonFace|ButtonText|ButtonBorder|Field|FieldText|Highlight|HighlightText|SelectedItem|SelectedItemText|Mark|MarkText|GrayText)$/i';
private const PATTERN_KEYWORDS = '/^(transparent|currentColor)$/i';
private const PATTERN_RGB = '/^rgb\(\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d)\s*\)$/i';
private const PATTERN_RGBA = '/^rgba\(\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|0?\.\d+|1(\.0)?)\s*\)$/i';
private const PATTERN_HSL = '/^hsl\(\s*(0|360|35\d|3[0-4]\d|[12]\d\d|0?\d?\d),\s*(0|100|\d{1,2})%,\s*(0|100|\d{1,2})%\s*\)$/i';
private const PATTERN_HSLA = '/^hsla\(\s*(0|360|35\d|3[0-4]\d|[12]\d\d|0?\d?\d),\s*(0|100|\d{1,2})%,\s*(0|100|\d{1,2})%,\s*(0|0?\.\d+|1(\.0)?)\s*\)$/i';
private const COLOR_PATTERNS = [
CssColor::HEX_LONG => self::PATTERN_HEX_LONG,
CssColor::HEX_LONG_WITH_ALPHA => self::PATTERN_HEX_LONG_WITH_ALPHA,
CssColor::HEX_SHORT => self::PATTERN_HEX_SHORT,
CssColor::HEX_SHORT_WITH_ALPHA => self::PATTERN_HEX_SHORT_WITH_ALPHA,
CssColor::BASIC_NAMED_COLORS => self::PATTERN_BASIC_NAMED_COLORS,
CssColor::EXTENDED_NAMED_COLORS => self::PATTERN_EXTENDED_NAMED_COLORS,
CssColor::SYSTEM_COLORS => self::PATTERN_SYSTEM_COLORS,
CssColor::KEYWORDS => self::PATTERN_KEYWORDS,
CssColor::RGB => self::PATTERN_RGB,
CssColor::RGBA => self::PATTERN_RGBA,
CssColor::HSL => self::PATTERN_HSL,
CssColor::HSLA => self::PATTERN_HSLA,
];
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint): void
{
if (!$constraint instanceof CssColor) {
throw new UnexpectedTypeException($constraint, CssColor::class);
}
if (null === $value || '' === $value) {
return;
}
if (!\is_string($value)) {
throw new UnexpectedValueException($value, 'string');
}
$formats = array_flip((array) $constraint->formats);
$formatRegexes = array_intersect_key(self::COLOR_PATTERNS, $formats);
foreach ($formatRegexes as $regex) {
if (preg_match($regex, (string) $value)) {
return;
}
}
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(CssColor::INVALID_FORMAT_ERROR)
->addViolation();
}
}

View File

@ -0,0 +1,46 @@
<?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\Constraints;
use Symfony\Component\Intl\Currencies;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\LogicException;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Miha Vrhovnik <miha.vrhovnik@pagein.si>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Currency extends Constraint
{
public const NO_SUCH_CURRENCY_ERROR = '69945ac1-2db4-405f-bec7-d2772f73df52';
protected static $errorNames = [
self::NO_SUCH_CURRENCY_ERROR => 'NO_SUCH_CURRENCY_ERROR',
];
public $message = 'This value is not a valid currency.';
public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null)
{
if (!class_exists(Currencies::class)) {
throw new LogicException('The Intl component is required to use the Currency constraint. Try running "composer require symfony/intl".');
}
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
}
}

View File

@ -0,0 +1,54 @@
<?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\Constraints;
use Symfony\Component\Intl\Currencies;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* Validates whether a value is a valid currency.
*
* @author Miha Vrhovnik <miha.vrhovnik@pagein.si>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class CurrencyValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Currency) {
throw new UnexpectedTypeException($constraint, Currency::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$value = (string) $value;
if (!Currencies::exists($value)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Currency::NO_SUCH_CURRENCY_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,41 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Date extends Constraint
{
public const INVALID_FORMAT_ERROR = '69819696-02ac-4a99-9ff0-14e127c4d1bc';
public const INVALID_DATE_ERROR = '3c184ce5-b31d-4de7-8b76-326da7b2be93';
protected static $errorNames = [
self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR',
self::INVALID_DATE_ERROR => 'INVALID_DATE_ERROR',
];
public $message = 'This value is not a valid date.';
public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null)
{
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
}
}

View File

@ -0,0 +1,60 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class DateTime extends Constraint
{
public const INVALID_FORMAT_ERROR = '1a9da513-2640-4f84-9b6a-4d99dcddc628';
public const INVALID_DATE_ERROR = 'd52afa47-620d-4d99-9f08-f4d85b36e33c';
public const INVALID_TIME_ERROR = '5e797c9d-74f7-4098-baa3-94390c447b27';
protected static $errorNames = [
self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR',
self::INVALID_DATE_ERROR => 'INVALID_DATE_ERROR',
self::INVALID_TIME_ERROR => 'INVALID_TIME_ERROR',
];
public $format = 'Y-m-d H:i:s';
public $message = 'This value is not a valid datetime.';
/**
* {@inheritdoc}
*
* @param string|array|null $format
*/
public function __construct($format = null, string $message = null, array $groups = null, $payload = null, array $options = [])
{
if (\is_array($format)) {
$options = array_merge($format, $options);
} elseif (null !== $format) {
$options['value'] = $format;
}
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
}
public function getDefaultOption()
{
return 'format';
}
}

View File

@ -0,0 +1,81 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Diego Saint Esteben <diego@saintesteben.me>
*/
class DateTimeValidator extends DateValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof DateTime) {
throw new UnexpectedTypeException($constraint, DateTime::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$value = (string) $value;
\DateTime::createFromFormat($constraint->format, $value);
$errors = \DateTime::getLastErrors();
if (0 < $errors['error_count']) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(DateTime::INVALID_FORMAT_ERROR)
->addViolation();
return;
}
if (str_ends_with($constraint->format, '+')) {
$errors['warnings'] = array_filter($errors['warnings'], function ($warning) {
return 'Trailing data' !== $warning;
});
}
foreach ($errors['warnings'] as $warning) {
if ('The parsed date was invalid' === $warning) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(DateTime::INVALID_DATE_ERROR)
->addViolation();
} elseif ('The parsed time was invalid' === $warning) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(DateTime::INVALID_TIME_ERROR)
->addViolation();
} else {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(DateTime::INVALID_FORMAT_ERROR)
->addViolation();
}
}
}
}

View File

@ -0,0 +1,75 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class DateValidator extends ConstraintValidator
{
public const PATTERN = '/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/';
/**
* Checks whether a date is valid.
*
* @internal
*/
public static function checkDate(int $year, int $month, int $day): bool
{
return checkdate($month, $day, $year);
}
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Date) {
throw new UnexpectedTypeException($constraint, Date::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$value = (string) $value;
if (!preg_match(static::PATTERN, $value, $matches)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Date::INVALID_FORMAT_ERROR)
->addViolation();
return;
}
if (!self::checkDate(
$matches['year'] ?? $matches[1],
$matches['month'] ?? $matches[2],
$matches['day'] ?? $matches[3]
)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Date::INVALID_DATE_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,46 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* Disables auto mapping.
*
* Using the annotations on a property has higher precedence than using it on a class,
* which has higher precedence than any configuration that might be defined outside the class.
*
* @Annotation
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)]
class DisableAutoMapping extends Constraint
{
public function __construct(array $options = null)
{
if (\is_array($options) && \array_key_exists('groups', $options)) {
throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__));
}
parent::__construct($options);
}
/**
* {@inheritdoc}
*/
public function getTargets()
{
return [self::PROPERTY_CONSTRAINT, self::CLASS_CONSTRAINT];
}
}

View File

@ -0,0 +1,30 @@
<?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\Constraints;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Colin O'Dell <colinodell@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class DivisibleBy extends AbstractComparison
{
public const NOT_DIVISIBLE_BY = '6d99d6c3-1464-4ccf-bdc7-14d083cf455c';
protected static $errorNames = [
self::NOT_DIVISIBLE_BY => 'NOT_DIVISIBLE_BY',
];
public $message = 'This value should be a multiple of {{ compared_value }}.';
}

View File

@ -0,0 +1,62 @@
<?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\Constraints;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* Validates that values are a multiple of the given number.
*
* @author Colin O'Dell <colinodell@gmail.com>
*/
class DivisibleByValidator extends AbstractComparisonValidator
{
/**
* {@inheritdoc}
*/
protected function compareValues($value1, $value2)
{
if (!is_numeric($value1)) {
throw new UnexpectedValueException($value1, 'numeric');
}
if (!is_numeric($value2)) {
throw new UnexpectedValueException($value2, 'numeric');
}
if (!$value2 = abs($value2)) {
return false;
}
if (\is_int($value1 = abs($value1)) && \is_int($value2)) {
return 0 === ($value1 % $value2);
}
if (!$remainder = fmod($value1, $value2)) {
return true;
}
if (\is_float($value2) && \INF !== $value2) {
$quotient = $value1 / $value2;
$rounded = round($quotient);
return sprintf('%.12e', $quotient) === sprintf('%.12e', $rounded);
}
return sprintf('%.12e', $value2) === sprintf('%.12e', $remainder);
}
/**
* {@inheritdoc}
*/
protected function getErrorCode()
{
return DivisibleBy::NOT_DIVISIBLE_BY;
}
}

View File

@ -0,0 +1,79 @@
<?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\Constraints;
use Egulias\EmailValidator\EmailValidator as StrictEmailValidator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\InvalidArgumentException;
use Symfony\Component\Validator\Exception\LogicException;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Email extends Constraint
{
public const VALIDATION_MODE_HTML5 = 'html5';
public const VALIDATION_MODE_STRICT = 'strict';
public const VALIDATION_MODE_LOOSE = 'loose';
public const INVALID_FORMAT_ERROR = 'bd79c0ab-ddba-46cc-a703-a7a4b08de310';
protected static $errorNames = [
self::INVALID_FORMAT_ERROR => 'STRICT_CHECK_FAILED_ERROR',
];
/**
* @var string[]
*
* @internal
*/
public static $validationModes = [
self::VALIDATION_MODE_HTML5,
self::VALIDATION_MODE_STRICT,
self::VALIDATION_MODE_LOOSE,
];
public $message = 'This value is not a valid email address.';
public $mode;
public $normalizer;
public function __construct(
array $options = null,
string $message = null,
string $mode = null,
callable $normalizer = null,
array $groups = null,
$payload = null
) {
if (\is_array($options) && \array_key_exists('mode', $options) && !\in_array($options['mode'], self::$validationModes, true)) {
throw new InvalidArgumentException('The "mode" parameter value is not valid.');
}
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
$this->mode = $mode ?? $this->mode;
$this->normalizer = $normalizer ?? $this->normalizer;
if (self::VALIDATION_MODE_STRICT === $this->mode && !class_exists(StrictEmailValidator::class)) {
throw new LogicException(sprintf('The "egulias/email-validator" component is required to use the "%s" constraint in strict mode.', __CLASS__));
}
if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));
}
}
}

View File

@ -0,0 +1,107 @@
<?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\Constraints;
use Egulias\EmailValidator\EmailValidator as EguliasEmailValidator;
use Egulias\EmailValidator\Validation\EmailValidation;
use Egulias\EmailValidator\Validation\NoRFCWarningsValidation;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class EmailValidator extends ConstraintValidator
{
private const PATTERN_HTML5 = '/^[a-zA-Z0-9.!#$%&\'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/';
private const PATTERN_LOOSE = '/^.+\@\S+\.\S+$/';
private const EMAIL_PATTERNS = [
Email::VALIDATION_MODE_LOOSE => self::PATTERN_LOOSE,
Email::VALIDATION_MODE_HTML5 => self::PATTERN_HTML5,
];
private $defaultMode;
public function __construct(string $defaultMode = Email::VALIDATION_MODE_LOOSE)
{
if (!\in_array($defaultMode, Email::$validationModes, true)) {
throw new \InvalidArgumentException('The "defaultMode" parameter value is not valid.');
}
$this->defaultMode = $defaultMode;
}
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Email) {
throw new UnexpectedTypeException($constraint, Email::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$value = (string) $value;
if ('' === $value) {
return;
}
if (null !== $constraint->normalizer) {
$value = ($constraint->normalizer)($value);
}
if (null === $constraint->mode) {
$constraint->mode = $this->defaultMode;
}
if (!\in_array($constraint->mode, Email::$validationModes, true)) {
throw new \InvalidArgumentException(sprintf('The "%s::$mode" parameter value is not valid.', get_debug_type($constraint)));
}
if (Email::VALIDATION_MODE_STRICT === $constraint->mode) {
$strictValidator = new EguliasEmailValidator();
if (interface_exists(EmailValidation::class) && !$strictValidator->isValid($value, new NoRFCWarningsValidation())) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Email::INVALID_FORMAT_ERROR)
->addViolation();
return;
} elseif (!interface_exists(EmailValidation::class) && !$strictValidator->isValid($value, false, true)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Email::INVALID_FORMAT_ERROR)
->addViolation();
return;
}
} elseif (!preg_match(self::EMAIL_PATTERNS[$constraint->mode], $value)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Email::INVALID_FORMAT_ERROR)
->addViolation();
return;
}
}
}

View File

@ -0,0 +1,46 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* Enables auto mapping.
*
* Using the annotations on a property has higher precedence than using it on a class,
* which has higher precedence than any configuration that might be defined outside the class.
*
* @Annotation
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)]
class EnableAutoMapping extends Constraint
{
public function __construct(array $options = null)
{
if (\is_array($options) && \array_key_exists('groups', $options)) {
throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__));
}
parent::__construct($options);
}
/**
* {@inheritdoc}
*/
public function getTargets()
{
return [self::PROPERTY_CONSTRAINT, self::CLASS_CONSTRAINT];
}
}

View File

@ -0,0 +1,31 @@
<?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\Constraints;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class EqualTo extends AbstractComparison
{
public const NOT_EQUAL_ERROR = '478618a7-95ba-473d-9101-cabd45e49115';
protected static $errorNames = [
self::NOT_EQUAL_ERROR => 'NOT_EQUAL_ERROR',
];
public $message = 'This value should be equal to {{ compared_value }}.';
}

View File

@ -0,0 +1,37 @@
<?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\Constraints;
/**
* Validates values are equal (==).
*
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class EqualToValidator extends AbstractComparisonValidator
{
/**
* {@inheritdoc}
*/
protected function compareValues($value1, $value2)
{
return $value1 == $value2;
}
/**
* {@inheritdoc}
*/
protected function getErrorCode()
{
return EqualTo::NOT_EQUAL_ERROR;
}
}

View File

@ -0,0 +1,30 @@
<?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\Constraints;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
abstract class Existence extends Composite
{
public $constraints = [];
public function getDefaultOption()
{
return 'constraints';
}
protected function getCompositeOption()
{
return 'constraints';
}
}

View File

@ -0,0 +1,101 @@
<?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\Constraints;
use Symfony\Component\ExpressionLanguage\Expression as ExpressionObject;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\LogicException;
/**
* @Annotation
* @Target({"CLASS", "PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
class Expression extends Constraint
{
public const EXPRESSION_FAILED_ERROR = '6b3befbc-2f01-4ddf-be21-b57898905284';
protected static $errorNames = [
self::EXPRESSION_FAILED_ERROR => 'EXPRESSION_FAILED_ERROR',
];
public $message = 'This value is not valid.';
public $expression;
public $values = [];
/**
* {@inheritdoc}
*
* @param string|ExpressionObject|array $expression The expression to evaluate or an array of options
*/
public function __construct(
$expression,
string $message = null,
array $values = null,
array $groups = null,
$payload = null,
array $options = []
) {
if (!class_exists(ExpressionLanguage::class)) {
throw new LogicException(sprintf('The "symfony/expression-language" component is required to use the "%s" constraint.', __CLASS__));
}
if (\is_array($expression)) {
$options = array_merge($expression, $options);
} elseif (!\is_string($expression) && !$expression instanceof ExpressionObject) {
throw new \TypeError(sprintf('"%s": Expected argument $expression to be either a string, an instance of "%s" or an array, got "%s".', __METHOD__, ExpressionObject::class, get_debug_type($expression)));
} else {
$options['value'] = $expression;
}
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
$this->values = $values ?? $this->values;
}
/**
* {@inheritdoc}
*/
public function getDefaultOption()
{
return 'expression';
}
/**
* {@inheritdoc}
*/
public function getRequiredOptions()
{
return ['expression'];
}
/**
* {@inheritdoc}
*/
public function getTargets()
{
return [self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT];
}
/**
* {@inheritdoc}
*/
public function validatedBy()
{
return 'validator.expression';
}
}

View File

@ -0,0 +1,51 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Andrey Sevastianov <mrpkmail@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class ExpressionLanguageSyntax extends Constraint
{
public const EXPRESSION_LANGUAGE_SYNTAX_ERROR = '1766a3f3-ff03-40eb-b053-ab7aa23d988a';
protected static $errorNames = [
self::EXPRESSION_LANGUAGE_SYNTAX_ERROR => 'EXPRESSION_LANGUAGE_SYNTAX_ERROR',
];
public $message = 'This value should be a valid expression.';
public $service;
public $allowedVariables;
public function __construct(array $options = null, string $message = null, string $service = null, array $allowedVariables = null, array $groups = null, $payload = null)
{
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
$this->service = $service ?? $this->service;
$this->allowedVariables = $allowedVariables ?? $this->allowedVariables;
}
/**
* {@inheritdoc}
*/
public function validatedBy()
{
return $this->service ?? static::class.'Validator';
}
}

View File

@ -0,0 +1,60 @@
<?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\Constraints;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\ExpressionLanguage\SyntaxError;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* @author Andrey Sevastianov <mrpkmail@gmail.com>
*/
class ExpressionLanguageSyntaxValidator extends ConstraintValidator
{
private $expressionLanguage;
public function __construct(ExpressionLanguage $expressionLanguage = null)
{
$this->expressionLanguage = $expressionLanguage;
}
/**
* {@inheritdoc}
*/
public function validate($expression, Constraint $constraint): void
{
if (!$constraint instanceof ExpressionLanguageSyntax) {
throw new UnexpectedTypeException($constraint, ExpressionLanguageSyntax::class);
}
if (!\is_string($expression)) {
throw new UnexpectedValueException($expression, 'string');
}
if (null === $this->expressionLanguage) {
$this->expressionLanguage = new ExpressionLanguage();
}
try {
$this->expressionLanguage->lint($expression, $constraint->allowedVariables);
} catch (SyntaxError $exception) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ syntax_error }}', $this->formatValue($exception->getMessage()))
->setInvalidValue((string) $expression)
->setCode(ExpressionLanguageSyntax::EXPRESSION_LANGUAGE_SYNTAX_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,61 @@
<?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\Constraints;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@symfony.com>
*/
class ExpressionValidator extends ConstraintValidator
{
private $expressionLanguage;
public function __construct(ExpressionLanguage $expressionLanguage = null)
{
$this->expressionLanguage = $expressionLanguage;
}
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Expression) {
throw new UnexpectedTypeException($constraint, Expression::class);
}
$variables = $constraint->values;
$variables['value'] = $value;
$variables['this'] = $this->context->getObject();
if (!$this->getExpressionLanguage()->evaluate($constraint->expression, $variables)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value, self::OBJECT_TO_STRING))
->setCode(Expression::EXPRESSION_FAILED_ERROR)
->addViolation();
}
}
private function getExpressionLanguage(): ExpressionLanguage
{
if (null === $this->expressionLanguage) {
$this->expressionLanguage = new ExpressionLanguage();
}
return $this->expressionLanguage;
}
}

View File

@ -0,0 +1,174 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @property int $maxSize
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class File extends Constraint
{
// Check the Image constraint for clashes if adding new constants here
public const NOT_FOUND_ERROR = 'd2a3fb6e-7ddc-4210-8fbf-2ab345ce1998';
public const NOT_READABLE_ERROR = 'c20c92a4-5bfa-4202-9477-28e800e0f6ff';
public const EMPTY_ERROR = '5d743385-9775-4aa5-8ff5-495fb1e60137';
public const TOO_LARGE_ERROR = 'df8637af-d466-48c6-a59d-e7126250a654';
public const INVALID_MIME_TYPE_ERROR = '744f00bc-4389-4c74-92de-9a43cde55534';
protected static $errorNames = [
self::NOT_FOUND_ERROR => 'NOT_FOUND_ERROR',
self::NOT_READABLE_ERROR => 'NOT_READABLE_ERROR',
self::EMPTY_ERROR => 'EMPTY_ERROR',
self::TOO_LARGE_ERROR => 'TOO_LARGE_ERROR',
self::INVALID_MIME_TYPE_ERROR => 'INVALID_MIME_TYPE_ERROR',
];
public $binaryFormat;
public $mimeTypes = [];
public $notFoundMessage = 'The file could not be found.';
public $notReadableMessage = 'The file is not readable.';
public $maxSizeMessage = 'The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}.';
public $mimeTypesMessage = 'The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}.';
public $disallowEmptyMessage = 'An empty file is not allowed.';
public $uploadIniSizeErrorMessage = 'The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.';
public $uploadFormSizeErrorMessage = 'The file is too large.';
public $uploadPartialErrorMessage = 'The file was only partially uploaded.';
public $uploadNoFileErrorMessage = 'No file was uploaded.';
public $uploadNoTmpDirErrorMessage = 'No temporary folder was configured in php.ini.';
public $uploadCantWriteErrorMessage = 'Cannot write temporary file to disk.';
public $uploadExtensionErrorMessage = 'A PHP extension caused the upload to fail.';
public $uploadErrorMessage = 'The file could not be uploaded.';
protected $maxSize;
/**
* {@inheritdoc}
*
* @param int|string|null $maxSize
* @param string[]|string|null $mimeTypes
*/
public function __construct(
array $options = null,
$maxSize = null,
bool $binaryFormat = null,
$mimeTypes = null,
string $notFoundMessage = null,
string $notReadableMessage = null,
string $maxSizeMessage = null,
string $mimeTypesMessage = null,
string $disallowEmptyMessage = null,
string $uploadIniSizeErrorMessage = null,
string $uploadFormSizeErrorMessage = null,
string $uploadPartialErrorMessage = null,
string $uploadNoFileErrorMessage = null,
string $uploadNoTmpDirErrorMessage = null,
string $uploadCantWriteErrorMessage = null,
string $uploadExtensionErrorMessage = null,
string $uploadErrorMessage = null,
array $groups = null,
$payload = null
) {
if (null !== $maxSize && !\is_int($maxSize) && !\is_string($maxSize)) {
throw new \TypeError(sprintf('"%s": Expected argument $maxSize to be either null, an integer or a string, got "%s".', __METHOD__, get_debug_type($maxSize)));
}
if (null !== $mimeTypes && !\is_array($mimeTypes) && !\is_string($mimeTypes)) {
throw new \TypeError(sprintf('"%s": Expected argument $mimeTypes to be either null, an array or a string, got "%s".', __METHOD__, get_debug_type($mimeTypes)));
}
parent::__construct($options, $groups, $payload);
$this->maxSize = $maxSize ?? $this->maxSize;
$this->binaryFormat = $binaryFormat ?? $this->binaryFormat;
$this->mimeTypes = $mimeTypes ?? $this->mimeTypes;
$this->notFoundMessage = $notFoundMessage ?? $this->notFoundMessage;
$this->notReadableMessage = $notReadableMessage ?? $this->notReadableMessage;
$this->maxSizeMessage = $maxSizeMessage ?? $this->maxSizeMessage;
$this->mimeTypesMessage = $mimeTypesMessage ?? $this->mimeTypesMessage;
$this->disallowEmptyMessage = $disallowEmptyMessage ?? $this->disallowEmptyMessage;
$this->uploadIniSizeErrorMessage = $uploadIniSizeErrorMessage ?? $this->uploadIniSizeErrorMessage;
$this->uploadFormSizeErrorMessage = $uploadFormSizeErrorMessage ?? $this->uploadFormSizeErrorMessage;
$this->uploadPartialErrorMessage = $uploadPartialErrorMessage ?? $this->uploadPartialErrorMessage;
$this->uploadNoFileErrorMessage = $uploadNoFileErrorMessage ?? $this->uploadNoFileErrorMessage;
$this->uploadNoTmpDirErrorMessage = $uploadNoTmpDirErrorMessage ?? $this->uploadNoTmpDirErrorMessage;
$this->uploadCantWriteErrorMessage = $uploadCantWriteErrorMessage ?? $this->uploadCantWriteErrorMessage;
$this->uploadExtensionErrorMessage = $uploadExtensionErrorMessage ?? $this->uploadExtensionErrorMessage;
$this->uploadErrorMessage = $uploadErrorMessage ?? $this->uploadErrorMessage;
if (null !== $this->maxSize) {
$this->normalizeBinaryFormat($this->maxSize);
}
}
public function __set(string $option, $value)
{
if ('maxSize' === $option) {
$this->normalizeBinaryFormat($value);
return;
}
parent::__set($option, $value);
}
public function __get(string $option)
{
if ('maxSize' === $option) {
return $this->maxSize;
}
return parent::__get($option);
}
public function __isset(string $option)
{
if ('maxSize' === $option) {
return true;
}
return parent::__isset($option);
}
/**
* @param int|string $maxSize
*/
private function normalizeBinaryFormat($maxSize)
{
$factors = [
'k' => 1000,
'ki' => 1 << 10,
'm' => 1000 * 1000,
'mi' => 1 << 20,
'g' => 1000 * 1000 * 1000,
'gi' => 1 << 30,
];
if (ctype_digit((string) $maxSize)) {
$this->maxSize = (int) $maxSize;
$this->binaryFormat = $this->binaryFormat ?? false;
} elseif (preg_match('/^(\d++)('.implode('|', array_keys($factors)).')$/i', $maxSize, $matches)) {
$this->maxSize = $matches[1] * $factors[$unit = strtolower($matches[2])];
$this->binaryFormat = $this->binaryFormat ?? (2 === \strlen($unit));
} else {
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size.', $maxSize));
}
}
}

View File

@ -0,0 +1,252 @@
<?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\Constraints;
use Symfony\Component\HttpFoundation\File\File as FileObject;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Mime\MimeTypes;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\LogicException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FileValidator extends ConstraintValidator
{
public const KB_BYTES = 1000;
public const MB_BYTES = 1000000;
public const KIB_BYTES = 1024;
public const MIB_BYTES = 1048576;
private const SUFFICES = [
1 => 'bytes',
self::KB_BYTES => 'kB',
self::MB_BYTES => 'MB',
self::KIB_BYTES => 'KiB',
self::MIB_BYTES => 'MiB',
];
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof File) {
throw new UnexpectedTypeException($constraint, File::class);
}
if (null === $value || '' === $value) {
return;
}
if ($value instanceof UploadedFile && !$value->isValid()) {
switch ($value->getError()) {
case \UPLOAD_ERR_INI_SIZE:
$iniLimitSize = UploadedFile::getMaxFilesize();
if ($constraint->maxSize && $constraint->maxSize < $iniLimitSize) {
$limitInBytes = $constraint->maxSize;
$binaryFormat = $constraint->binaryFormat;
} else {
$limitInBytes = $iniLimitSize;
$binaryFormat = $constraint->binaryFormat ?? true;
}
[, $limitAsString, $suffix] = $this->factorizeSizes(0, $limitInBytes, $binaryFormat);
$this->context->buildViolation($constraint->uploadIniSizeErrorMessage)
->setParameter('{{ limit }}', $limitAsString)
->setParameter('{{ suffix }}', $suffix)
->setCode((string) \UPLOAD_ERR_INI_SIZE)
->addViolation();
return;
case \UPLOAD_ERR_FORM_SIZE:
$this->context->buildViolation($constraint->uploadFormSizeErrorMessage)
->setCode((string) \UPLOAD_ERR_FORM_SIZE)
->addViolation();
return;
case \UPLOAD_ERR_PARTIAL:
$this->context->buildViolation($constraint->uploadPartialErrorMessage)
->setCode((string) \UPLOAD_ERR_PARTIAL)
->addViolation();
return;
case \UPLOAD_ERR_NO_FILE:
$this->context->buildViolation($constraint->uploadNoFileErrorMessage)
->setCode((string) \UPLOAD_ERR_NO_FILE)
->addViolation();
return;
case \UPLOAD_ERR_NO_TMP_DIR:
$this->context->buildViolation($constraint->uploadNoTmpDirErrorMessage)
->setCode((string) \UPLOAD_ERR_NO_TMP_DIR)
->addViolation();
return;
case \UPLOAD_ERR_CANT_WRITE:
$this->context->buildViolation($constraint->uploadCantWriteErrorMessage)
->setCode((string) \UPLOAD_ERR_CANT_WRITE)
->addViolation();
return;
case \UPLOAD_ERR_EXTENSION:
$this->context->buildViolation($constraint->uploadExtensionErrorMessage)
->setCode((string) \UPLOAD_ERR_EXTENSION)
->addViolation();
return;
default:
$this->context->buildViolation($constraint->uploadErrorMessage)
->setCode((string) $value->getError())
->addViolation();
return;
}
}
if (!is_scalar($value) && !$value instanceof FileObject && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$path = $value instanceof FileObject ? $value->getPathname() : (string) $value;
if (!is_file($path)) {
$this->context->buildViolation($constraint->notFoundMessage)
->setParameter('{{ file }}', $this->formatValue($path))
->setCode(File::NOT_FOUND_ERROR)
->addViolation();
return;
}
if (!is_readable($path)) {
$this->context->buildViolation($constraint->notReadableMessage)
->setParameter('{{ file }}', $this->formatValue($path))
->setCode(File::NOT_READABLE_ERROR)
->addViolation();
return;
}
$sizeInBytes = filesize($path);
$basename = $value instanceof UploadedFile ? $value->getClientOriginalName() : basename($path);
if (0 === $sizeInBytes) {
$this->context->buildViolation($constraint->disallowEmptyMessage)
->setParameter('{{ file }}', $this->formatValue($path))
->setParameter('{{ name }}', $this->formatValue($basename))
->setCode(File::EMPTY_ERROR)
->addViolation();
return;
}
if ($constraint->maxSize) {
$limitInBytes = $constraint->maxSize;
if ($sizeInBytes > $limitInBytes) {
[$sizeAsString, $limitAsString, $suffix] = $this->factorizeSizes($sizeInBytes, $limitInBytes, $constraint->binaryFormat);
$this->context->buildViolation($constraint->maxSizeMessage)
->setParameter('{{ file }}', $this->formatValue($path))
->setParameter('{{ size }}', $sizeAsString)
->setParameter('{{ limit }}', $limitAsString)
->setParameter('{{ suffix }}', $suffix)
->setParameter('{{ name }}', $this->formatValue($basename))
->setCode(File::TOO_LARGE_ERROR)
->addViolation();
return;
}
}
if ($constraint->mimeTypes) {
if ($value instanceof FileObject) {
$mime = $value->getMimeType();
} elseif (class_exists(MimeTypes::class)) {
$mime = MimeTypes::getDefault()->guessMimeType($path);
} elseif (!class_exists(FileObject::class)) {
throw new LogicException('You cannot validate the mime-type of files as the Mime component is not installed. Try running "composer require symfony/mime".');
} else {
$mime = (new FileObject($value))->getMimeType();
}
$mimeTypes = (array) $constraint->mimeTypes;
foreach ($mimeTypes as $mimeType) {
if ($mimeType === $mime) {
return;
}
if ($discrete = strstr($mimeType, '/*', true)) {
if (strstr($mime, '/', true) === $discrete) {
return;
}
}
}
$this->context->buildViolation($constraint->mimeTypesMessage)
->setParameter('{{ file }}', $this->formatValue($path))
->setParameter('{{ type }}', $this->formatValue($mime))
->setParameter('{{ types }}', $this->formatValues($mimeTypes))
->setParameter('{{ name }}', $this->formatValue($basename))
->setCode(File::INVALID_MIME_TYPE_ERROR)
->addViolation();
}
}
private static function moreDecimalsThan(string $double, int $numberOfDecimals): bool
{
return \strlen($double) > \strlen(round($double, $numberOfDecimals));
}
/**
* Convert the limit to the smallest possible number
* (i.e. try "MB", then "kB", then "bytes").
*
* @param int|float $limit
*/
private function factorizeSizes(int $size, $limit, bool $binaryFormat): array
{
if ($binaryFormat) {
$coef = self::MIB_BYTES;
$coefFactor = self::KIB_BYTES;
} else {
$coef = self::MB_BYTES;
$coefFactor = self::KB_BYTES;
}
$limitAsString = (string) ($limit / $coef);
// Restrict the limit to 2 decimals (without rounding! we
// need the precise value)
while (self::moreDecimalsThan($limitAsString, 2)) {
$coef /= $coefFactor;
$limitAsString = (string) ($limit / $coef);
}
// Convert size to the same measure, but round to 2 decimals
$sizeAsString = (string) round($size / $coef, 2);
// If the size and limit produce the same string output
// (due to rounding), reduce the coefficient
while ($sizeAsString === $limitAsString) {
$coef /= $coefFactor;
$limitAsString = (string) ($limit / $coef);
$sizeAsString = (string) round($size / $coef, 2);
}
return [$sizeAsString, $limitAsString, self::SUFFICES[$coef]];
}
}

View File

@ -0,0 +1,31 @@
<?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\Constraints;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class GreaterThan extends AbstractComparison
{
public const TOO_LOW_ERROR = '778b7ae0-84d3-481a-9dec-35fdb64b1d78';
protected static $errorNames = [
self::TOO_LOW_ERROR => 'TOO_LOW_ERROR',
];
public $message = 'This value should be greater than {{ compared_value }}.';
}

View File

@ -0,0 +1,31 @@
<?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\Constraints;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class GreaterThanOrEqual extends AbstractComparison
{
public const TOO_LOW_ERROR = 'ea4e51d1-3342-48bd-87f1-9e672cd90cad';
protected static $errorNames = [
self::TOO_LOW_ERROR => 'TOO_LOW_ERROR',
];
public $message = 'This value should be greater than or equal to {{ compared_value }}.';
}

View File

@ -0,0 +1,37 @@
<?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\Constraints;
/**
* Validates values are greater than or equal to the previous (>=).
*
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class GreaterThanOrEqualValidator extends AbstractComparisonValidator
{
/**
* {@inheritdoc}
*/
protected function compareValues($value1, $value2)
{
return null === $value2 || $value1 >= $value2;
}
/**
* {@inheritdoc}
*/
protected function getErrorCode()
{
return GreaterThanOrEqual::TOO_LOW_ERROR;
}
}

View File

@ -0,0 +1,37 @@
<?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\Constraints;
/**
* Validates values are greater than the previous (>).
*
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class GreaterThanValidator extends AbstractComparisonValidator
{
/**
* {@inheritdoc}
*/
protected function compareValues($value1, $value2)
{
return null === $value2 || $value1 > $value2;
}
/**
* {@inheritdoc}
*/
protected function getErrorCode()
{
return GreaterThan::TOO_LOW_ERROR;
}
}

View File

@ -0,0 +1,90 @@
<?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\Constraints;
/**
* A sequence of validation groups.
*
* When validating a group sequence, each group will only be validated if all
* of the previous groups in the sequence succeeded. For example:
*
* $validator->validate($address, null, new GroupSequence(['Basic', 'Strict']));
*
* In the first step, all constraints that belong to the group "Basic" will be
* validated. If none of the constraints fail, the validator will then validate
* the constraints in group "Strict". This is useful, for example, if "Strict"
* contains expensive checks that require a lot of CPU or slow, external
* services. You usually don't want to run expensive checks if any of the cheap
* checks fail.
*
* When adding metadata to a class, you can override the "Default" group of
* that class with a group sequence:
* /**
* * @GroupSequence({"Address", "Strict"})
* *\/
* class Address
* {
* // ...
* }
*
* Whenever you validate that object in the "Default" group, the group sequence
* will be validated:
*
* $validator->validate($address);
*
* If you want to execute the constraints of the "Default" group for a class
* with an overridden default group, pass the class name as group name instead:
*
* $validator->validate($address, null, "Address")
*
* @Annotation
* @Target({"CLASS", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
class GroupSequence
{
/**
* The groups in the sequence.
*
* @var array<int, string|string[]|GroupSequence>
*/
public $groups;
/**
* The group in which cascaded objects are validated when validating
* this sequence.
*
* By default, cascaded objects are validated in each of the groups of
* the sequence.
*
* If a class has a group sequence attached, that sequence replaces the
* "Default" group. When validating that class in the "Default" group, the
* group sequence is used instead, but still the "Default" group should be
* cascaded to other objects.
*
* @var string|GroupSequence
*/
public $cascadedGroup;
/**
* Creates a new group sequence.
*
* @param array<string|string[]|GroupSequence> $groups The groups in the sequence
*/
public function __construct(array $groups)
{
// Support for Doctrine annotations
$this->groups = $groups['value'] ?? $groups;
}
}

View File

@ -0,0 +1,25 @@
<?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\Constraints;
/**
* Annotation to define a group sequence provider.
*
* @Annotation
* @Target({"CLASS", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
class GroupSequenceProvider
{
}

View File

@ -0,0 +1,46 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Dmitrii Poddubnyi <dpoddubny@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Hostname extends Constraint
{
public const INVALID_HOSTNAME_ERROR = '7057ffdb-0af4-4f7e-bd5e-e9acfa6d7a2d';
protected static $errorNames = [
self::INVALID_HOSTNAME_ERROR => 'INVALID_HOSTNAME_ERROR',
];
public $message = 'This value is not a valid hostname.';
public $requireTld = true;
public function __construct(
array $options = null,
string $message = null,
bool $requireTld = null,
array $groups = null,
$payload = null
) {
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
$this->requireTld = $requireTld ?? $this->requireTld;
}
}

View File

@ -0,0 +1,69 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* @author Dmitrii Poddubnyi <dpoddubny@gmail.com>
*/
class HostnameValidator extends ConstraintValidator
{
/**
* https://tools.ietf.org/html/rfc2606.
*/
private const RESERVED_TLDS = [
'example',
'invalid',
'localhost',
'test',
];
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Hostname) {
throw new UnexpectedTypeException($constraint, Hostname::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$value = (string) $value;
if ('' === $value) {
return;
}
if (!$this->isValid($value) || ($constraint->requireTld && !$this->hasValidTld($value))) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Hostname::INVALID_HOSTNAME_ERROR)
->addViolation();
}
}
private function isValid(string $domain): bool
{
return false !== filter_var($domain, \FILTER_VALIDATE_DOMAIN, \FILTER_FLAG_HOSTNAME);
}
private function hasValidTld(string $domain): bool
{
return false !== strpos($domain, '.') && !\in_array(substr($domain, strrpos($domain, '.') + 1), self::RESERVED_TLDS, true);
}
}

View File

@ -0,0 +1,49 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Manuel Reinhard <manu@sprain.ch>
* @author Michael Schummel
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Iban extends Constraint
{
public const INVALID_COUNTRY_CODE_ERROR = 'de78ee2c-bd50-44e2-aec8-3d8228aeadb9';
public const INVALID_CHARACTERS_ERROR = '8d3d85e4-784f-4719-a5bc-d9e40d45a3a5';
public const CHECKSUM_FAILED_ERROR = 'b9401321-f9bf-4dcb-83c1-f31094440795';
public const INVALID_FORMAT_ERROR = 'c8d318f1-2ecc-41ba-b983-df70d225cf5a';
public const NOT_SUPPORTED_COUNTRY_CODE_ERROR = 'e2c259f3-4b46-48e6-b72e-891658158ec8';
protected static $errorNames = [
self::INVALID_COUNTRY_CODE_ERROR => 'INVALID_COUNTRY_CODE_ERROR',
self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR',
self::CHECKSUM_FAILED_ERROR => 'CHECKSUM_FAILED_ERROR',
self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR',
self::NOT_SUPPORTED_COUNTRY_CODE_ERROR => 'NOT_SUPPORTED_COUNTRY_CODE_ERROR',
];
public $message = 'This is not a valid International Bank Account Number (IBAN).';
public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null)
{
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
}
}

View File

@ -0,0 +1,259 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* @author Manuel Reinhard <manu@sprain.ch>
* @author Michael Schummel
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @see http://www.michael-schummel.de/2007/10/05/iban-prufung-mit-php/
*/
class IbanValidator extends ConstraintValidator
{
/**
* IBAN country specific formats.
*
* The first 2 characters from an IBAN format are the two-character ISO country code.
* The following 2 characters represent the check digits calculated from the rest of the IBAN characters.
* The rest are up to thirty alphanumeric characters for
* a BBAN (Basic Bank Account Number) which has a fixed length per country and,
* included within it, a bank identifier with a fixed position and a fixed length per country
*
* @see https://www.swift.com/sites/default/files/resources/iban_registry.pdf
*/
private const FORMATS = [
'AD' => 'AD\d{2}\d{4}\d{4}[\dA-Z]{12}', // Andorra
'AE' => 'AE\d{2}\d{3}\d{16}', // United Arab Emirates
'AL' => 'AL\d{2}\d{8}[\dA-Z]{16}', // Albania
'AO' => 'AO\d{2}\d{21}', // Angola
'AT' => 'AT\d{2}\d{5}\d{11}', // Austria
'AX' => 'FI\d{2}\d{6}\d{7}\d{1}', // Aland Islands
'AZ' => 'AZ\d{2}[A-Z]{4}[\dA-Z]{20}', // Azerbaijan
'BA' => 'BA\d{2}\d{3}\d{3}\d{8}\d{2}', // Bosnia and Herzegovina
'BE' => 'BE\d{2}\d{3}\d{7}\d{2}', // Belgium
'BF' => 'BF\d{2}\d{23}', // Burkina Faso
'BG' => 'BG\d{2}[A-Z]{4}\d{4}\d{2}[\dA-Z]{8}', // Bulgaria
'BH' => 'BH\d{2}[A-Z]{4}[\dA-Z]{14}', // Bahrain
'BI' => 'BI\d{2}\d{12}', // Burundi
'BJ' => 'BJ\d{2}[A-Z]{1}\d{23}', // Benin
'BY' => 'BY\d{2}[\dA-Z]{4}\d{4}[\dA-Z]{16}', // Belarus - https://bank.codes/iban/structure/belarus/
'BL' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Saint Barthelemy
'BR' => 'BR\d{2}\d{8}\d{5}\d{10}[A-Z][\dA-Z]', // Brazil
'CG' => 'CG\d{2}\d{23}', // Congo
'CH' => 'CH\d{2}\d{5}[\dA-Z]{12}', // Switzerland
'CI' => 'CI\d{2}[A-Z]{1}\d{23}', // Ivory Coast
'CM' => 'CM\d{2}\d{23}', // Cameron
'CR' => 'CR\d{2}0\d{3}\d{14}', // Costa Rica
'CV' => 'CV\d{2}\d{21}', // Cape Verde
'CY' => 'CY\d{2}\d{3}\d{5}[\dA-Z]{16}', // Cyprus
'CZ' => 'CZ\d{2}\d{20}', // Czech Republic
'DE' => 'DE\d{2}\d{8}\d{10}', // Germany
'DO' => 'DO\d{2}[\dA-Z]{4}\d{20}', // Dominican Republic
'DK' => 'DK\d{2}\d{4}\d{10}', // Denmark
'DZ' => 'DZ\d{2}\d{20}', // Algeria
'EE' => 'EE\d{2}\d{2}\d{2}\d{11}\d{1}', // Estonia
'ES' => 'ES\d{2}\d{4}\d{4}\d{1}\d{1}\d{10}', // Spain (also includes Canary Islands, Ceuta and Melilla)
'FI' => 'FI\d{2}\d{6}\d{7}\d{1}', // Finland
'FO' => 'FO\d{2}\d{4}\d{9}\d{1}', // Faroe Islands
'FR' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France
'GF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // French Guyana
'GB' => 'GB\d{2}[A-Z]{4}\d{6}\d{8}', // United Kingdom of Great Britain and Northern Ireland
'GE' => 'GE\d{2}[A-Z]{2}\d{16}', // Georgia
'GI' => 'GI\d{2}[A-Z]{4}[\dA-Z]{15}', // Gibraltar
'GL' => 'GL\d{2}\d{4}\d{9}\d{1}', // Greenland
'GP' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Guadeloupe
'GR' => 'GR\d{2}\d{3}\d{4}[\dA-Z]{16}', // Greece
'GT' => 'GT\d{2}[\dA-Z]{4}[\dA-Z]{20}', // Guatemala
'HR' => 'HR\d{2}\d{7}\d{10}', // Croatia
'HU' => 'HU\d{2}\d{3}\d{4}\d{1}\d{15}\d{1}', // Hungary
'IE' => 'IE\d{2}[A-Z]{4}\d{6}\d{8}', // Ireland
'IL' => 'IL\d{2}\d{3}\d{3}\d{13}', // Israel
'IR' => 'IR\d{2}\d{22}', // Iran
'IS' => 'IS\d{2}\d{4}\d{2}\d{6}\d{10}', // Iceland
'IT' => 'IT\d{2}[A-Z]{1}\d{5}\d{5}[\dA-Z]{12}', // Italy
'JO' => 'JO\d{2}[A-Z]{4}\d{4}[\dA-Z]{18}', // Jordan
'KW' => 'KW\d{2}[A-Z]{4}\d{22}', // KUWAIT
'KZ' => 'KZ\d{2}\d{3}[\dA-Z]{13}', // Kazakhstan
'LB' => 'LB\d{2}\d{4}[\dA-Z]{20}', // LEBANON
'LI' => 'LI\d{2}\d{5}[\dA-Z]{12}', // Liechtenstein (Principality of)
'LT' => 'LT\d{2}\d{5}\d{11}', // Lithuania
'LU' => 'LU\d{2}\d{3}[\dA-Z]{13}', // Luxembourg
'LV' => 'LV\d{2}[A-Z]{4}[\dA-Z]{13}', // Latvia
'MC' => 'MC\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Monaco
'MD' => 'MD\d{2}[\dA-Z]{2}[\dA-Z]{18}', // Moldova
'ME' => 'ME\d{2}\d{3}\d{13}\d{2}', // Montenegro
'MF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Saint Martin (French part)
'MG' => 'MG\d{2}\d{23}', // Madagascar
'MK' => 'MK\d{2}\d{3}[\dA-Z]{10}\d{2}', // Macedonia, Former Yugoslav Republic of
'ML' => 'ML\d{2}[A-Z]{1}\d{23}', // Mali
'MQ' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Martinique
'MR' => 'MR13\d{5}\d{5}\d{11}\d{2}', // Mauritania
'MT' => 'MT\d{2}[A-Z]{4}\d{5}[\dA-Z]{18}', // Malta
'MU' => 'MU\d{2}[A-Z]{4}\d{2}\d{2}\d{12}\d{3}[A-Z]{3}', // Mauritius
'MZ' => 'MZ\d{2}\d{21}', // Mozambique
'NC' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // New Caledonia
'NL' => 'NL\d{2}[A-Z]{4}\d{10}', // The Netherlands
'NO' => 'NO\d{2}\d{4}\d{6}\d{1}', // Norway
'PF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // French Polynesia
'PK' => 'PK\d{2}[A-Z]{4}[\dA-Z]{16}', // Pakistan
'PL' => 'PL\d{2}\d{8}\d{16}', // Poland
'PM' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Saint Pierre et Miquelon
'PS' => 'PS\d{2}[A-Z]{4}[\dA-Z]{21}', // Palestine, State of
'PT' => 'PT\d{2}\d{4}\d{4}\d{11}\d{2}', // Portugal (plus Azores and Madeira)
'QA' => 'QA\d{2}[A-Z]{4}[\dA-Z]{21}', // Qatar
'RE' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Reunion
'RO' => 'RO\d{2}[A-Z]{4}[\dA-Z]{16}', // Romania
'RS' => 'RS\d{2}\d{3}\d{13}\d{2}', // Serbia
'SA' => 'SA\d{2}\d{2}[\dA-Z]{18}', // Saudi Arabia
'SE' => 'SE\d{2}\d{3}\d{16}\d{1}', // Sweden
'SI' => 'SI\d{2}\d{5}\d{8}\d{2}', // Slovenia
'SK' => 'SK\d{2}\d{4}\d{6}\d{10}', // Slovak Republic
'SM' => 'SM\d{2}[A-Z]{1}\d{5}\d{5}[\dA-Z]{12}', // San Marino
'SN' => 'SN\d{2}[A-Z]{1}\d{23}', // Senegal
'TF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // French Southern Territories
'TL' => 'TL\d{2}\d{3}\d{14}\d{2}', // Timor-Leste
'TN' => 'TN59\d{2}\d{3}\d{13}\d{2}', // Tunisia
'TR' => 'TR\d{2}\d{5}[\dA-Z]{1}[\dA-Z]{16}', // Turkey
'UA' => 'UA\d{2}\d{6}[\dA-Z]{19}', // Ukraine
'VA' => 'VA\d{2}\d{3}\d{15}', // Vatican City State
'VG' => 'VG\d{2}[A-Z]{4}\d{16}', // Virgin Islands, British
'WF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Wallis and Futuna Islands
'XK' => 'XK\d{2}\d{4}\d{10}\d{2}', // Republic of Kosovo
'YT' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Mayotte
];
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Iban) {
throw new UnexpectedTypeException($constraint, Iban::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$value = (string) $value;
// Remove spaces and convert to uppercase
$canonicalized = str_replace(' ', '', strtoupper($value));
// The IBAN must contain only digits and characters...
if (!ctype_alnum($canonicalized)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Iban::INVALID_CHARACTERS_ERROR)
->addViolation();
return;
}
// ...start with a two-letter country code
$countryCode = substr($canonicalized, 0, 2);
if (!ctype_alpha($countryCode)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Iban::INVALID_COUNTRY_CODE_ERROR)
->addViolation();
return;
}
// ...have a format available
if (!\array_key_exists($countryCode, self::FORMATS)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Iban::NOT_SUPPORTED_COUNTRY_CODE_ERROR)
->addViolation();
return;
}
// ...and have a valid format
if (!preg_match('/^'.self::FORMATS[$countryCode].'$/', $canonicalized)
) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Iban::INVALID_FORMAT_ERROR)
->addViolation();
return;
}
// Move the first four characters to the end
// e.g. CH93 0076 2011 6238 5295 7
// -> 0076 2011 6238 5295 7 CH93
$canonicalized = substr($canonicalized, 4).substr($canonicalized, 0, 4);
// Convert all remaining letters to their ordinals
// The result is an integer, which is too large for PHP's int
// data type, so we store it in a string instead.
// e.g. 0076 2011 6238 5295 7 CH93
// -> 0076 2011 6238 5295 7 121893
$checkSum = self::toBigInt($canonicalized);
// Do a modulo-97 operation on the large integer
// We cannot use PHP's modulo operator, so we calculate the
// modulo step-wisely instead
if (1 !== self::bigModulo97($checkSum)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Iban::CHECKSUM_FAILED_ERROR)
->addViolation();
}
}
private static function toBigInt(string $string): string
{
$chars = str_split($string);
$bigInt = '';
foreach ($chars as $char) {
// Convert uppercase characters to ordinals, starting with 10 for "A"
if (ctype_upper($char)) {
$bigInt .= (\ord($char) - 55);
continue;
}
// Simply append digits
$bigInt .= $char;
}
return $bigInt;
}
private static function bigModulo97(string $bigInt): int
{
$parts = str_split($bigInt, 7);
$rest = 0;
foreach ($parts as $part) {
$rest = ($rest.$part) % 97;
}
return $rest;
}
}

View File

@ -0,0 +1,31 @@
<?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\Constraints;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class IdenticalTo extends AbstractComparison
{
public const NOT_IDENTICAL_ERROR = '2a8cc50f-58a2-4536-875e-060a2ce69ed5';
protected static $errorNames = [
self::NOT_IDENTICAL_ERROR => 'NOT_IDENTICAL_ERROR',
];
public $message = 'This value should be identical to {{ compared_value_type }} {{ compared_value }}.';
}

View File

@ -0,0 +1,37 @@
<?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\Constraints;
/**
* Validates values are identical (===).
*
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class IdenticalToValidator extends AbstractComparisonValidator
{
/**
* {@inheritdoc}
*/
protected function compareValues($value1, $value2)
{
return $value1 === $value2;
}
/**
* {@inheritdoc}
*/
protected function getErrorCode()
{
return IdenticalTo::NOT_IDENTICAL_ERROR;
}
}

View File

@ -0,0 +1,193 @@
<?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\Constraints;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Benjamin Dulau <benjamin.dulau@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Image extends File
{
public const SIZE_NOT_DETECTED_ERROR = '6d55c3f4-e58e-4fe3-91ee-74b492199956';
public const TOO_WIDE_ERROR = '7f87163d-878f-47f5-99ba-a8eb723a1ab2';
public const TOO_NARROW_ERROR = '9afbd561-4f90-4a27-be62-1780fc43604a';
public const TOO_HIGH_ERROR = '7efae81c-4877-47ba-aa65-d01ccb0d4645';
public const TOO_LOW_ERROR = 'aef0cb6a-c07f-4894-bc08-1781420d7b4c';
public const TOO_FEW_PIXEL_ERROR = '1b06b97d-ae48-474e-978f-038a74854c43';
public const TOO_MANY_PIXEL_ERROR = 'ee0804e8-44db-4eac-9775-be91aaf72ce1';
public const RATIO_TOO_BIG_ERROR = '70cafca6-168f-41c9-8c8c-4e47a52be643';
public const RATIO_TOO_SMALL_ERROR = '59b8c6ef-bcf2-4ceb-afff-4642ed92f12e';
public const SQUARE_NOT_ALLOWED_ERROR = '5d41425b-facb-47f7-a55a-de9fbe45cb46';
public const LANDSCAPE_NOT_ALLOWED_ERROR = '6f895685-7cf2-4d65-b3da-9029c5581d88';
public const PORTRAIT_NOT_ALLOWED_ERROR = '65608156-77da-4c79-a88c-02ef6d18c782';
public const CORRUPTED_IMAGE_ERROR = '5d4163f3-648f-4e39-87fd-cc5ea7aad2d1';
// Include the mapping from the base class
protected static $errorNames = [
self::NOT_FOUND_ERROR => 'NOT_FOUND_ERROR',
self::NOT_READABLE_ERROR => 'NOT_READABLE_ERROR',
self::EMPTY_ERROR => 'EMPTY_ERROR',
self::TOO_LARGE_ERROR => 'TOO_LARGE_ERROR',
self::INVALID_MIME_TYPE_ERROR => 'INVALID_MIME_TYPE_ERROR',
self::SIZE_NOT_DETECTED_ERROR => 'SIZE_NOT_DETECTED_ERROR',
self::TOO_WIDE_ERROR => 'TOO_WIDE_ERROR',
self::TOO_NARROW_ERROR => 'TOO_NARROW_ERROR',
self::TOO_HIGH_ERROR => 'TOO_HIGH_ERROR',
self::TOO_LOW_ERROR => 'TOO_LOW_ERROR',
self::TOO_FEW_PIXEL_ERROR => 'TOO_FEW_PIXEL_ERROR',
self::TOO_MANY_PIXEL_ERROR => 'TOO_MANY_PIXEL_ERROR',
self::RATIO_TOO_BIG_ERROR => 'RATIO_TOO_BIG_ERROR',
self::RATIO_TOO_SMALL_ERROR => 'RATIO_TOO_SMALL_ERROR',
self::SQUARE_NOT_ALLOWED_ERROR => 'SQUARE_NOT_ALLOWED_ERROR',
self::LANDSCAPE_NOT_ALLOWED_ERROR => 'LANDSCAPE_NOT_ALLOWED_ERROR',
self::PORTRAIT_NOT_ALLOWED_ERROR => 'PORTRAIT_NOT_ALLOWED_ERROR',
self::CORRUPTED_IMAGE_ERROR => 'CORRUPTED_IMAGE_ERROR',
];
public $mimeTypes = 'image/*';
public $minWidth;
public $maxWidth;
public $maxHeight;
public $minHeight;
public $maxRatio;
public $minRatio;
public $minPixels;
public $maxPixels;
public $allowSquare = true;
public $allowLandscape = true;
public $allowPortrait = true;
public $detectCorrupted = false;
// The constant for a wrong MIME type is taken from the parent class.
public $mimeTypesMessage = 'This file is not a valid image.';
public $sizeNotDetectedMessage = 'The size of the image could not be detected.';
public $maxWidthMessage = 'The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px.';
public $minWidthMessage = 'The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px.';
public $maxHeightMessage = 'The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px.';
public $minHeightMessage = 'The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px.';
public $minPixelsMessage = 'The image has too few pixels ({{ pixels }} pixels). Minimum amount expected is {{ min_pixels }} pixels.';
public $maxPixelsMessage = 'The image has too many pixels ({{ pixels }} pixels). Maximum amount expected is {{ max_pixels }} pixels.';
public $maxRatioMessage = 'The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}.';
public $minRatioMessage = 'The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}.';
public $allowSquareMessage = 'The image is square ({{ width }}x{{ height }}px). Square images are not allowed.';
public $allowLandscapeMessage = 'The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed.';
public $allowPortraitMessage = 'The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed.';
public $corruptedMessage = 'The image file is corrupted.';
/**
* {@inheritdoc}
*
* @param int|float $maxRatio
* @param int|float $minRatio
* @param int|float $minPixels
* @param int|float $maxPixels
*/
public function __construct(
array $options = null,
$maxSize = null,
bool $binaryFormat = null,
array $mimeTypes = null,
int $minWidth = null,
int $maxWidth = null,
int $maxHeight = null,
int $minHeight = null,
$maxRatio = null,
$minRatio = null,
$minPixels = null,
$maxPixels = null,
bool $allowSquare = null,
bool $allowLandscape = null,
bool $allowPortrait = null,
bool $detectCorrupted = null,
string $notFoundMessage = null,
string $notReadableMessage = null,
string $maxSizeMessage = null,
string $mimeTypesMessage = null,
string $disallowEmptyMessage = null,
string $uploadIniSizeErrorMessage = null,
string $uploadFormSizeErrorMessage = null,
string $uploadPartialErrorMessage = null,
string $uploadNoFileErrorMessage = null,
string $uploadNoTmpDirErrorMessage = null,
string $uploadCantWriteErrorMessage = null,
string $uploadExtensionErrorMessage = null,
string $uploadErrorMessage = null,
string $sizeNotDetectedMessage = null,
string $maxWidthMessage = null,
string $minWidthMessage = null,
string $maxHeightMessage = null,
string $minHeightMessage = null,
string $minPixelsMessage = null,
string $maxPixelsMessage = null,
string $maxRatioMessage = null,
string $minRatioMessage = null,
string $allowSquareMessage = null,
string $allowLandscapeMessage = null,
string $allowPortraitMessage = null,
string $corruptedMessage = null,
array $groups = null,
$payload = null
) {
parent::__construct(
$options,
$maxSize,
$binaryFormat,
$mimeTypes,
$notFoundMessage,
$notReadableMessage,
$maxSizeMessage,
$mimeTypesMessage,
$disallowEmptyMessage,
$uploadIniSizeErrorMessage,
$uploadFormSizeErrorMessage,
$uploadPartialErrorMessage,
$uploadNoFileErrorMessage,
$uploadNoTmpDirErrorMessage,
$uploadCantWriteErrorMessage,
$uploadExtensionErrorMessage,
$uploadErrorMessage,
$groups,
$payload
);
$this->minWidth = $minWidth ?? $this->minWidth;
$this->maxWidth = $maxWidth ?? $this->maxWidth;
$this->maxHeight = $maxHeight ?? $this->maxHeight;
$this->minHeight = $minHeight ?? $this->minHeight;
$this->maxRatio = $maxRatio ?? $this->maxRatio;
$this->minRatio = $minRatio ?? $this->minRatio;
$this->minPixels = $minPixels ?? $this->minPixels;
$this->maxPixels = $maxPixels ?? $this->maxPixels;
$this->allowSquare = $allowSquare ?? $this->allowSquare;
$this->allowLandscape = $allowLandscape ?? $this->allowLandscape;
$this->allowPortrait = $allowPortrait ?? $this->allowPortrait;
$this->detectCorrupted = $detectCorrupted ?? $this->detectCorrupted;
$this->sizeNotDetectedMessage = $sizeNotDetectedMessage ?? $this->sizeNotDetectedMessage;
$this->maxWidthMessage = $maxWidthMessage ?? $this->maxWidthMessage;
$this->minWidthMessage = $minWidthMessage ?? $this->minWidthMessage;
$this->maxHeightMessage = $maxHeightMessage ?? $this->maxHeightMessage;
$this->minHeightMessage = $minHeightMessage ?? $this->minHeightMessage;
$this->minPixelsMessage = $minPixelsMessage ?? $this->minPixelsMessage;
$this->maxPixelsMessage = $maxPixelsMessage ?? $this->maxPixelsMessage;
$this->maxRatioMessage = $maxRatioMessage ?? $this->maxRatioMessage;
$this->minRatioMessage = $minRatioMessage ?? $this->minRatioMessage;
$this->allowSquareMessage = $allowSquareMessage ?? $this->allowSquareMessage;
$this->allowLandscapeMessage = $allowLandscapeMessage ?? $this->allowLandscapeMessage;
$this->allowPortraitMessage = $allowPortraitMessage ?? $this->allowPortraitMessage;
$this->corruptedMessage = $corruptedMessage ?? $this->corruptedMessage;
}
}

View File

@ -0,0 +1,237 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\LogicException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Validates whether a value is a valid image file and is valid
* against minWidth, maxWidth, minHeight and maxHeight constraints.
*
* @author Benjamin Dulau <benjamin.dulau@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ImageValidator extends FileValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Image) {
throw new UnexpectedTypeException($constraint, Image::class);
}
$violations = \count($this->context->getViolations());
parent::validate($value, $constraint);
$failed = \count($this->context->getViolations()) !== $violations;
if ($failed || null === $value || '' === $value) {
return;
}
if (null === $constraint->minWidth && null === $constraint->maxWidth
&& null === $constraint->minHeight && null === $constraint->maxHeight
&& null === $constraint->minPixels && null === $constraint->maxPixels
&& null === $constraint->minRatio && null === $constraint->maxRatio
&& $constraint->allowSquare && $constraint->allowLandscape && $constraint->allowPortrait
&& !$constraint->detectCorrupted) {
return;
}
$size = @getimagesize($value);
if (empty($size) || (0 === $size[0]) || (0 === $size[1])) {
$this->context->buildViolation($constraint->sizeNotDetectedMessage)
->setCode(Image::SIZE_NOT_DETECTED_ERROR)
->addViolation();
return;
}
$width = $size[0];
$height = $size[1];
if ($constraint->minWidth) {
if (!ctype_digit((string) $constraint->minWidth)) {
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid minimum width.', $constraint->minWidth));
}
if ($width < $constraint->minWidth) {
$this->context->buildViolation($constraint->minWidthMessage)
->setParameter('{{ width }}', $width)
->setParameter('{{ min_width }}', $constraint->minWidth)
->setCode(Image::TOO_NARROW_ERROR)
->addViolation();
return;
}
}
if ($constraint->maxWidth) {
if (!ctype_digit((string) $constraint->maxWidth)) {
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum width.', $constraint->maxWidth));
}
if ($width > $constraint->maxWidth) {
$this->context->buildViolation($constraint->maxWidthMessage)
->setParameter('{{ width }}', $width)
->setParameter('{{ max_width }}', $constraint->maxWidth)
->setCode(Image::TOO_WIDE_ERROR)
->addViolation();
return;
}
}
if ($constraint->minHeight) {
if (!ctype_digit((string) $constraint->minHeight)) {
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid minimum height.', $constraint->minHeight));
}
if ($height < $constraint->minHeight) {
$this->context->buildViolation($constraint->minHeightMessage)
->setParameter('{{ height }}', $height)
->setParameter('{{ min_height }}', $constraint->minHeight)
->setCode(Image::TOO_LOW_ERROR)
->addViolation();
return;
}
}
if ($constraint->maxHeight) {
if (!ctype_digit((string) $constraint->maxHeight)) {
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum height.', $constraint->maxHeight));
}
if ($height > $constraint->maxHeight) {
$this->context->buildViolation($constraint->maxHeightMessage)
->setParameter('{{ height }}', $height)
->setParameter('{{ max_height }}', $constraint->maxHeight)
->setCode(Image::TOO_HIGH_ERROR)
->addViolation();
}
}
$pixels = $width * $height;
if (null !== $constraint->minPixels) {
if (!ctype_digit((string) $constraint->minPixels)) {
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid minimum amount of pixels.', $constraint->minPixels));
}
if ($pixels < $constraint->minPixels) {
$this->context->buildViolation($constraint->minPixelsMessage)
->setParameter('{{ pixels }}', $pixels)
->setParameter('{{ min_pixels }}', $constraint->minPixels)
->setParameter('{{ height }}', $height)
->setParameter('{{ width }}', $width)
->setCode(Image::TOO_FEW_PIXEL_ERROR)
->addViolation();
}
}
if (null !== $constraint->maxPixels) {
if (!ctype_digit((string) $constraint->maxPixels)) {
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum amount of pixels.', $constraint->maxPixels));
}
if ($pixels > $constraint->maxPixels) {
$this->context->buildViolation($constraint->maxPixelsMessage)
->setParameter('{{ pixels }}', $pixels)
->setParameter('{{ max_pixels }}', $constraint->maxPixels)
->setParameter('{{ height }}', $height)
->setParameter('{{ width }}', $width)
->setCode(Image::TOO_MANY_PIXEL_ERROR)
->addViolation();
}
}
$ratio = round($width / $height, 2);
if (null !== $constraint->minRatio) {
if (!is_numeric((string) $constraint->minRatio)) {
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid minimum ratio.', $constraint->minRatio));
}
if ($ratio < round($constraint->minRatio, 2)) {
$this->context->buildViolation($constraint->minRatioMessage)
->setParameter('{{ ratio }}', $ratio)
->setParameter('{{ min_ratio }}', round($constraint->minRatio, 2))
->setCode(Image::RATIO_TOO_SMALL_ERROR)
->addViolation();
}
}
if (null !== $constraint->maxRatio) {
if (!is_numeric((string) $constraint->maxRatio)) {
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum ratio.', $constraint->maxRatio));
}
if ($ratio > round($constraint->maxRatio, 2)) {
$this->context->buildViolation($constraint->maxRatioMessage)
->setParameter('{{ ratio }}', $ratio)
->setParameter('{{ max_ratio }}', round($constraint->maxRatio, 2))
->setCode(Image::RATIO_TOO_BIG_ERROR)
->addViolation();
}
}
if (!$constraint->allowSquare && $width == $height) {
$this->context->buildViolation($constraint->allowSquareMessage)
->setParameter('{{ width }}', $width)
->setParameter('{{ height }}', $height)
->setCode(Image::SQUARE_NOT_ALLOWED_ERROR)
->addViolation();
}
if (!$constraint->allowLandscape && $width > $height) {
$this->context->buildViolation($constraint->allowLandscapeMessage)
->setParameter('{{ width }}', $width)
->setParameter('{{ height }}', $height)
->setCode(Image::LANDSCAPE_NOT_ALLOWED_ERROR)
->addViolation();
}
if (!$constraint->allowPortrait && $width < $height) {
$this->context->buildViolation($constraint->allowPortraitMessage)
->setParameter('{{ width }}', $width)
->setParameter('{{ height }}', $height)
->setCode(Image::PORTRAIT_NOT_ALLOWED_ERROR)
->addViolation();
}
if ($constraint->detectCorrupted) {
if (!\function_exists('imagecreatefromstring')) {
throw new LogicException('Corrupted images detection requires installed and enabled GD extension.');
}
$resource = @imagecreatefromstring(file_get_contents($value));
if (false === $resource) {
$this->context->buildViolation($constraint->corruptedMessage)
->setCode(Image::CORRUPTED_IMAGE_ERROR)
->addViolation();
return;
}
imagedestroy($resource);
}
}
}

View File

@ -0,0 +1,104 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\InvalidArgumentException;
/**
* Validates that a value is a valid IP address.
*
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Joseph Bielawski <stloyd@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Ip extends Constraint
{
public const V4 = '4';
public const V6 = '6';
public const ALL = 'all';
// adds FILTER_FLAG_NO_PRIV_RANGE flag (skip private ranges)
public const V4_NO_PRIV = '4_no_priv';
public const V6_NO_PRIV = '6_no_priv';
public const ALL_NO_PRIV = 'all_no_priv';
// adds FILTER_FLAG_NO_RES_RANGE flag (skip reserved ranges)
public const V4_NO_RES = '4_no_res';
public const V6_NO_RES = '6_no_res';
public const ALL_NO_RES = 'all_no_res';
// adds FILTER_FLAG_NO_PRIV_RANGE and FILTER_FLAG_NO_RES_RANGE flags (skip both)
public const V4_ONLY_PUBLIC = '4_public';
public const V6_ONLY_PUBLIC = '6_public';
public const ALL_ONLY_PUBLIC = 'all_public';
public const INVALID_IP_ERROR = 'b1b427ae-9f6f-41b0-aa9b-84511fbb3c5b';
protected static $versions = [
self::V4,
self::V6,
self::ALL,
self::V4_NO_PRIV,
self::V6_NO_PRIV,
self::ALL_NO_PRIV,
self::V4_NO_RES,
self::V6_NO_RES,
self::ALL_NO_RES,
self::V4_ONLY_PUBLIC,
self::V6_ONLY_PUBLIC,
self::ALL_ONLY_PUBLIC,
];
protected static $errorNames = [
self::INVALID_IP_ERROR => 'INVALID_IP_ERROR',
];
public $version = self::V4;
public $message = 'This is not a valid IP address.';
public $normalizer;
/**
* {@inheritdoc}
*/
public function __construct(
array $options = null,
string $version = null,
string $message = null,
callable $normalizer = null,
array $groups = null,
$payload = null
) {
parent::__construct($options, $groups, $payload);
$this->version = $version ?? $this->version;
$this->message = $message ?? $this->message;
$this->normalizer = $normalizer ?? $this->normalizer;
if (!\in_array($this->version, self::$versions)) {
throw new ConstraintDefinitionException(sprintf('The option "version" must be one of "%s".', implode('", "', self::$versions)));
}
if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));
}
}
}

View File

@ -0,0 +1,107 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* Validates whether a value is a valid IP address.
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Joseph Bielawski <stloyd@gmail.com>
*/
class IpValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Ip) {
throw new UnexpectedTypeException($constraint, Ip::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$value = (string) $value;
if (null !== $constraint->normalizer) {
$value = ($constraint->normalizer)($value);
}
switch ($constraint->version) {
case Ip::V4:
$flag = \FILTER_FLAG_IPV4;
break;
case Ip::V6:
$flag = \FILTER_FLAG_IPV6;
break;
case Ip::V4_NO_PRIV:
$flag = \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_PRIV_RANGE;
break;
case Ip::V6_NO_PRIV:
$flag = \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_PRIV_RANGE;
break;
case Ip::ALL_NO_PRIV:
$flag = \FILTER_FLAG_NO_PRIV_RANGE;
break;
case Ip::V4_NO_RES:
$flag = \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_RES_RANGE;
break;
case Ip::V6_NO_RES:
$flag = \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_RES_RANGE;
break;
case Ip::ALL_NO_RES:
$flag = \FILTER_FLAG_NO_RES_RANGE;
break;
case Ip::V4_ONLY_PUBLIC:
$flag = \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE;
break;
case Ip::V6_ONLY_PUBLIC:
$flag = \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE;
break;
case Ip::ALL_ONLY_PUBLIC:
$flag = \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE;
break;
default:
$flag = 0;
break;
}
if (!filter_var($value, \FILTER_VALIDATE_IP, $flag)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Ip::INVALID_IP_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,39 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class IsFalse extends Constraint
{
public const NOT_FALSE_ERROR = 'd53a91b0-def3-426a-83d7-269da7ab4200';
protected static $errorNames = [
self::NOT_FALSE_ERROR => 'NOT_FALSE_ERROR',
];
public $message = 'This value should be false.';
public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null)
{
parent::__construct($options ?? [], $groups, $payload);
$this->message = $message ?? $this->message;
}
}

View File

@ -0,0 +1,41 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class IsFalseValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof IsFalse) {
throw new UnexpectedTypeException($constraint, IsFalse::class);
}
if (null === $value || false === $value || 0 === $value || '0' === $value) {
return;
}
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(IsFalse::NOT_FALSE_ERROR)
->addViolation();
}
}

View File

@ -0,0 +1,39 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class IsNull extends Constraint
{
public const NOT_NULL_ERROR = '60d2f30b-8cfa-4372-b155-9656634de120';
protected static $errorNames = [
self::NOT_NULL_ERROR => 'NOT_NULL_ERROR',
];
public $message = 'This value should be null.';
public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null)
{
parent::__construct($options ?? [], $groups, $payload);
$this->message = $message ?? $this->message;
}
}

View File

@ -0,0 +1,39 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class IsNullValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof IsNull) {
throw new UnexpectedTypeException($constraint, IsNull::class);
}
if (null !== $value) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(IsNull::NOT_NULL_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,39 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class IsTrue extends Constraint
{
public const NOT_TRUE_ERROR = '2beabf1c-54c0-4882-a928-05249b26e23b';
protected static $errorNames = [
self::NOT_TRUE_ERROR => 'NOT_TRUE_ERROR',
];
public $message = 'This value should be true.';
public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null)
{
parent::__construct($options ?? [], $groups, $payload);
$this->message = $message ?? $this->message;
}
}

View File

@ -0,0 +1,43 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class IsTrueValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof IsTrue) {
throw new UnexpectedTypeException($constraint, IsTrue::class);
}
if (null === $value) {
return;
}
if (true !== $value && 1 !== $value && '1' !== $value) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(IsTrue::NOT_TRUE_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,86 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author The Whole Life To Learn <thewholelifetolearn@gmail.com>
* @author Manuel Reinhard <manu@sprain.ch>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Isbn extends Constraint
{
public const ISBN_10 = 'isbn10';
public const ISBN_13 = 'isbn13';
public const TOO_SHORT_ERROR = '949acbb0-8ef5-43ed-a0e9-032dfd08ae45';
public const TOO_LONG_ERROR = '3171387d-f80a-47b3-bd6e-60598545316a';
public const INVALID_CHARACTERS_ERROR = '23d21cea-da99-453d-98b1-a7d916fbb339';
public const CHECKSUM_FAILED_ERROR = '2881c032-660f-46b6-8153-d352d9706640';
public const TYPE_NOT_RECOGNIZED_ERROR = 'fa54a457-f042-441f-89c4-066ee5bdd3e1';
protected static $errorNames = [
self::TOO_SHORT_ERROR => 'TOO_SHORT_ERROR',
self::TOO_LONG_ERROR => 'TOO_LONG_ERROR',
self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR',
self::CHECKSUM_FAILED_ERROR => 'CHECKSUM_FAILED_ERROR',
self::TYPE_NOT_RECOGNIZED_ERROR => 'TYPE_NOT_RECOGNIZED_ERROR',
];
public $isbn10Message = 'This value is not a valid ISBN-10.';
public $isbn13Message = 'This value is not a valid ISBN-13.';
public $bothIsbnMessage = 'This value is neither a valid ISBN-10 nor a valid ISBN-13.';
public $type;
public $message;
/**
* {@inheritdoc}
*
* @param string|array|null $type The ISBN standard to validate or a set of options
*/
public function __construct(
$type = null,
string $message = null,
string $isbn10Message = null,
string $isbn13Message = null,
string $bothIsbnMessage = null,
array $groups = null,
$payload = null,
array $options = []
) {
if (\is_array($type)) {
$options = array_merge($type, $options);
} elseif (null !== $type) {
$options['value'] = $type;
}
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
$this->isbn10Message = $isbn10Message ?? $this->isbn10Message;
$this->isbn13Message = $isbn13Message ?? $this->isbn13Message;
$this->bothIsbnMessage = $bothIsbnMessage ?? $this->bothIsbnMessage;
}
/**
* {@inheritdoc}
*/
public function getDefaultOption()
{
return 'type';
}
}

View File

@ -0,0 +1,184 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* Validates whether the value is a valid ISBN-10 or ISBN-13.
*
* @author The Whole Life To Learn <thewholelifetolearn@gmail.com>
* @author Manuel Reinhard <manu@sprain.ch>
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @see https://en.wikipedia.org/wiki/Isbn
*/
class IsbnValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Isbn) {
throw new UnexpectedTypeException($constraint, Isbn::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$value = (string) $value;
$canonical = str_replace('-', '', $value);
// Explicitly validate against ISBN-10
if (Isbn::ISBN_10 === $constraint->type) {
if (true !== ($code = $this->validateIsbn10($canonical))) {
$this->context->buildViolation($this->getMessage($constraint, $constraint->type))
->setParameter('{{ value }}', $this->formatValue($value))
->setCode($code)
->addViolation();
}
return;
}
// Explicitly validate against ISBN-13
if (Isbn::ISBN_13 === $constraint->type) {
if (true !== ($code = $this->validateIsbn13($canonical))) {
$this->context->buildViolation($this->getMessage($constraint, $constraint->type))
->setParameter('{{ value }}', $this->formatValue($value))
->setCode($code)
->addViolation();
}
return;
}
// Try both ISBNs
// First, try ISBN-10
$code = $this->validateIsbn10($canonical);
// The ISBN can only be an ISBN-13 if the value was too long for ISBN-10
if (Isbn::TOO_LONG_ERROR === $code) {
// Try ISBN-13 now
$code = $this->validateIsbn13($canonical);
// If too short, this means we have 11 or 12 digits
if (Isbn::TOO_SHORT_ERROR === $code) {
$code = Isbn::TYPE_NOT_RECOGNIZED_ERROR;
}
}
if (true !== $code) {
$this->context->buildViolation($this->getMessage($constraint))
->setParameter('{{ value }}', $this->formatValue($value))
->setCode($code)
->addViolation();
}
}
protected function validateIsbn10(string $isbn)
{
// Choose an algorithm so that ERROR_INVALID_CHARACTERS is preferred
// over ERROR_TOO_SHORT/ERROR_TOO_LONG
// Otherwise "0-45122-5244" passes, but "0-45122_5244" reports
// "too long"
// Error priority:
// 1. ERROR_INVALID_CHARACTERS
// 2. ERROR_TOO_SHORT/ERROR_TOO_LONG
// 3. ERROR_CHECKSUM_FAILED
$checkSum = 0;
for ($i = 0; $i < 10; ++$i) {
// If we test the length before the loop, we get an ERROR_TOO_SHORT
// when actually an ERROR_INVALID_CHARACTERS is wanted, e.g. for
// "0-45122_5244" (typo)
if (!isset($isbn[$i])) {
return Isbn::TOO_SHORT_ERROR;
}
if ('X' === $isbn[$i]) {
$digit = 10;
} elseif (ctype_digit($isbn[$i])) {
$digit = $isbn[$i];
} else {
return Isbn::INVALID_CHARACTERS_ERROR;
}
$checkSum += $digit * (10 - $i);
}
if (isset($isbn[$i])) {
return Isbn::TOO_LONG_ERROR;
}
return 0 === $checkSum % 11 ? true : Isbn::CHECKSUM_FAILED_ERROR;
}
protected function validateIsbn13(string $isbn)
{
// Error priority:
// 1. ERROR_INVALID_CHARACTERS
// 2. ERROR_TOO_SHORT/ERROR_TOO_LONG
// 3. ERROR_CHECKSUM_FAILED
if (!ctype_digit($isbn)) {
return Isbn::INVALID_CHARACTERS_ERROR;
}
$length = \strlen($isbn);
if ($length < 13) {
return Isbn::TOO_SHORT_ERROR;
}
if ($length > 13) {
return Isbn::TOO_LONG_ERROR;
}
$checkSum = 0;
for ($i = 0; $i < 13; $i += 2) {
$checkSum += $isbn[$i];
}
for ($i = 1; $i < 12; $i += 2) {
$checkSum += $isbn[$i] * 3;
}
return 0 === $checkSum % 10 ? true : Isbn::CHECKSUM_FAILED_ERROR;
}
protected function getMessage(Isbn $constraint, string $type = null)
{
if (null !== $constraint->message) {
return $constraint->message;
} elseif (Isbn::ISBN_10 === $type) {
return $constraint->isbn10Message;
} elseif (Isbn::ISBN_13 === $type) {
return $constraint->isbn13Message;
}
return $constraint->bothIsbnMessage;
}
}

View File

@ -0,0 +1,46 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Laurent Masforné <l.masforne@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Isin extends Constraint
{
public const VALIDATION_LENGTH = 12;
public const VALIDATION_PATTERN = '/[A-Z]{2}[A-Z0-9]{9}[0-9]{1}/';
public const INVALID_LENGTH_ERROR = '88738dfc-9ed5-ba1e-aebe-402a2a9bf58e';
public const INVALID_PATTERN_ERROR = '3d08ce0-ded9-a93d-9216-17ac21265b65e';
public const INVALID_CHECKSUM_ERROR = '32089b-0ee1-93ba-399e-aa232e62f2d29d';
protected static $errorNames = [
self::INVALID_LENGTH_ERROR => 'INVALID_LENGTH_ERROR',
self::INVALID_PATTERN_ERROR => 'INVALID_PATTERN_ERROR',
self::INVALID_CHECKSUM_ERROR => 'INVALID_CHECKSUM_ERROR',
];
public $message = 'This value is not a valid International Securities Identification Number (ISIN).';
public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null)
{
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
}
}

View File

@ -0,0 +1,81 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* @author Laurent Masforné <l.masforne@gmail.com>
*
* @see https://en.wikipedia.org/wiki/International_Securities_Identification_Number
*/
class IsinValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Isin) {
throw new UnexpectedTypeException($constraint, Isin::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$value = strtoupper($value);
if (Isin::VALIDATION_LENGTH !== \strlen($value)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Isin::INVALID_LENGTH_ERROR)
->addViolation();
return;
}
if (!preg_match(Isin::VALIDATION_PATTERN, $value)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Isin::INVALID_PATTERN_ERROR)
->addViolation();
return;
}
if (!$this->isCorrectChecksum($value)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Isin::INVALID_CHECKSUM_ERROR)
->addViolation();
}
}
private function isCorrectChecksum(string $input): bool
{
$characters = str_split($input);
foreach ($characters as $i => $char) {
$characters[$i] = \intval($char, 36);
}
$number = implode('', $characters);
return 0 === $this->context->getValidator()->validate($number, new Luhn())->count();
}
}

View File

@ -0,0 +1,60 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Antonio J. García Lagar <aj@garcialagar.es>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Issn extends Constraint
{
public const TOO_SHORT_ERROR = '6a20dd3d-f463-4460-8e7b-18a1b98abbfb';
public const TOO_LONG_ERROR = '37cef893-5871-464e-8b12-7fb79324833c';
public const MISSING_HYPHEN_ERROR = '2983286f-8134-4693-957a-1ec4ef887b15';
public const INVALID_CHARACTERS_ERROR = 'a663d266-37c2-4ece-a914-ae891940c588';
public const INVALID_CASE_ERROR = '7b6dd393-7523-4a6c-b84d-72b91bba5e1a';
public const CHECKSUM_FAILED_ERROR = 'b0f92dbc-667c-48de-b526-ad9586d43e85';
protected static $errorNames = [
self::TOO_SHORT_ERROR => 'TOO_SHORT_ERROR',
self::TOO_LONG_ERROR => 'TOO_LONG_ERROR',
self::MISSING_HYPHEN_ERROR => 'MISSING_HYPHEN_ERROR',
self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR',
self::INVALID_CASE_ERROR => 'INVALID_CASE_ERROR',
self::CHECKSUM_FAILED_ERROR => 'CHECKSUM_FAILED_ERROR',
];
public $message = 'This value is not a valid ISSN.';
public $caseSensitive = false;
public $requireHyphen = false;
public function __construct(
array $options = null,
string $message = null,
bool $caseSensitive = null,
bool $requireHyphen = null,
array $groups = null,
$payload = null
) {
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
$this->caseSensitive = $caseSensitive ?? $this->caseSensitive;
$this->requireHyphen = $requireHyphen ?? $this->requireHyphen;
}
}

View File

@ -0,0 +1,131 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* Validates whether the value is a valid ISSN.
*
* @author Antonio J. García Lagar <aj@garcialagar.es>
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @see https://en.wikipedia.org/wiki/Issn
*/
class IssnValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Issn) {
throw new UnexpectedTypeException($constraint, Issn::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$value = (string) $value;
$canonical = $value;
// 1234-567X
// ^
if (isset($canonical[4]) && '-' === $canonical[4]) {
// remove hyphen
$canonical = substr($canonical, 0, 4).substr($canonical, 5);
} elseif ($constraint->requireHyphen) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Issn::MISSING_HYPHEN_ERROR)
->addViolation();
return;
}
$length = \strlen($canonical);
if ($length < 8) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Issn::TOO_SHORT_ERROR)
->addViolation();
return;
}
if ($length > 8) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Issn::TOO_LONG_ERROR)
->addViolation();
return;
}
// 1234567X
// ^^^^^^^ digits only
if (!ctype_digit(substr($canonical, 0, 7))) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Issn::INVALID_CHARACTERS_ERROR)
->addViolation();
return;
}
// 1234567X
// ^ digit, x or X
if (!ctype_digit($canonical[7]) && 'x' !== $canonical[7] && 'X' !== $canonical[7]) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Issn::INVALID_CHARACTERS_ERROR)
->addViolation();
return;
}
// 1234567X
// ^ case-sensitive?
if ($constraint->caseSensitive && 'x' === $canonical[7]) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Issn::INVALID_CASE_ERROR)
->addViolation();
return;
}
// Calculate a checksum. "X" equals 10.
$checkSum = 'X' === $canonical[7] || 'x' === $canonical[7] ? 10 : $canonical[7];
for ($i = 0; $i < 7; ++$i) {
// Multiply the first digit by 8, the second by 7, etc.
$checkSum += (8 - $i) * (int) $canonical[$i];
}
if (0 !== $checkSum % 11) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Issn::CHECKSUM_FAILED_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,39 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Imad ZAIRIG <imadzairig@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Json extends Constraint
{
public const INVALID_JSON_ERROR = '0789c8ad-2d2b-49a4-8356-e2ce63998504';
protected static $errorNames = [
self::INVALID_JSON_ERROR => 'INVALID_JSON_ERROR',
];
public $message = 'This value should be valid JSON.';
public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null)
{
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
}
}

View File

@ -0,0 +1,51 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* @author Imad ZAIRIG <imadzairig@gmail.com>
*/
class JsonValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Json) {
throw new UnexpectedTypeException($constraint, Json::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedTypeException($value, 'string');
}
$value = (string) $value;
json_decode($value);
if (\JSON_ERROR_NONE !== json_last_error()) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Json::INVALID_JSON_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,52 @@
<?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\Constraints;
use Symfony\Component\Intl\Languages;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\LogicException;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Language extends Constraint
{
public const NO_SUCH_LANGUAGE_ERROR = 'ee65fec4-9a20-4202-9f39-ca558cd7bdf7';
protected static $errorNames = [
self::NO_SUCH_LANGUAGE_ERROR => 'NO_SUCH_LANGUAGE_ERROR',
];
public $message = 'This value is not a valid language.';
public $alpha3 = false;
public function __construct(
array $options = null,
string $message = null,
bool $alpha3 = null,
array $groups = null,
$payload = null
) {
if (!class_exists(Languages::class)) {
throw new LogicException('The Intl component is required to use the Language constraint. Try running "composer require symfony/intl".');
}
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
$this->alpha3 = $alpha3 ?? $this->alpha3;
}
}

View File

@ -0,0 +1,53 @@
<?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\Constraints;
use Symfony\Component\Intl\Languages;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* Validates whether a value is a valid language code.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LanguageValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Language) {
throw new UnexpectedTypeException($constraint, Language::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$value = (string) $value;
if ($constraint->alpha3 ? !Languages::alpha3CodeExists($value) : !Languages::exists($value)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Language::NO_SUCH_LANGUAGE_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,105 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\InvalidArgumentException;
use Symfony\Component\Validator\Exception\MissingOptionsException;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Length extends Constraint
{
public const TOO_SHORT_ERROR = '9ff3fdc4-b214-49db-8718-39c315e33d45';
public const TOO_LONG_ERROR = 'd94b19cc-114f-4f44-9cc4-4138e80a87b9';
public const NOT_EQUAL_LENGTH_ERROR = '4b6f5c76-22b4-409d-af16-fbe823ba9332';
public const INVALID_CHARACTERS_ERROR = '35e6a710-aa2e-4719-b58e-24b35749b767';
protected static $errorNames = [
self::TOO_SHORT_ERROR => 'TOO_SHORT_ERROR',
self::TOO_LONG_ERROR => 'TOO_LONG_ERROR',
self::NOT_EQUAL_LENGTH_ERROR => 'NOT_EQUAL_LENGTH_ERROR',
self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR',
];
public $maxMessage = 'This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less.';
public $minMessage = 'This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more.';
public $exactMessage = 'This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.';
public $charsetMessage = 'This value does not match the expected {{ charset }} charset.';
public $max;
public $min;
public $charset = 'UTF-8';
public $normalizer;
public $allowEmptyString = false;
/**
* {@inheritdoc}
*
* @param int|array|null $exactly The expected exact length or a set of options
*/
public function __construct(
$exactly = null,
int $min = null,
int $max = null,
string $charset = null,
callable $normalizer = null,
string $exactMessage = null,
string $minMessage = null,
string $maxMessage = null,
string $charsetMessage = null,
array $groups = null,
$payload = null,
array $options = []
) {
if (\is_array($exactly)) {
$options = array_merge($exactly, $options);
$exactly = $options['value'] ?? null;
}
$min = $min ?? $options['min'] ?? null;
$max = $max ?? $options['max'] ?? null;
unset($options['value'], $options['min'], $options['max']);
if (null !== $exactly && null === $min && null === $max) {
$min = $max = $exactly;
}
parent::__construct($options, $groups, $payload);
$this->min = $min;
$this->max = $max;
$this->charset = $charset ?? $this->charset;
$this->normalizer = $normalizer ?? $this->normalizer;
$this->exactMessage = $exactMessage ?? $this->exactMessage;
$this->minMessage = $minMessage ?? $this->minMessage;
$this->maxMessage = $maxMessage ?? $this->maxMessage;
$this->charsetMessage = $charsetMessage ?? $this->charsetMessage;
if (null === $this->min && null === $this->max) {
throw new MissingOptionsException(sprintf('Either option "min" or "max" must be given for constraint "%s".', __CLASS__), ['min', 'max']);
}
if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));
}
if (isset($options['allowEmptyString'])) {
trigger_deprecation('symfony/validator', '5.2', sprintf('The "allowEmptyString" option of the "%s" constraint is deprecated.', self::class));
}
}
}

View File

@ -0,0 +1,96 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LengthValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Length) {
throw new UnexpectedTypeException($constraint, Length::class);
}
if (null === $value || ('' === $value && $constraint->allowEmptyString)) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$stringValue = (string) $value;
if (null !== $constraint->normalizer) {
$stringValue = ($constraint->normalizer)($stringValue);
}
try {
$invalidCharset = !@mb_check_encoding($stringValue, $constraint->charset);
} catch (\ValueError $e) {
if (!str_starts_with($e->getMessage(), 'mb_check_encoding(): Argument #2 ($encoding) must be a valid encoding')) {
throw $e;
}
$invalidCharset = true;
}
if ($invalidCharset) {
$this->context->buildViolation($constraint->charsetMessage)
->setParameter('{{ value }}', $this->formatValue($stringValue))
->setParameter('{{ charset }}', $constraint->charset)
->setInvalidValue($value)
->setCode(Length::INVALID_CHARACTERS_ERROR)
->addViolation();
return;
}
$length = mb_strlen($stringValue, $constraint->charset);
if (null !== $constraint->max && $length > $constraint->max) {
$exactlyOptionEnabled = $constraint->min == $constraint->max;
$this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->maxMessage)
->setParameter('{{ value }}', $this->formatValue($stringValue))
->setParameter('{{ limit }}', $constraint->max)
->setInvalidValue($value)
->setPlural((int) $constraint->max)
->setCode($exactlyOptionEnabled ? Length::NOT_EQUAL_LENGTH_ERROR : Length::TOO_LONG_ERROR)
->addViolation();
return;
}
if (null !== $constraint->min && $length < $constraint->min) {
$exactlyOptionEnabled = $constraint->min == $constraint->max;
$this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->minMessage)
->setParameter('{{ value }}', $this->formatValue($stringValue))
->setParameter('{{ limit }}', $constraint->min)
->setInvalidValue($value)
->setPlural((int) $constraint->min)
->setCode($exactlyOptionEnabled ? Length::NOT_EQUAL_LENGTH_ERROR : Length::TOO_SHORT_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,31 @@
<?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\Constraints;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class LessThan extends AbstractComparison
{
public const TOO_HIGH_ERROR = '079d7420-2d13-460c-8756-de810eeb37d2';
protected static $errorNames = [
self::TOO_HIGH_ERROR => 'TOO_HIGH_ERROR',
];
public $message = 'This value should be less than {{ compared_value }}.';
}

View File

@ -0,0 +1,31 @@
<?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\Constraints;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class LessThanOrEqual extends AbstractComparison
{
public const TOO_HIGH_ERROR = '30fbb013-d015-4232-8b3b-8f3be97a7e14';
protected static $errorNames = [
self::TOO_HIGH_ERROR => 'TOO_HIGH_ERROR',
];
public $message = 'This value should be less than or equal to {{ compared_value }}.';
}

View File

@ -0,0 +1,37 @@
<?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\Constraints;
/**
* Validates values are less than or equal to the previous (<=).
*
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LessThanOrEqualValidator extends AbstractComparisonValidator
{
/**
* {@inheritdoc}
*/
protected function compareValues($value1, $value2)
{
return null === $value2 || $value1 <= $value2;
}
/**
* {@inheritdoc}
*/
protected function getErrorCode()
{
return LessThanOrEqual::TOO_HIGH_ERROR;
}
}

View File

@ -0,0 +1,37 @@
<?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\Constraints;
/**
* Validates values are less than the previous (<).
*
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LessThanValidator extends AbstractComparisonValidator
{
/**
* {@inheritdoc}
*/
protected function compareValues($value1, $value2)
{
return null === $value2 || $value1 < $value2;
}
/**
* {@inheritdoc}
*/
protected function getErrorCode()
{
return LessThan::TOO_HIGH_ERROR;
}
}

View File

@ -0,0 +1,52 @@
<?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\Constraints;
use Symfony\Component\Intl\Locales;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\LogicException;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Locale extends Constraint
{
public const NO_SUCH_LOCALE_ERROR = 'a0af4293-1f1a-4a1c-a328-979cba6182a2';
protected static $errorNames = [
self::NO_SUCH_LOCALE_ERROR => 'NO_SUCH_LOCALE_ERROR',
];
public $message = 'This value is not a valid locale.';
public $canonicalize = true;
public function __construct(
array $options = null,
string $message = null,
bool $canonicalize = null,
array $groups = null,
$payload = null
) {
if (!class_exists(Locales::class)) {
throw new LogicException('The Intl component is required to use the Locale constraint. Try running "composer require symfony/intl".');
}
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
$this->canonicalize = $canonicalize ?? $this->canonicalize;
}
}

View File

@ -0,0 +1,57 @@
<?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\Constraints;
use Symfony\Component\Intl\Locales;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* Validates whether a value is a valid locale code.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LocaleValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Locale) {
throw new UnexpectedTypeException($constraint, Locale::class);
}
if (null === $value || '' === $value) {
return;
}
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$inputValue = (string) $value;
$value = $inputValue;
if ($constraint->canonicalize) {
$value = \Locale::canonicalize($value);
}
if (!Locales::exists($value)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($inputValue))
->setCode(Locale::NO_SUCH_LOCALE_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,49 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* Metadata for the LuhnValidator.
*
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Tim Nagel <t.nagel@infinite.net.au>
* @author Greg Knapp http://gregk.me/2011/php-implementation-of-bank-card-luhn-algorithm/
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Luhn extends Constraint
{
public const INVALID_CHARACTERS_ERROR = 'dfad6d23-1b74-4374-929b-5cbb56fc0d9e';
public const CHECKSUM_FAILED_ERROR = '4d760774-3f50-4cd5-a6d5-b10a3299d8d3';
protected static $errorNames = [
self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR',
self::CHECKSUM_FAILED_ERROR => 'CHECKSUM_FAILED_ERROR',
];
public $message = 'Invalid card number.';
public function __construct(
array $options = null,
string $message = null,
array $groups = null,
$payload = null
) {
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
}
}

View File

@ -0,0 +1,96 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
/**
* Validates a PAN using the LUHN Algorithm.
*
* For a list of example card numbers that are used to test this
* class, please see the LuhnValidatorTest class.
*
* @see http://en.wikipedia.org/wiki/Luhn_algorithm
*
* @author Tim Nagel <t.nagel@infinite.net.au>
* @author Greg Knapp http://gregk.me/2011/php-implementation-of-bank-card-luhn-algorithm/
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LuhnValidator extends ConstraintValidator
{
/**
* Validates a credit card number with the Luhn algorithm.
*
* @param mixed $value
*
* @throws UnexpectedTypeException when the given credit card number is no string
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Luhn) {
throw new UnexpectedTypeException($constraint, Luhn::class);
}
if (null === $value || '' === $value) {
return;
}
// Work with strings only, because long numbers are represented as floats
// internally and don't work with strlen()
if (!\is_string($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$value = (string) $value;
if (!ctype_digit($value)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Luhn::INVALID_CHARACTERS_ERROR)
->addViolation();
return;
}
$checkSum = 0;
$length = \strlen($value);
// Starting with the last digit and walking left, add every second
// digit to the check sum
// e.g. 7 9 9 2 7 3 9 8 7 1 3
// ^ ^ ^ ^ ^ ^
// = 7 + 9 + 7 + 9 + 7 + 3
for ($i = $length - 1; $i >= 0; $i -= 2) {
$checkSum += $value[$i];
}
// Starting with the second last digit and walking left, double every
// second digit and add it to the check sum
// For doubles greater than 9, sum the individual digits
// e.g. 7 9 9 2 7 3 9 8 7 1 3
// ^ ^ ^ ^ ^
// = 1+8 + 4 + 6 + 1+6 + 2
for ($i = $length - 2; $i >= 0; $i -= 2) {
$checkSum += array_sum(str_split((int) $value[$i] * 2));
}
if (0 === $checkSum || 0 !== $checkSum % 10) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Luhn::CHECKSUM_FAILED_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,26 @@
<?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\Constraints;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Negative extends LessThan
{
use ZeroComparisonConstraintTrait;
public $message = 'This value should be negative.';
}

View File

@ -0,0 +1,26 @@
<?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\Constraints;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class NegativeOrZero extends LessThanOrEqual
{
use ZeroComparisonConstraintTrait;
public $message = 'This value should be either negative or zero.';
}

View File

@ -0,0 +1,49 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\InvalidArgumentException;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Kévin Dunglas <dunglas@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class NotBlank extends Constraint
{
public const IS_BLANK_ERROR = 'c1051bb4-d103-4f74-8988-acbcafc7fdc3';
protected static $errorNames = [
self::IS_BLANK_ERROR => 'IS_BLANK_ERROR',
];
public $message = 'This value should not be blank.';
public $allowNull = false;
public $normalizer;
public function __construct(array $options = null, string $message = null, bool $allowNull = null, callable $normalizer = null, array $groups = null, $payload = null)
{
parent::__construct($options ?? [], $groups, $payload);
$this->message = $message ?? $this->message;
$this->allowNull = $allowNull ?? $this->allowNull;
$this->normalizer = $normalizer ?? $this->normalizer;
if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));
}
}
}

View File

@ -0,0 +1,48 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class NotBlankValidator extends ConstraintValidator
{
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof NotBlank) {
throw new UnexpectedTypeException($constraint, NotBlank::class);
}
if ($constraint->allowNull && null === $value) {
return;
}
if (\is_string($value) && null !== $constraint->normalizer) {
$value = ($constraint->normalizer)($value);
}
if (false === $value || (empty($value) && '0' != $value)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(NotBlank::IS_BLANK_ERROR)
->addViolation();
}
}
}

View File

@ -0,0 +1,49 @@
<?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\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* Checks if a password has been leaked in a data breach.
*
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class NotCompromisedPassword extends Constraint
{
public const COMPROMISED_PASSWORD_ERROR = 'd9bcdbfe-a9d6-4bfa-a8ff-da5fd93e0f6d';
protected static $errorNames = [self::COMPROMISED_PASSWORD_ERROR => 'COMPROMISED_PASSWORD_ERROR'];
public $message = 'This password has been leaked in a data breach, it must not be used. Please use another password.';
public $threshold = 1;
public $skipOnError = false;
public function __construct(
array $options = null,
string $message = null,
int $threshold = null,
bool $skipOnError = null,
array $groups = null,
$payload = null
) {
parent::__construct($options, $groups, $payload);
$this->message = $message ?? $this->message;
$this->threshold = $threshold ?? $this->threshold;
$this->skipOnError = $skipOnError ?? $this->skipOnError;
}
}

View File

@ -0,0 +1,105 @@
<?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\Constraints;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* Checks if a password has been leaked in a data breach using haveibeenpwned.com's API.
* Use a k-anonymity model to protect the password being searched for.
*
* @see https://haveibeenpwned.com/API/v2#SearchingPwnedPasswordsByRange
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class NotCompromisedPasswordValidator extends ConstraintValidator
{
private const DEFAULT_API_ENDPOINT = 'https://api.pwnedpasswords.com/range/%s';
private $httpClient;
private $charset;
private $enabled;
private $endpoint;
public function __construct(HttpClientInterface $httpClient = null, string $charset = 'UTF-8', bool $enabled = true, string $endpoint = null)
{
if (null === $httpClient && !class_exists(HttpClient::class)) {
throw new \LogicException(sprintf('The "%s" class requires the "HttpClient" component. Try running "composer require symfony/http-client".', self::class));
}
$this->httpClient = $httpClient ?? HttpClient::create();
$this->charset = $charset;
$this->enabled = $enabled;
$this->endpoint = $endpoint ?? self::DEFAULT_API_ENDPOINT;
}
/**
* {@inheritdoc}
*
* @throws ExceptionInterface
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof NotCompromisedPassword) {
throw new UnexpectedTypeException($constraint, NotCompromisedPassword::class);
}
if (!$this->enabled) {
return;
}
if (null !== $value && !is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedValueException($value, 'string');
}
$value = (string) $value;
if ('' === $value) {
return;
}
if ('UTF-8' !== $this->charset) {
$value = mb_convert_encoding($value, 'UTF-8', $this->charset);
}
$hash = strtoupper(sha1($value));
$hashPrefix = substr($hash, 0, 5);
$url = sprintf($this->endpoint, $hashPrefix);
try {
$result = $this->httpClient->request('GET', $url)->getContent();
} catch (ExceptionInterface $e) {
if ($constraint->skipOnError) {
return;
}
throw $e;
}
foreach (explode("\r\n", $result) as $line) {
[$hashSuffix, $count] = explode(':', $line);
if ($hashPrefix.$hashSuffix === $hash && $constraint->threshold <= (int) $count) {
$this->context->buildViolation($constraint->message)
->setCode(NotCompromisedPassword::COMPROMISED_PASSWORD_ERROR)
->addViolation();
return;
}
}
}
}

View File

@ -0,0 +1,31 @@
<?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\Constraints;
/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*
* @author Daniel Holmes <daniel@danielholmes.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class NotEqualTo extends AbstractComparison
{
public const IS_EQUAL_ERROR = 'aa2e33da-25c8-4d76-8c6c-812f02ea89dd';
protected static $errorNames = [
self::IS_EQUAL_ERROR => 'IS_EQUAL_ERROR',
];
public $message = 'This value should not be equal to {{ compared_value }}.';
}

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