Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit b82e065

Browse files
committed
feat: added Country rule
1 parent bc3b803 commit b82e065

File tree

4 files changed

+122
-0
lines changed

4 files changed

+122
-0
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
],
1414
"require": {
1515
"php": ">=8.1",
16+
"symfony/intl": "^6.3",
1617
"symfony/polyfill-ctype": "^1.27"
1718
},
1819
"require-dev": {

src/Exception/CountryException.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\YetAnotherPhpValidator\Exception;
4+
5+
class CountryException extends ValidationException {}

src/Rule/Country.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\YetAnotherPhpValidator\Rule;
4+
5+
use ProgrammatorDev\YetAnotherPhpValidator\Exception\CountryException;
6+
use ProgrammatorDev\YetAnotherPhpValidator\Exception\UnexpectedValueException;
7+
use Symfony\Component\Intl\Countries;
8+
9+
class Country extends AbstractRule implements RuleInterface
10+
{
11+
public const ALPHA2_CODE = 'alpha2';
12+
public const ALPHA3_CODE = 'alpha3';
13+
14+
private const CODE_OPTIONS = [
15+
self::ALPHA2_CODE,
16+
self::ALPHA3_CODE
17+
];
18+
19+
public function __construct(
20+
private readonly string $code = self::ALPHA2_CODE,
21+
private readonly string $message = 'The "{{ name }}" value is not a valid "{{ code }}" country code, "{{ value }}" given.'
22+
) {}
23+
24+
public function assert(mixed $value, string $name): void
25+
{
26+
if (!\in_array($this->code, self::CODE_OPTIONS)) {
27+
throw new UnexpectedValueException(
28+
\sprintf(
29+
'Invalid code "%s". Accepted values are: "%s".',
30+
$this->code,
31+
\implode(", ", self::CODE_OPTIONS)
32+
)
33+
);
34+
}
35+
36+
if (!\is_string($value)) {
37+
throw new UnexpectedValueException(
38+
\sprintf('Expected value of type "string", "%s" given.', get_debug_type($value))
39+
);
40+
}
41+
42+
// Keep original value for parameters
43+
$input = strtoupper($value);
44+
45+
if (
46+
($this->code === self::ALPHA2_CODE && !Countries::exists($input))
47+
|| ($this->code === self::ALPHA3_CODE && !Countries::alpha3CodeExists($input))
48+
) {
49+
throw new CountryException(
50+
message: $this->message,
51+
parameters: [
52+
'name' => $name,
53+
'value' => $value,
54+
'code' => $this->code
55+
]
56+
);
57+
}
58+
}
59+
}

tests/CountryTest.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace ProgrammatorDev\YetAnotherPhpValidator\Test;
4+
5+
use ProgrammatorDev\YetAnotherPhpValidator\Exception\CountryException;
6+
use ProgrammatorDev\YetAnotherPhpValidator\Rule\Country;
7+
use ProgrammatorDev\YetAnotherPhpValidator\Test\Util\TestRuleFailureConditionTrait;
8+
use ProgrammatorDev\YetAnotherPhpValidator\Test\Util\TestRuleMessageOptionTrait;
9+
use ProgrammatorDev\YetAnotherPhpValidator\Test\Util\TestRuleSuccessConditionTrait;
10+
use ProgrammatorDev\YetAnotherPhpValidator\Test\Util\TestRuleUnexpectedValueTrait;
11+
12+
class CountryTest extends AbstractTest
13+
{
14+
use TestRuleUnexpectedValueTrait;
15+
use TestRuleFailureConditionTrait;
16+
use TestRuleSuccessConditionTrait;
17+
use TestRuleMessageOptionTrait;
18+
19+
public static function provideRuleUnexpectedValueData(): \Generator
20+
{
21+
$codeMessage = '/Invalid code "(.*)". Accepted values are: "(.*)"./';
22+
$typeMessage = '/Expected value of type "string", "(.*)" given./';
23+
24+
yield 'invalid code' => [new Country('invalid'), 'PT', $codeMessage];
25+
yield 'invalid type' => [new Country(), 123, $typeMessage];
26+
}
27+
28+
public static function provideRuleFailureConditionData(): \Generator
29+
{
30+
$exception = CountryException::class;
31+
$message = '/The "(.*)" value is not a valid "(.*)" country code, "(.*)" given./';
32+
33+
yield 'default' => [new Country(), 'PRT', $exception, $message];
34+
yield 'alpha2' => [new Country(code: 'alpha2'), 'PRT', $exception, $message];
35+
yield 'alpha3' => [new Country(code: 'alpha3'), 'PT', $exception, $message];
36+
}
37+
38+
public static function provideRuleSuccessConditionData(): \Generator
39+
{
40+
yield 'default' => [new Country(), 'PT'];
41+
yield 'alpha2' => [new Country(code: 'alpha2'), 'PT'];
42+
yield 'alpha2 lowercase' => [new Country(code: 'alpha2'), 'pt'];
43+
yield 'alpha3' => [new Country(code: 'alpha3'), 'PRT'];
44+
yield 'alpha3 lowercase' => [new Country(code: 'alpha3'), 'prt'];
45+
}
46+
47+
public static function provideRuleMessageOptionData(): \Generator
48+
{
49+
yield 'message' => [
50+
new Country(
51+
message: 'The "{{ name }}" value "{{ value }}" is not a valid "{{ code }}" country code.'
52+
),
53+
'invalid',
54+
'The "test" value "invalid" is not a valid "alpha2" country code.'
55+
];
56+
}
57+
}

0 commit comments

Comments
 (0)