Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 347b2a1

Browse filesBrowse files
committed
Added new CssColor constraint
1 parent 93d07e9 commit 347b2a1
Copy full SHA for 347b2a1

File tree

8 files changed

+657
-1
lines changed
Filter options

8 files changed

+657
-1
lines changed

‎src/Symfony/Component/Validator/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
5.4
55
---
66

7+
* Add a `CssColor` constraint to validate hexadecimal colors
78
* Add support for `ConstraintViolationList::createFromMessage()`
89

910
5.3
+106Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\Exception\InvalidArgumentException;
16+
17+
/**
18+
* @Annotation
19+
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
20+
*
21+
* @author Mathieu Santostefano <msantostefano@protonmail.com>
22+
*/
23+
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
24+
class CssColor extends Constraint
25+
{
26+
public const HEX_LONG = 'hex_long';
27+
public const HEX_LONG_WITH_ALPHA = 'hex_long_with_alpha';
28+
public const HEX_SHORT = 'hex_short';
29+
public const HEX_SHORT_WITH_ALPHA = 'hex_short_with_alpha';
30+
public const BASIC_NAMED_COLORS = 'basic_named_colors';
31+
public const EXTENDED_NAMED_COLORS = 'extended_named_colors';
32+
public const SYSTEM_COLORS = 'system_colors';
33+
public const KEYWORDS = 'keywords';
34+
public const RGB = 'rgb';
35+
public const RGBA = 'rgba';
36+
public const HSL = 'hsl';
37+
public const HSLA = 'hsla';
38+
public const INVALID_FORMAT_ERROR = '454ab47b-aacf-4059-8f26-184b2dc9d48d';
39+
40+
protected static $errorNames = [
41+
self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR',
42+
];
43+
44+
/**
45+
* @var string[]
46+
*/
47+
private static $validationModes = [
48+
self::HEX_LONG,
49+
self::HEX_LONG_WITH_ALPHA,
50+
self::HEX_SHORT,
51+
self::HEX_SHORT_WITH_ALPHA,
52+
self::BASIC_NAMED_COLORS,
53+
self::EXTENDED_NAMED_COLORS,
54+
self::SYSTEM_COLORS,
55+
self::KEYWORDS,
56+
self::RGB,
57+
self::RGBA,
58+
self::HSL,
59+
self::HSLA,
60+
];
61+
62+
public $message = 'This value is not a valid CSS color.';
63+
public $formats;
64+
65+
/**
66+
* @param array|string $formats The types of CSS colors allowed (e.g. hexadecimal only, RGB and HSL only, etc.).
67+
*/
68+
public function __construct($formats, string $message = null, array $groups = null, $payload = null, array $options = null)
69+
{
70+
$validationModesAsString = array_reduce(self::$validationModes, function ($carry, $value) {
71+
return $carry ? $carry.', '.$value : $value;
72+
}, '');
73+
74+
if (\is_array($formats) && \is_string(key($formats))) {
75+
$options = array_merge($formats, $options);
76+
} elseif (\is_array($formats)) {
77+
if ([] === array_intersect(static::$validationModes, $formats)) {
78+
throw new InvalidArgumentException('The "formats" parameter value is not valid. It must contain one or more of the following values: "'.$validationModesAsString.'"');
79+
}
80+
81+
$options['value'] = $formats;
82+
} elseif (\is_string($formats)) {
83+
if (!\in_array($formats, static::$validationModes)) {
84+
throw new InvalidArgumentException('The "formats" parameter value is not valid. It must contain one or more of the following values: "'.$validationModesAsString.'"');
85+
}
86+
87+
$options['value'] = [$formats];
88+
} else {
89+
throw new InvalidArgumentException('The "formats" parameter type is not valid. It should be a string or an array.');
90+
}
91+
92+
parent::__construct($options, $groups, $payload);
93+
94+
$this->message = $message ?? $this->message;
95+
}
96+
97+
public function getDefaultOption(): string
98+
{
99+
return 'formats';
100+
}
101+
102+
public function getRequiredOptions(): array
103+
{
104+
return ['formats'];
105+
}
106+
}
+86Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\ConstraintValidator;
16+
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
17+
use Symfony\Component\Validator\Exception\UnexpectedValueException;
18+
19+
/**
20+
* @author Mathieu Santostefano <msantostefano@protonmail.com>
21+
*/
22+
class CssColorValidator extends ConstraintValidator
23+
{
24+
private const PATTERN_HEX_LONG = '/^#[0-9a-f]{6}$/i';
25+
private const PATTERN_HEX_LONG_WITH_ALPHA = '/^#[0-9a-f]{8}$/i';
26+
private const PATTERN_HEX_SHORT = '/^#[0-9a-f]{3}$/i';
27+
private const PATTERN_HEX_SHORT_WITH_ALPHA = '/^#[0-9a-f]{4}$/i';
28+
// List comes from https://www.w3.org/wiki/CSS/Properties/color/keywords#Basic_Colors
29+
private const PATTERN_BASIC_NAMED_COLORS = '/^(black|silver|gray|white|maroon|red|purple|fuchsia|green|lime|olive|yellow|navy|blue|teal|aqua)$/i';
30+
// List comes from https://www.w3.org/wiki/CSS/Properties/color/keywords#Extended_colors
31+
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';
32+
// List comes from https://drafts.csswg.org/css-color/#css-system-colors
33+
private const PATTERN_SYSTEM_COLORS = '/^(Canvas|CanvasText|LinkText|VisitedText|ActiveText|ButtonFace|ButtonText|ButtonBorder|Field|FieldText|Highlight|HighlightText|SelectedItem|SelectedItemText|Mark|MarkText|GrayText)$/i';
34+
private const PATTERN_KEYWORDS = '/^(transparent|currentColor)$/i';
35+
private const PATTERN_RGB = '/^rgb\((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)\)$/i';
36+
private const PATTERN_RGBA = '/^rgba\((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)?)\)$/i';
37+
private const PATTERN_HSL = '/^hsl\((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})%\)$/i';
38+
private const PATTERN_HSLA = '/^hsla\((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?\.\d|1(\.0)?)\)$/i';
39+
40+
private const COLOR_PATTERNS = [
41+
CssColor::HEX_LONG => self::PATTERN_HEX_LONG,
42+
CssColor::HEX_LONG_WITH_ALPHA => self::PATTERN_HEX_LONG_WITH_ALPHA,
43+
CssColor::HEX_SHORT => self::PATTERN_HEX_SHORT,
44+
CssColor::HEX_SHORT_WITH_ALPHA => self::PATTERN_HEX_SHORT_WITH_ALPHA,
45+
CssColor::BASIC_NAMED_COLORS => self::PATTERN_BASIC_NAMED_COLORS,
46+
CssColor::EXTENDED_NAMED_COLORS => self::PATTERN_EXTENDED_NAMED_COLORS,
47+
CssColor::SYSTEM_COLORS => self::PATTERN_SYSTEM_COLORS,
48+
CssColor::KEYWORDS => self::PATTERN_KEYWORDS,
49+
CssColor::RGB => self::PATTERN_RGB,
50+
CssColor::RGBA => self::PATTERN_RGBA,
51+
CssColor::HSL => self::PATTERN_HSL,
52+
CssColor::HSLA => self::PATTERN_HSLA,
53+
];
54+
55+
/**
56+
* {@inheritdoc}
57+
*/
58+
public function validate($value, Constraint $constraint): void
59+
{
60+
if (!$constraint instanceof CssColor) {
61+
throw new UnexpectedTypeException($constraint, CssColor::class);
62+
}
63+
64+
if (null === $value || '' === $value) {
65+
return;
66+
}
67+
68+
if (!\is_string($value)) {
69+
throw new UnexpectedValueException($value, 'string');
70+
}
71+
72+
$formats = array_flip((array) $constraint->formats);
73+
$formatRegexes = array_intersect_key(self::COLOR_PATTERNS, $formats);
74+
75+
foreach ($formatRegexes as $regex) {
76+
if (preg_match($regex, (string) $value)) {
77+
return;
78+
}
79+
}
80+
81+
$this->context->buildViolation($constraint->message)
82+
->setParameter('{{ value }}', $this->formatValue($value))
83+
->setCode(CssColor::INVALID_FORMAT_ERROR)
84+
->addViolation();
85+
}
86+
}

‎src/Symfony/Component/Validator/Resources/translations/validators.en.xlf

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Resources/translations/validators.en.xlf
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,10 @@
390390
<source>This value should be a valid expression.</source>
391391
<target>This value should be a valid expression.</target>
392392
</trans-unit>
393+
<trans-unit id="101">
394+
<source>This value is not a valid CSS color.</source>
395+
<target>This value is not a valid CSS color.</target>
396+
</trans-unit>
393397
</body>
394398
</file>
395399
</xliff>

‎src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,10 @@
390390
<source>This value should be a valid expression.</source>
391391
<target>Cette valeur doit être une expression valide.</target>
392392
</trans-unit>
393+
<trans-unit id="101">
394+
<source>This value is not a valid CSS color.</source>
395+
<target>Cette valeur n'est pas une couleur CSS valide.</target>
396+
</trans-unit>
393397
</body>
394398
</file>
395399
</xliff>

‎src/Symfony/Component/Validator/Resources/translations/validators.it.xlf

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Resources/translations/validators.it.xlf
+5-1Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
</trans-unit>
5757
<trans-unit id="14">
5858
<source>The file could not be found.</source>
59-
<target>Non è stato possibile trovare il file.</target>
59+
<target>Non è stato possible trovare il file.</target>
6060
</trans-unit>
6161
<trans-unit id="15">
6262
<source>The file is not readable.</source>
@@ -390,6 +390,10 @@
390390
<source>This value should be a valid expression.</source>
391391
<target>Questo valore dovrebbe essere un'espressione valida.</target>
392392
</trans-unit>
393+
<trans-unit id="101">
394+
<source>This value is not a valid CSS color.</source>
395+
<target>Questo valore non è un colore CSS valido.</target>
396+
</trans-unit>
393397
</body>
394398
</file>
395399
</xliff>
+56Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Tests\Constraints;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Validator\Constraints\CssColor;
16+
use Symfony\Component\Validator\Mapping\ClassMetadata;
17+
use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;
18+
19+
/**
20+
* @author Mathieu Santostefano <msantostefano@protonmail.com>
21+
* @requires PHP 8
22+
*/
23+
final class CssColorTest extends TestCase
24+
{
25+
public function testAttributes()
26+
{
27+
$metadata = new ClassMetadata(CssColorDummy::class);
28+
$loader = new AnnotationLoader();
29+
self::assertTrue($loader->loadClassMetadata($metadata));
30+
31+
[$aConstraint] = $metadata->properties['a']->getConstraints();
32+
self::assertSame([CssColor::HEX_LONG, CssColor::HEX_SHORT], $aConstraint->formats);
33+
34+
[$bConstraint] = $metadata->properties['b']->getConstraints();
35+
self::assertSame([CssColor::HEX_LONG], $bConstraint->formats);
36+
self::assertSame('myMessage', $bConstraint->message);
37+
self::assertSame(['Default', 'CssColorDummy'], $bConstraint->groups);
38+
39+
[$cConstraint] = $metadata->properties['c']->getConstraints();
40+
self::assertSame([CssColor::HEX_SHORT], $cConstraint->formats);
41+
self::assertSame(['my_group'], $cConstraint->groups);
42+
self::assertSame('some attached data', $cConstraint->payload);
43+
}
44+
}
45+
46+
class CssColorDummy
47+
{
48+
#[CssColor([CssColor::HEX_LONG, CssColor::HEX_SHORT])]
49+
private $a;
50+
51+
#[CssColor(formats: CssColor::HEX_LONG, message: 'myMessage')]
52+
private $b;
53+
54+
#[CssColor(formats: [CssColor::HEX_SHORT], groups: ['my_group'], payload: 'some attached data')]
55+
private $c;
56+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.