├── tests
├── Fixtures
│ └── IsoObject.php
└── Constraints
│ ├── ZipCodeTest.php
│ ├── PaZipCodeValidatorTest.php
│ ├── NlZipCodeValidatorTest.php
│ ├── ChZipCodeValidatorTest.php
│ ├── EsZipCodeValidatorTest.php
│ ├── CuZipCodeValidatorTest.php
│ ├── NaZipCodeValidatorTest.php
│ ├── LtZipCodeValidatorTest.php
│ ├── MdZipCodeValidatorTest.php
│ ├── JmZipCodeValidatorTest.php
│ ├── ZipCodeValidatorTest.php
│ └── GbZipCodeValidatorTest.php
├── .gitignore
├── phpunit.xml
├── composer.json
├── phpunit.xml.dist
├── LICENSE
├── src
└── ZipCodeValidator
│ └── Constraints
│ ├── ZipCode.php
│ └── ZipCodeValidator.php
├── .github
└── workflows
│ └── ci.yaml
├── CODE_OF_CONDUCT.md
└── README.md
/tests/Fixtures/IsoObject.php:
--------------------------------------------------------------------------------
1 | iso;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #OS generated files
2 | .DS_STORE
3 | .DS_STORE?
4 | ._*
5 | -Spotlight-V11
6 | .Trashes
7 | Icon?
8 | ethumbs.db
9 | Thumbs.db
10 |
11 | #editor related
12 | .idea
13 |
14 | #composer related
15 | composer.phar
16 | composer.lock
17 | vendor/
18 |
19 | .phpunit.result.cache
20 | .phpunit.cache
21 | code-coverage
--------------------------------------------------------------------------------
/tests/Constraints/ZipCodeTest.php:
--------------------------------------------------------------------------------
1 | expectException(MissingOptionsException::class);
14 | $constraint = new ZipCode(null);
15 | }
16 | }
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 | ./tests
12 |
13 |
14 |
15 |
16 |
17 | ./src/
18 |
19 |
20 | ./tests
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "barbieswimcrew/zip-code-validator",
3 | "description": "Constraint class for international zipcode validation",
4 | "keywords": ["symfony", "form", "constraints", "constraint", "validator", "zip code", "zipcode", "postal code", "postalcode", "validation"],
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Martin Schindler"
9 | }
10 | ],
11 | "minimum-stability": "dev",
12 | "prefer-stable": true,
13 | "require": {
14 | "php": ">=8.0",
15 | "symfony/validator": ">=4.4.40"
16 | },
17 | "require-dev": {
18 | "phpunit/phpunit": "^9.6 || ^10.5 || ^11.0.3"
19 | },
20 | "autoload": {
21 | "psr-4": {
22 | "ZipCodeValidator\\": "src/ZipCodeValidator"
23 | }
24 | },
25 | "autoload-dev": {
26 | "psr-4": {
27 | "ZipCodeValidator\\Tests\\": "tests/"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Tests
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | ./src/
28 |
29 |
30 | ./tests
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Martin Schindler
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/src/ZipCodeValidator/Constraints/ZipCode.php:
--------------------------------------------------------------------------------
1 | $options
28 | );
29 | }
30 |
31 | parent::__construct($options, $groups, $payload);
32 |
33 | if (null === $this->iso && null === $this->getter) {
34 | throw new MissingOptionsException(sprintf('Either the option "iso" or "getter" must be given for constraint %s', __CLASS__), ['iso', 'getter']);
35 | }
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | # .github/workflows/ci.yaml
2 | name: Code_Checks
3 |
4 | on: ["push", "pull_request"]
5 |
6 | jobs:
7 | tests:
8 | runs-on: ubuntu-latest
9 | strategy:
10 | fail-fast: false
11 | matrix:
12 | php: ['8.0']
13 | stability: [ prefer-stable ]
14 | include:
15 | - php: '8.0'
16 | stability: prefer-lowest
17 | symfony-version: 4.4
18 | - php: '8.0'
19 | symfony-version: 5.4
20 | - php: '8.1'
21 | symfony-version: 5.4
22 | - php: '8.2'
23 | symfony-version: 5.4
24 | - php: '8.2'
25 | symfony-version: 6.4
26 | - php: '8.3'
27 | symfony-version: 7.0
28 |
29 | name: PHP ${{ matrix.php }} - ${{ matrix.stability }} tests
30 | steps:
31 | # basically git clone
32 | - uses: actions/checkout@v4
33 |
34 | - name: Cache dependencies
35 | uses: actions/cache@v4
36 | with:
37 | path: ~/.composer/cache/files
38 | key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
39 |
40 | # use PHP of specific version
41 | - uses: shivammathur/setup-php@v2
42 | with:
43 | php-version: ${{ matrix.php }}
44 | extensions: pcov
45 | coverage: pcov
46 |
47 | - name: Install dependencies
48 | env:
49 | SYMFONY_REQUIRE: ${{ matrix.symfony-version }}
50 | run: |
51 | composer global config --no-plugins allow-plugins.symfony/flex true
52 | composer global require --no-progress --no-scripts --no-plugins symfony/flex
53 | composer update --no-interaction --prefer-dist --optimize-autoloader
54 |
55 | - name: Execute tests
56 | run: vendor/bin/phpunit -c ./phpunit.xml.dist ./tests
57 |
--------------------------------------------------------------------------------
/tests/Constraints/PaZipCodeValidatorTest.php:
--------------------------------------------------------------------------------
1 | validator = new ZipCodeValidator;
22 | }
23 |
24 | /**
25 | * This test verifies that all known Panama codes are valid.
26 | *
27 | * @dataProvider getPanamaZipCodes
28 | */
29 | public function testZipcodes(string $zipCode): void
30 | {
31 | $constraint = new ZipCode('PA');
32 |
33 | /** @var ExecutionContext|MockObject $contextMock */
34 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
35 | ->disableOriginalConstructor()
36 | ->getMock();
37 |
38 | # be sure that buildViolation never gets called
39 | $contextMock->expects($this->never())->method('buildViolation');
40 | $contextMock->setConstraint($constraint);
41 |
42 | $this->validator->initialize($contextMock);
43 | $this->validator->validate($zipCode, $constraint);
44 | }
45 |
46 | /**
47 | * used postal codes
48 | * from https://en.wikipedia.org/wiki/Postal_codes_in_Panama
49 | */
50 | public static function getPanamaZipCodes(): array
51 | {
52 | return [
53 | ['0101'],
54 | ['0401'],
55 | ['0201'],
56 | ['0301'],
57 | ['0501'],
58 | ['0601'],
59 | ['0701'],
60 | ['0801'],
61 | ['1001'],
62 | ['0901'],
63 | ];
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/tests/Constraints/NlZipCodeValidatorTest.php:
--------------------------------------------------------------------------------
1 | validator = new ZipCodeValidator;
19 | }
20 |
21 | /**
22 | * @doesNotPerformAssertions
23 | */
24 | public function testValidationOfNlZipWithIsoCode(): void
25 | {
26 | $constraint = new ZipCode('NL');
27 |
28 | // Test some variations
29 | $this->validator->validate('1000AA', $constraint);
30 | $this->validator->validate('1000 AA', $constraint);
31 | }
32 |
33 | /**
34 | * @doesNotPerformAssertions
35 | */
36 | public function testValidationOfNlZipWithIsoCodeAnSmallCaps(): void
37 | {
38 | $constraint = new ZipCode([
39 | 'iso' => 'NL',
40 | 'caseSensitiveCheck' => false
41 | ]);
42 |
43 | // Test some variations
44 | $this->validator->validate('1000aa', $constraint);
45 | $this->validator->validate('1000 aa', $constraint);
46 | }
47 |
48 | public function testValidationErrorWithInvalidNlZipCode(): void
49 | {
50 | $value = "1000";
51 | $constraint = new ZipCode('NL');
52 |
53 | $violationBuilderMock = $this->getMockBuilder(ConstraintViolationBuilderInterface::class)
54 | ->disableOriginalConstructor()
55 | ->getMock();
56 | $violationBuilderMock->expects($this->once())->method('setParameter')->willReturnSelf();
57 |
58 | /** @var ExecutionContext|MockObject $contextMock */
59 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
60 | ->disableOriginalConstructor()
61 | ->getMock();
62 | $contextMock->expects($this->once())
63 | ->method('buildViolation')
64 | ->with($constraint->message)
65 | ->willReturn($violationBuilderMock);
66 |
67 | $contextMock->setConstraint($constraint);
68 | $this->validator->initialize($contextMock);
69 | $this->validator->validate($value, $constraint);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Constraints/ChZipCodeValidatorTest.php:
--------------------------------------------------------------------------------
1 | validator = new ZipCodeValidator;
19 | }
20 |
21 | /**
22 | * @dataProvider chValidZipCodes
23 | */
24 | public function testValidationOfChZipCode(string $zipCode): void
25 | {
26 | $constraint = new ZipCode('CH');
27 |
28 | /** @var ExecutionContext|MockObject $contextMock */
29 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
30 | ->disableOriginalConstructor()
31 | ->getMock();
32 |
33 | //be sure that buildViolation never gets called
34 | $contextMock->expects($this->never())->method('buildViolation');
35 |
36 | $contextMock->setConstraint($constraint);
37 | $this->validator->initialize($contextMock);
38 |
39 | // Test some variations
40 | $this->validator->validate($zipCode, $constraint);
41 | }
42 |
43 | public static function chValidZipCodes(): array
44 | {
45 | return [
46 | ['1000'],
47 | ['3000'],
48 | ['3250'],
49 | ['9658'],
50 | ];
51 | }
52 |
53 | /**
54 | * @dataProvider chInvalidZipCodes
55 | */
56 | public function testValidationErrorWithInvalidChZipCode(string $zipcode): void
57 | {
58 | $constraint = new ZipCode('CH');
59 |
60 | $violationBuilderMock = $this->getMockBuilder(ConstraintViolationBuilderInterface::class)
61 | ->disableOriginalConstructor()
62 | ->getMock();
63 | $violationBuilderMock->expects($this->once())->method('setParameter')->willReturnSelf();
64 |
65 | /** @var ExecutionContext|MockObject $contextMock */
66 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
67 | ->disableOriginalConstructor()
68 | ->getMock();
69 | $contextMock->expects($this->once())
70 | ->method('buildViolation')
71 | ->with($constraint->message)
72 | ->willReturn($violationBuilderMock);
73 |
74 | $contextMock->setConstraint($constraint);
75 | $this->validator->initialize($contextMock);
76 | $this->validator->validate($zipcode, $constraint);
77 | }
78 |
79 | public static function chInvalidZipCodes(): array
80 | {
81 | return [
82 | ['0000'],
83 | ['0099'],
84 | ['024567'],
85 | ['ch128'],
86 | ['2-341'],
87 | ];
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/tests/Constraints/EsZipCodeValidatorTest.php:
--------------------------------------------------------------------------------
1 | validator = new ZipCodeValidator;
19 | }
20 |
21 | /**
22 | * @dataProvider esValidZipCodes
23 | */
24 | public function testValidationOfEsZipCode(string $zipCode): void
25 | {
26 | $constraint = new ZipCode('ES');
27 |
28 | /** @var ExecutionContext|MockObject $contextMock */
29 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
30 | ->disableOriginalConstructor()
31 | ->getMock();
32 |
33 | //be sure that buildViolation never gets called
34 | $contextMock->expects($this->never())->method('buildViolation');
35 |
36 | $contextMock->setConstraint($constraint);
37 | $this->validator->initialize($contextMock);
38 |
39 | // Test some variations
40 | $this->validator->validate($zipCode, $constraint);
41 | }
42 |
43 | public static function esValidZipCodes(): array
44 | {
45 | return [
46 | ['08020'],
47 | ['11231'],
48 | ['52006'],
49 | ['49739'],
50 | ];
51 | }
52 |
53 | /**
54 | * @dataProvider esInvalidZipCodes
55 | */
56 | public function testValidationErrorWithInvalidEsZipCode(string $zipcode): void
57 | {
58 | $constraint = new ZipCode('ES');
59 |
60 | $violationBuilderMock = $this->getMockBuilder(ConstraintViolationBuilderInterface::class)
61 | ->disableOriginalConstructor()
62 | ->getMock();
63 | $violationBuilderMock->expects($this->once())->method('setParameter')->willReturnSelf();
64 |
65 | /** @var ExecutionContext|MockObject $contextMock */
66 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
67 | ->disableOriginalConstructor()
68 | ->getMock();
69 | $contextMock->expects($this->once())
70 | ->method('buildViolation')
71 | ->with($constraint->message)
72 | ->willReturn($violationBuilderMock);
73 |
74 | $contextMock->setConstraint($constraint);
75 | $this->validator->initialize($contextMock);
76 | $this->validator->validate($zipcode, $constraint);
77 | }
78 |
79 | public static function esInvalidZipCodes(): array
80 | {
81 | return [
82 | ['53456'],
83 | ['0413A'],
84 | ['024567'],
85 | ['es123'],
86 | ['2341'],
87 | ];
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tests/Constraints/CuZipCodeValidatorTest.php:
--------------------------------------------------------------------------------
1 | validator = new ZipCodeValidator;
21 | }
22 |
23 | /**
24 | * @dataProvider cuValidZipCodes
25 | */
26 | public function testValidationOfCuZipCode(string $zipCode): void
27 | {
28 | $constraint = new ZipCode('CU');
29 |
30 | /** @var ExecutionContext|MockObject $contextMock */
31 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
32 | ->disableOriginalConstructor()
33 | ->getMock();
34 |
35 | //be sure that buildViolation never gets called
36 | $contextMock->expects($this->never())->method('buildViolation');
37 |
38 | $contextMock->setConstraint($constraint);
39 | $this->validator->initialize($contextMock);
40 |
41 | // Test some variations
42 | $this->validator->validate($zipCode, $constraint);
43 | }
44 |
45 | public static function cuValidZipCodes(): array
46 | {
47 | return [
48 | ['11300'],
49 | ['22700'],
50 | ['33400'],
51 | ['67600'],
52 | ];
53 | }
54 |
55 | /**
56 | * @dataProvider cuInvalidZipCodes
57 | */
58 | public function testValidationErrorWithInvalidCuZipCode(string $zipcode): void
59 | {
60 | $constraint = new ZipCode('CU');
61 |
62 | $violationBuilderMock = $this->getMockBuilder(ConstraintViolationBuilderInterface::class)
63 | ->disableOriginalConstructor()
64 | ->getMock();
65 | $violationBuilderMock->expects($this->once())->method('setParameter')->willReturnSelf();
66 |
67 | /** @var ExecutionContext|MockObject $contextMock */
68 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
69 | ->disableOriginalConstructor()
70 | ->getMock();
71 | $contextMock->expects($this->once())
72 | ->method('buildViolation')
73 | ->with($constraint->message)
74 | ->willReturn($violationBuilderMock);
75 |
76 | $contextMock->setConstraint($constraint);
77 | $this->validator->initialize($contextMock);
78 | $this->validator->validate($zipcode, $constraint);
79 | }
80 |
81 | public static function cuInvalidZipCodes(): array
82 | {
83 | return [
84 | ['1420'],
85 | ['CA123'],
86 | ['CACAC'],
87 | ['ch1289'],
88 | ['14-241'],
89 | ];
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/tests/Constraints/NaZipCodeValidatorTest.php:
--------------------------------------------------------------------------------
1 | validator = new ZipCodeValidator;
21 | }
22 |
23 | /**
24 | * @dataProvider naValidZipCodes
25 | */
26 | public function testValidationOfNaZipCode(string $zipCode): void
27 | {
28 | $constraint = new ZipCode('NA');
29 |
30 | /** @var ExecutionContext|MockObject $contextMock */
31 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
32 | ->disableOriginalConstructor()
33 | ->getMock();
34 |
35 | //be sure that buildViolation never gets called
36 | $contextMock->expects($this->never())->method('buildViolation');
37 |
38 | $contextMock->setConstraint($constraint);
39 | $this->validator->initialize($contextMock);
40 |
41 | // Test some variations
42 | $this->validator->validate($zipCode, $constraint);
43 | }
44 |
45 | public static function naValidZipCodes(): array
46 | {
47 | return [
48 | ['13006'],
49 | ['22002'],
50 | ['19001'],
51 | ['10033'],
52 | ];
53 | }
54 |
55 | /**
56 | * @dataProvider naInvalidZipCodes
57 | */
58 | public function testValidationErrorWithInvalidNaZipCode(string $zipcode): void
59 | {
60 | $constraint = new ZipCode('NA');
61 |
62 | $violationBuilderMock = $this->getMockBuilder(ConstraintViolationBuilderInterface::class)
63 | ->disableOriginalConstructor()
64 | ->getMock();
65 | $violationBuilderMock->expects($this->once())->method('setParameter')->willReturnSelf();
66 |
67 | /** @var ExecutionContext|MockObject $contextMock */
68 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
69 | ->disableOriginalConstructor()
70 | ->getMock();
71 | $contextMock->expects($this->once())
72 | ->method('buildViolation')
73 | ->with($constraint->message)
74 | ->willReturn($violationBuilderMock);
75 |
76 | $contextMock->setConstraint($constraint);
77 | $this->validator->initialize($contextMock);
78 | $this->validator->validate($zipcode, $constraint);
79 | }
80 |
81 | public static function naInvalidZipCodes(): array
82 | {
83 | return [
84 | ['12345'],
85 | ['14-20'],
86 | ['110655'],
87 | ['024567'],
88 | ['PP023'],
89 | ['AC-70'],
90 | ];
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/tests/Constraints/LtZipCodeValidatorTest.php:
--------------------------------------------------------------------------------
1 | validator = new ZipCodeValidator;
19 | }
20 |
21 | /**
22 | * @dataProvider getValidLithuanianZipCodes
23 | */
24 | public function testValidZipcodes(string $zipCode): void
25 | {
26 | $constraint = new ZipCode('LT');
27 |
28 | /** @var ExecutionContext|MockObject $contextMock */
29 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
30 | ->disableOriginalConstructor()
31 | ->getMock();
32 |
33 | # be sure that buildViolation never gets called
34 | $contextMock->expects($this->never())->method('buildViolation');
35 | $contextMock->setConstraint($constraint);
36 |
37 | $this->validator->initialize($contextMock);
38 | $this->validator->validate($zipCode, $constraint);
39 | }
40 |
41 | /**
42 | * Valid Lithuanian postal codes are five-digit numbers, optionally prefixed with "LT-".
43 | * @see https://en.wikipedia.org/wiki/Postal_codes_in_Lithuania
44 | */
45 | public static function getValidLithuanianZipCodes(): array
46 | {
47 | return [
48 | ['12345'],
49 | ['01234'],
50 | ['98765'],
51 | ['LT-12345'],
52 | ['LT-01234'],
53 | ['LT-98765'],
54 | ];
55 | }
56 |
57 | /**
58 | * @dataProvider getInvalidLithuanianZipCodes
59 | */
60 | public function testInvalidZipcodes(string $zipCode): void
61 | {
62 | $constraint = new ZipCode('LT');
63 |
64 | $violation = $this->createMock(ConstraintViolationBuilderInterface::class);
65 | $violation->expects($this->once())->method('setParameter')->willReturnSelf();
66 |
67 | /** @var ExecutionContext|MockObject $contextMock */
68 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
69 | ->disableOriginalConstructor()
70 | ->getMock();
71 |
72 | # be sure that buildViolation never gets called
73 | $contextMock->expects($this->once())->method('buildViolation')->willReturn($violation);
74 | $contextMock->setConstraint($constraint);
75 |
76 | $this->validator->initialize($contextMock);
77 | $this->validator->validate($zipCode, $constraint);
78 | }
79 |
80 | /**
81 | * Valid Lithuanian postal codes are five-digit numbers, optionally prefixed with "LT-".
82 | * @see https://en.wikipedia.org/wiki/Postal_codes_in_Lithuania
83 | */
84 | public static function getInvalidLithuanianZipCodes(): array
85 | {
86 | return [
87 | ['1234'],
88 | ['LT12345'],
89 | ['LY-12345'],
90 | ['LT-1234'],
91 | ];
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/tests/Constraints/MdZipCodeValidatorTest.php:
--------------------------------------------------------------------------------
1 | validator = new ZipCodeValidator;
19 | }
20 |
21 | /**
22 | * @dataProvider getValidMoldovaZipCodes
23 | */
24 | public function testValidZipcodes(string $zipCode): void
25 | {
26 | $constraint = new ZipCode('MD');
27 |
28 | /** @var ExecutionContext|MockObject $contextMock */
29 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
30 | ->disableOriginalConstructor()
31 | ->getMock();
32 |
33 | # be sure that buildViolation never gets called
34 | $contextMock->expects($this->never())->method('buildViolation');
35 | $contextMock->setConstraint($constraint);
36 |
37 | $this->validator->initialize($contextMock);
38 | $this->validator->validate($zipCode, $constraint);
39 | }
40 |
41 | /**
42 | * Valid Moldova postal codes are four-digit numbers, optionally prefixed with "MD-".
43 | * @see https://en.wikipedia.org/wiki/Postal_codes_in_Moldova
44 | */
45 | public static function getValidMoldovaZipCodes(): array
46 | {
47 | return [
48 | ['1234'],
49 | ['0123'],
50 | ['9876'],
51 | ['MD-1234'],
52 | ['MD-0123'],
53 | ['MD-9876'],
54 | ];
55 | }
56 |
57 | /**
58 | * @dataProvider getInvalidMoldovaZipCodes
59 | */
60 | public function testInvalidZipcodes(string $zipCode): void
61 | {
62 | $constraint = new ZipCode('MD');
63 |
64 | $violation = $this->createMock(ConstraintViolationBuilderInterface::class);
65 | $violation->expects($this->once())->method('setParameter')->willReturnSelf();
66 |
67 | /** @var ExecutionContext|MockObject $contextMock */
68 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
69 | ->disableOriginalConstructor()
70 | ->getMock();
71 |
72 | # be sure that buildViolation never gets called
73 | $contextMock->expects($this->once())->method('buildViolation')->willReturn($violation);
74 | $contextMock->setConstraint($constraint);
75 |
76 | $this->validator->initialize($contextMock);
77 | $this->validator->validate($zipCode, $constraint);
78 | }
79 |
80 | /**
81 | * Valid Moldova postal codes are four-digit numbers, optionally prefixed with "MD-".
82 | * @see https://en.wikipedia.org/wiki/Postal_codes_in_Moldova
83 | */
84 | public static function getInvalidMoldovaZipCodes(): array
85 | {
86 | return [
87 | ['123'],
88 | ['12345'],
89 | ['MD12345'],
90 | ['MO-12345'],
91 | ['MD-12345'],
92 | ];
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mrtnschndlr@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/tests/Constraints/JmZipCodeValidatorTest.php:
--------------------------------------------------------------------------------
1 | validator = new ZipCodeValidator;
22 | }
23 |
24 | /**
25 | * This test verifies that all known Jamaica codes are valid.
26 | *
27 | * @dataProvider getValidJamaicaZipCodes
28 | */
29 | public function testValidZipcodes(string $zipCode): void
30 | {
31 | $constraint = new ZipCode('JM');
32 |
33 | /** @var ExecutionContext|MockObject $contextMock */
34 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
35 | ->disableOriginalConstructor()
36 | ->getMock();
37 |
38 | # be sure that buildViolation never gets called
39 | $contextMock->expects($this->never())->method('buildViolation');
40 | $contextMock->setConstraint($constraint);
41 |
42 | $this->validator->initialize($contextMock);
43 | $this->validator->validate($zipCode, $constraint);
44 | }
45 |
46 | /**
47 | * used postal codes
48 | * from https://en.wikipedia.org/wiki/Postal_codes_in_Jamaica
49 | */
50 | public static function getValidJamaicaZipCodes(): array
51 | {
52 | return [
53 | ['KN'],
54 | ['AW'],
55 | ['CE'],
56 | ['TS'],
57 | ['PD'],
58 | ['MY'],
59 | ];
60 | }
61 |
62 | /**
63 | * This test verifies that all known Jamaica codes are valid.
64 | *
65 | * @dataProvider getInvalidJamaicaZipCodes
66 | */
67 | public function testInvalidZipcodes(string $zipCode): void
68 | {
69 | $constraint = new ZipCode('JM');
70 |
71 | $violation = $this->createMock(ConstraintViolationBuilderInterface::class);
72 | $violation->expects($this->once())->method('setParameter')->willReturnSelf();
73 |
74 | /** @var ExecutionContext|MockObject $contextMock */
75 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
76 | ->disableOriginalConstructor()
77 | ->getMock();
78 |
79 | # be sure that buildViolation never gets called
80 | $contextMock->expects($this->once())->method('buildViolation')->willReturn($violation);
81 | $contextMock->setConstraint($constraint);
82 |
83 | $this->validator->initialize($contextMock);
84 | $this->validator->validate($zipCode, $constraint);
85 | }
86 |
87 | /**
88 | * used postal codes
89 | * from https://en.wikipedia.org/wiki/Postal_codes_in_Jamaica
90 | */
91 | public static function getInvalidJamaicaZipCodes(): array
92 | {
93 | return [
94 | ['12'],
95 | ['\W'],
96 | ['A1'],
97 | ['0z'],
98 | ];
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Constraint Class for international Zipcode Validation
2 |
3 | [](https://github.com/barbieswimcrew/zip-code-validator/actions/workflows/ci.yaml)
4 | [](https://packagist.org/packages/barbieswimcrew/zip-code-validator)
5 | [](https://packagist.org/packages/barbieswimcrew/zip-code-validator)
6 | [](./composer.json)
7 | [](https://github.com/barbieswimcrew/zip-code-validator/stargazers)
8 | [](https://github.com/barbieswimcrew/zip-code-validator/blob/master/LICENSE)
9 |
10 | ## Installation
11 | This package uses Composer, please checkout the [composer website](https://getcomposer.org) for more information.
12 |
13 | The following command will install `zip-code-validator` into your project. It will also add a new entry in your `composer.json` and update the `composer.lock` as well.
14 |
15 | ```bash
16 | $ composer require barbieswimcrew/zip-code-validator
17 | ```
18 |
19 | > This package follows the PSR-4 convention names for its classes, which means you can easily integrate `zip-code-validator` classes loading in your own autoloader.
20 |
21 | ## What now?
22 | For validating a zip code you need to instantiate a new ZipCode class provided by this package to set it as a constraint to your form field, for example:
23 |
24 | ```php
25 | createFormBuilder($address)
28 | ->add('zipcode', TextType::class, [
29 | 'constraints' => [
30 | new ZipCodeValidator\Constraints\ZipCode([
31 | 'iso' => 'DE'
32 | ])
33 | ]
34 | ])
35 | ->add('save', SubmitType::class, ['label' => 'Create Task'])
36 | ->getForm();
37 | ```
38 |
39 | Another way would be to use the constraint as an annotation of a class property, for example:
40 | ```php
41 | 'DE'])
63 | protected $zipCode;
64 | }
65 | ```
66 |
67 | > Please consider to inject a valid ISO 3166 2-letter country code (e.g. DE, US, FR)!
68 |
69 | > NOTE: This library validates against known zip code regex patterns and does not validate the existence of a zipcode.
70 |
71 | ### Use a getter to inject the country code dynamically
72 |
73 | If you have a form, in which the user can select a country, you may want to validate the zip code dynamically.
74 | In this case you can use the `getter` option instead:
75 |
76 | ```php
77 | country;
93 | }
94 | }
95 | ```
96 |
97 | To disable that the validator throws an exception, when the zip code pattern is not available for a country,
98 | you can set the `strict` option to `FALSE`.
99 |
100 | ```php
101 | /**
102 | * @ZipCode(getter="getCountry", strict=false)
103 | */
104 | protected $zipCode;
105 | ```
106 |
107 | The validator will not validate empty strings and null values. To disallow them use the Symfony stock `NotNull` or `NotBlank` constraint in addition to `ZipCode`.
108 |
109 | ```php
110 | /**
111 | * @ZipCode(getter="getCountry")
112 | * @NotBlank
113 | */
114 | protected $zipCode;
115 | ```
116 |
117 | ### Case insensitive zip code matching
118 | In case you want to match the zip code in a case insensitive way you have to pass a `caseSensitiveCheck` parameter with `false` value via the constructor:
119 | ```php
120 | $constraint = new ZipCode([
121 | 'iso' => 'GB',
122 | 'caseSensitiveCheck' => false
123 | ]);
124 |
125 | ```
126 | By the default the library is using case sensitive zip code matching.
127 |
128 | ## Copying / License
129 | This repository is distributed under the MIT License (MIT). You can find the whole license text in the [LICENSE](LICENSE) file.
130 |
--------------------------------------------------------------------------------
/tests/Constraints/ZipCodeValidatorTest.php:
--------------------------------------------------------------------------------
1 | contextMock = $this->getMockBuilder(ExecutionContext::class)
25 | ->disableOriginalConstructor()
26 | ->getMock();
27 | $this->validator = new ZipCodeValidator;
28 | }
29 |
30 | /**
31 | * @dataProvider validZipCodes
32 | * @doesNotPerformAssertions
33 | */
34 | public function testZipCodeValidationWithIso(string|int $isoCode, string|int|null $value): void
35 | {
36 | $constraint = new ZipCode($isoCode);
37 | $this->validator->validate($value, $constraint);
38 | }
39 |
40 | /**
41 | * @dataProvider
42 | */
43 | public static function validZipCodes(): array
44 | {
45 | return [
46 | ['HK', 999077],
47 | ['KE', 12345],
48 | ['MU', '15325'],
49 | ['MU', '153BU123'],
50 | ['NL', '1234AB'],
51 | ['NL', '1234 AB'],
52 | ['PN', 'PCRN 1ZZ']
53 | ];
54 | }
55 |
56 | public function testValidZipCodeValidationWithGetter(): void
57 | {
58 | /** @var ZipCode|MockObject $constraintMock */
59 | $constraintMock = $this->getMockBuilder(ZipCode::class)
60 | ->disableOriginalConstructor()
61 | ->getMock();
62 |
63 | $constraintMock->iso = null;
64 | $constraintMock->getter = 'myValidationMethod';
65 |
66 | $gbObject = new \ZipCodeValidator\Tests\Fixtures\IsoObject('VN');
67 |
68 | $this->contextMock = $this->getMockBuilder(ExecutionContext::class)
69 | ->disableOriginalConstructor()
70 | ->getMock();
71 | $this->contextMock->expects($this->once())
72 | ->method('getObject')
73 | ->willReturn($gbObject);
74 |
75 | $this->contextMock->setConstraint($constraintMock);
76 | $this->validator->initialize($this->contextMock);
77 | $this->validator->validate(123456, $constraintMock);
78 | }
79 |
80 | public function testUnexpectedTypeException(): void
81 | {
82 | $constraint = $this->getMockBuilder(Constraint::class)->disableOriginalConstructor()->getMock();
83 | $this->expectException(UnexpectedTypeException::class);
84 | $this->validator = new ZipCodeValidator();
85 | $this->validator->validate('FOO', $constraint);
86 | }
87 |
88 | /**
89 | * @doesNotPerformAssertions
90 | */
91 | public function testValidationIgnoresBlankValues(): void
92 | {
93 | $constraint = new ZipCode('HK');
94 |
95 | $this->validator->validate('', $constraint);
96 | $this->validator->validate(null, $constraint);
97 | }
98 |
99 | public function testValidationReturnsNothingOnEmptyIso(): void
100 | {
101 | /** @var ZipCode|MockObject $constraintMock */
102 | $constraintMock = $this->getMockBuilder(ZipCode::class)
103 | ->disableOriginalConstructor()
104 | ->getMock();
105 |
106 | $constraintMock->iso = '';
107 | $constraintMock->getter = 'myValidationMethod';
108 |
109 | $isoObject = new \ZipCodeValidator\Tests\Fixtures\IsoObject('');
110 |
111 | /** @var ExecutionContext|MockObject $this->contextMock */
112 | $this->contextMock = $this->getMockBuilder(ExecutionContext::class)
113 | ->disableOriginalConstructor()
114 | ->getMock();
115 | $this->contextMock->expects($this->once())
116 | ->method('getObject')
117 | ->willReturn($isoObject);
118 |
119 | $this->contextMock->setConstraint($constraintMock);
120 | $this->validator->initialize($this->contextMock);
121 |
122 | $this->validator->validate('dummy', $constraintMock);
123 | }
124 |
125 | public function testConstraintDefinitionExceptionWhenInvalidIsoInStrictMode(): void
126 | {
127 | $constraint = new ZipCode('FOO');
128 | $this->expectException(ConstraintDefinitionException::class);
129 | $this->validator->validate('dummy', $constraint);
130 | }
131 |
132 | }
133 |
--------------------------------------------------------------------------------
/tests/Constraints/GbZipCodeValidatorTest.php:
--------------------------------------------------------------------------------
1 | validator = new ZipCodeValidator;
20 | }
21 |
22 | /**
23 | * @dataProvider GbZipCodes
24 | */
25 | public function testValidationOfGbZipCodeWithIso($zipCode): void
26 | {
27 | $constraint = new ZipCode('GB');
28 |
29 | /** @var ExecutionContext|MockObject $contextMock */
30 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
31 | ->disableOriginalConstructor()
32 | ->getMock();
33 |
34 | //be sure that buildViolation never gets called
35 | $contextMock->expects($this->never())->method('buildViolation');
36 |
37 | $contextMock->setConstraint($constraint);
38 | $this->validator->initialize($contextMock);
39 |
40 | // Test some variations
41 | $this->validator->validate($zipCode, $constraint);
42 | }
43 |
44 | /**
45 | * @dataProvider
46 | */
47 | public static function gbZipCodes(): array
48 | {
49 | return [
50 | ['EC1A 1BB'],
51 | ['W1A 0AX'],
52 | ['M1 1AE'],
53 | ['B33 8TH'],
54 | ['CR2 6XH'],
55 | ['DN55 1PT'],
56 | ['BFPO 801'],
57 | ['GIR 0AA'],
58 | ['PH1 5RB'],
59 | ['CO4 3SQ'],
60 | ['CO4 3SQ'],
61 | ];
62 | }
63 |
64 | /**
65 | * @dataProvider gbZipCodesSmallCaps
66 | */
67 | public function testValidationOfGbZipCodeWithIsoAndSmallCaps(string $zipCode): void
68 | {
69 | $constraint = new ZipCode([
70 | 'iso' => 'GB',
71 | 'caseSensitiveCheck' => false
72 | ]);
73 |
74 | /** @var ExecutionContext|MockObject $contextMock */
75 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
76 | ->disableOriginalConstructor()
77 | ->getMock();
78 |
79 | //be sure that buildViolation never gets called
80 | $contextMock->expects($this->never())->method('buildViolation');
81 |
82 | $contextMock->setConstraint($constraint);
83 | $this->validator->initialize($contextMock);
84 |
85 | $this->validator->validate($zipCode, $constraint);
86 | }
87 |
88 | /**
89 | * @dataProvider
90 | */
91 | public static function gbZipCodesSmallCaps(): array
92 | {
93 | return [
94 | ['ec1a 1bb'],
95 | ['w1a 0ax'],
96 | ['m1 1ae'],
97 | ['b33 8th'],
98 | ['cr2 6xh'],
99 | ['dn55 1pt'],
100 | ['bfpo 801'],
101 | ['gir 0aa'],
102 | ['ph1 5rb'],
103 | ['co4 3sq']
104 | ];
105 | }
106 |
107 | public function testValidationErrorWithInvalidGbZipCode(): void
108 | {
109 | $value = 'invalid';
110 | $constraint = new ZipCode('GB');
111 |
112 | $violationBuilderMock = $this->getMockBuilder(ConstraintViolationBuilderInterface::class)
113 | ->disableOriginalConstructor()
114 | ->getMock();
115 | $violationBuilderMock->expects($this->once())->method('setParameter')->willReturnSelf();
116 |
117 | /** @var ExecutionContext|MockObject $contextMock */
118 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
119 | ->disableOriginalConstructor()
120 | ->getMock();
121 | $contextMock->expects($this->once())
122 | ->method('buildViolation')
123 | ->with($constraint->message)
124 | ->willReturn($violationBuilderMock);
125 |
126 | $contextMock->setConstraint($constraint);
127 | $this->validator->initialize($contextMock);
128 | $this->validator->validate($value, $constraint);
129 | }
130 |
131 | public function testValidationErrorWithValidGbZipCodeWithExtraLeadingChars(): void
132 | {
133 | $value = 'XEC1A 1BB';
134 | $constraint = new ZipCode('GB');
135 |
136 | $violationBuilderMock = $this->getMockBuilder(ConstraintViolationBuilderInterface::class)
137 | ->disableOriginalConstructor()
138 | ->getMock();
139 | $violationBuilderMock->expects($this->once())->method('setParameter')->willReturnSelf();
140 |
141 | /** @var ExecutionContext|MockObject $contextMock */
142 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
143 | ->disableOriginalConstructor()
144 | ->getMock();
145 | $contextMock->expects($this->once())
146 | ->method('buildViolation')
147 | ->with($constraint->message)
148 | ->willReturn($violationBuilderMock);
149 |
150 | $contextMock->setConstraint($constraint);
151 | $this->validator->initialize($contextMock);
152 | $this->validator->validate($value, $constraint);
153 | }
154 |
155 | public function testValidationErrorWithValidGbZipCodeWithExtraTrailingChars(): void
156 | {
157 | $value = 'EC1A 1BBX';
158 | $constraint = new ZipCode('GB');
159 |
160 | $violationBuilderMock = $this->getMockBuilder(ConstraintViolationBuilderInterface::class)
161 | ->disableOriginalConstructor()
162 | ->getMock();
163 | $violationBuilderMock->expects($this->once())->method('setParameter')->willReturnSelf();
164 |
165 | /** @var ExecutionContext|MockObject $contextMock */
166 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
167 | ->disableOriginalConstructor()
168 | ->getMock();
169 | $contextMock->expects($this->once())
170 | ->method('buildViolation')
171 | ->with($constraint->message)
172 | ->willReturn($violationBuilderMock);
173 |
174 | $contextMock->setConstraint($constraint);
175 | $this->validator->initialize($contextMock);
176 | $this->validator->validate($value, $constraint);
177 | }
178 |
179 | public function testEmptyIsoWithValidZipCodeWillCallGetterMethodOnObject(): void
180 | {
181 | $value = 'EC1A 1BB';
182 | /** @var ZipCode|MockObject $constraintMock */
183 | $constraintMock = $this->getMockBuilder(ZipCode::class)
184 | ->disableOriginalConstructor()
185 | ->getMock();
186 |
187 | $constraintMock->iso = null;
188 | $constraintMock->getter = 'myValidationMethod';
189 |
190 | $gbObject = new \ZipCodeValidator\Tests\Fixtures\IsoObject('GB');
191 |
192 | /** @var ExecutionContext|MockObject $contextMock */
193 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
194 | ->disableOriginalConstructor()
195 | ->getMock();
196 | $contextMock->expects($this->once())
197 | ->method('getObject')
198 | ->willReturn($gbObject);
199 |
200 | $contextMock->setConstraint($constraintMock);
201 | $this->validator->initialize($contextMock);
202 | $this->validator->validate($value, $constraintMock);
203 | }
204 |
205 | public function testConstraintDefinitionExceptionWhenCallableMethodNotExists(): void
206 | {
207 | $value = 'EC1A 1BB';
208 | /** @var ZipCode|MockObject $constraintMock */
209 | $constraintMock = $this->getMockBuilder(ZipCode::class)
210 | ->disableOriginalConstructor()
211 | ->getMock();
212 |
213 | $constraintMock->iso = null;
214 | $constraintMock->getter = 'myFooMethod'; // not existing in GbObject
215 |
216 | $gbObject = new \ZipCodeValidator\Tests\Fixtures\IsoObject('GB');
217 |
218 | /** @var ExecutionContext|MockObject $contextMock */
219 | $contextMock = $this->getMockBuilder(ExecutionContext::class)
220 | ->disableOriginalConstructor()
221 | ->getMock();
222 | $contextMock->expects($this->once())
223 | ->method('getObject')
224 | ->willReturn($gbObject);
225 |
226 | $contextMock->setConstraint($constraintMock);
227 | $this->validator->initialize($contextMock);
228 |
229 | $exceptionMessage = 'Method "myFooMethod" used as iso code getter does not exist in class ' .
230 | get_class($gbObject);
231 |
232 | $this->expectException(ConstraintDefinitionException::class);
233 | $this->expectExceptionMessage($exceptionMessage);
234 | $this->validator->validate($value, $constraintMock);
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/src/ZipCodeValidator/Constraints/ZipCodeValidator.php:
--------------------------------------------------------------------------------
1 |
18 | * $patterns = [];
19 | * $data = json_decode(file_get_contents('http://i18napis.appspot.com/address/data'), true);
20 | * $countries = explode('~', $data['countries']);
21 | * foreach ($countries as $country) {
22 | * $data = json_decode(file_get_contents('http://i18napis.appspot.com/address/data/'.$country), true);
23 | * if (isset($data['zip'])) {
24 | * $patterns[$country] = $data['zip'];
25 | * }
26 | * }
27 | * var_export($patterns);
28 | *
29 | */
30 | private array $patterns = [
31 | 'AC' => 'ASCN 1ZZ',
32 | 'AD' => 'AD[1-7]0\\d',
33 | 'AF' => '\\d{4}',
34 | 'AI' => '(?:AI-)?2640',
35 | 'AL' => '\\d{4}',
36 | 'AM' => '(?:37)?\\d{4}',
37 | 'AR' => '((?:[A-HJ-NP-Z])?\\d{4})([A-Z]{3})?',
38 | 'AS' => '(96799)(?:[ \\-](\\d{4}))?',
39 | 'AT' => '\\d{4}',
40 | 'AU' => '\\d{4}',
41 | 'AX' => '22\\d{3}',
42 | 'AZ' => '\\d{4}',
43 | 'BA' => '\\d{5}',
44 | 'BB' => 'BB\\d{5}',
45 | 'BD' => '\\d{4}',
46 | 'BE' => '\\d{4}',
47 | 'BG' => '\\d{4}',
48 | 'BH' => '(?:\\d|1[0-2])\\d{2}',
49 | 'BL' => '9[78][01]\\d{2}',
50 | 'BM' => '[A-Z]{2} ?[A-Z0-9]{2}',
51 | 'BN' => '[A-Z]{2} ?\\d{4}',
52 | 'BR' => '\\d{5}-?\\d{3}',
53 | 'BT' => '\\d{5}',
54 | 'BY' => '\\d{6}',
55 | 'CA' => '[ABCEGHJKLMNPRSTVXY]\\d[ABCEGHJ-NPRSTV-Z] ?\\d[ABCEGHJ-NPRSTV-Z]\\d',
56 | 'CC' => '6799',
57 | 'CH' => '[1-9]\\d{3}',
58 | 'CL' => '\\d{7}',
59 | 'CN' => '\\d{6}',
60 | 'CO' => '\\d{6}',
61 | 'CR' => '\\d{4,5}|\\d{3}-\\d{4}',
62 | 'CU' => '\\d{5}',
63 | 'CV' => '\\d{4}',
64 | 'CX' => '6798',
65 | 'CY' => '\\d{4}',
66 | 'CZ' => '\\d{3} ?\\d{2}',
67 | 'DE' => '\\d{5}',
68 | 'DK' => '\\d{4}',
69 | 'DO' => '\\d{5}',
70 | 'DZ' => '\\d{5}',
71 | 'EC' => '\\d{6}',
72 | 'EE' => '\\d{5}',
73 | 'EG' => '\\d{5}',
74 | 'EH' => '\\d{5}',
75 | 'ES' => '^([0-4]\\d{4}|5[0-2]\\d{3})$',
76 | 'ET' => '\\d{4}',
77 | 'FI' => '\\d{5}',
78 | 'FK' => 'FIQQ 1ZZ',
79 | 'FM' => '(9694[1-4])(?:[ \\-](\\d{4}))?',
80 | 'FO' => '\\d{3}',
81 | 'FR' => '\\d{1}(?:A|B|\\d{1}) ?\\d{3}',
82 | 'GB' => '^GIR ?0AA$|^(?:(?:AB|AL|B|BA|BB|BD|BH|BL|BN|BR|BS|BT|BX|CA|CB|CF|CH|CM|CO|CR|CT|CV|CW|DA|DD|DE|DG|DH|DL|DN|DT|DY|E|EC|EH|EN|EX|FK|FY|G|GL|GY|GU|HA|HD|HG|HP|HR|HS|HU|HX|IG|IM|IP|IV|JE|KA|KT|KW|KY|L|LA|LD|LE|LL|LN|LS|LU|M|ME|MK|ML|N|NE|NG|NN|NP|NR|NW|OL|OX|PA|PE|PH|PL|PO|PR|RG|RH|RM|S|SA|SE|SG|SK|SL|SM|SN|SO|SP|SR|SS|ST|SW|SY|TA|TD|TF|TN|TQ|TR|TS|TW|UB|W|WA|WC|WD|WF|WN|WR|WS|WV|YO|ZE)(?:\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}))$|^BFPO ?\\d{1,4}$',
83 | 'GE' => '\\d{4}',
84 | 'GF' => '9[78]3\\d{2}',
85 | 'GG' => 'GY\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}',
86 | 'GI' => 'GX11 1AA',
87 | 'GL' => '39\\d{2}',
88 | 'GN' => '\\d{3}',
89 | 'GP' => '9[78][01]\\d{2}',
90 | 'GR' => '\\d{3} ?\\d{2}',
91 | 'GS' => 'SIQQ 1ZZ',
92 | 'GT' => '\\d{5}',
93 | 'GU' => '(969(?:[12]\\d|3[12]))(?:[ \\-](\\d{4}))?',
94 | 'GW' => '\\d{4}',
95 | 'HK' => '999077', //Hong Kong
96 | 'HM' => '\\d{4}',
97 | 'HN' => '\\d{5}',
98 | 'HR' => '\\d{5}',
99 | 'HT' => '\\d{4}',
100 | 'HU' => '\\d{4}',
101 | 'ID' => '\\d{5}',
102 | 'IE' => '[\\dA-Z]{3}( ?[\\dA-Z]{4})?',
103 | 'IL' => '\\d{5}(?:\\d{2})?',
104 | 'IM' => 'IM\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}',
105 | 'IN' => '\\d{6}',
106 | 'IO' => 'BBND 1ZZ',
107 | 'IQ' => '\\d{5}',
108 | 'IR' => '\\d{5}-?\\d{5}',
109 | 'IS' => '\\d{3}',
110 | 'IT' => '\\d{5}',
111 | 'JE' => 'JE\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}',
112 | 'JM' => '[a-zA-Z]{2}',
113 | 'JO' => '\\d{5}',
114 | 'JP' => '\\d{3}-?\\d{4}',
115 | 'KE' => '\\d{5}',
116 | 'KG' => '\\d{6}',
117 | 'KH' => '\\d{5}',
118 | 'KR' => '\\d{3}(?:\\d{2}|-\\d{3})',
119 | 'KW' => '\\d{5}',
120 | 'KY' => 'KY\\d-\\d{4}',
121 | 'KZ' => '\\d{6}',
122 | 'LA' => '\\d{5}',
123 | 'LB' => '(?:\\d{4})(?: ?(?:\\d{4}))?',
124 | 'LI' => '948[5-9]|949[0-7]',
125 | 'LK' => '\\d{5}',
126 | 'LR' => '\\d{4}',
127 | 'LS' => '\\d{3}',
128 | 'LT' => '(LT-)?\\d{5}',
129 | 'LU' => '\\d{4}',
130 | 'LV' => '(LV-)?\\d{4}',
131 | 'MA' => '\\d{5}',
132 | 'MC' => '980\\d{2}',
133 | 'MD' => '(MD-)?\\d{4}',
134 | 'ME' => '8\\d{4}',
135 | 'MF' => '9[78][01]\\d{2}',
136 | 'MG' => '\\d{3}',
137 | 'MH' => '(969[67]\\d)(?:[ \\-](\\d{4}))?',
138 | 'MK' => '\\d{4}',
139 | 'MM' => '\\d{5}',
140 | 'MN' => '\\d{5}',
141 | 'MO' => '999078', //Macau
142 | 'MP' => '(9695[012])(?:[ \\-](\\d{4}))?',
143 | 'MQ' => '9[78]2\\d{2}',
144 | 'MT' => '[A-Z]{3} ?\\d{2,4}',
145 | 'MU' => '\\d{3}(?:\\d{2}|[A-Z]{2}\\d{3})',
146 | 'MV' => '\\d{5}',
147 | 'MX' => '\\d{5}',
148 | 'MY' => '\\d{5}',
149 | 'MZ' => '\\d{4}',
150 | 'NA' => '\\d{2}0\d{2}',
151 | 'NC' => '988\\d{2}',
152 | 'NE' => '\\d{4}',
153 | 'NF' => '2899',
154 | 'NG' => '\\d{6}',
155 | 'NI' => '\\d{5}',
156 | 'NL' => '[1-9]{1}\\d{3}([ ]?[A-Z]{2})', // Changed: ZipCode cannot start with 0, fixed optional letters
157 | 'NO' => '\\d{4}',
158 | 'NP' => '\\d{5}',
159 | 'NZ' => '\\d{4}',
160 | 'OM' => '(?:PC )?\\d{3}',
161 | 'PA' => '\\d{4}',
162 | 'PE' => '(?:LIMA \\d{1,2}|CALLAO 0?\\d)|[0-2]\\d{4}',
163 | 'PF' => '987\\d{2}',
164 | 'PG' => '\\d{3}',
165 | 'PH' => '\\d{4}',
166 | 'PK' => '\\d{5}',
167 | 'PL' => '\\d{2}-\\d{3}',
168 | 'PM' => '9[78]5\\d{2}',
169 | 'PN' => 'PCRN 1ZZ',
170 | 'PR' => '(00[679]\\d{2})(?:[ \\-](\\d{4}))?',
171 | 'PT' => '\\d{4}-\\d{3}',
172 | 'PW' => '(969(?:39|40))(?:[ \\-](\\d{4}))?',
173 | 'PY' => '\\d{4}',
174 | 'RE' => '9[78]4\\d{2}',
175 | 'RO' => '\\d{6}',
176 | 'RS' => '\\d{5,6}',
177 | 'RU' => '\\d{6}',
178 | 'SA' => '\\d{5}',
179 | 'SE' => '\\d{3} ?\\d{2}',
180 | 'SG' => '\\d{6}',
181 | 'SH' => '(?:ASCN|STHL) 1ZZ',
182 | 'SI' => '\\d{4}',
183 | 'SJ' => '\\d{4}',
184 | 'SK' => '\\d{3} ?\\d{2}',
185 | 'SM' => '4789\\d',
186 | 'SN' => '\\d{5}',
187 | 'SO' => '[A-Z]{2} ?\\d{5}',
188 | 'SV' => 'CP [1-3][1-7][0-2]\\d',
189 | 'SZ' => '[HLMS]\\d{3}',
190 | 'TA' => 'TDCU 1ZZ',
191 | 'TC' => 'TKCA 1ZZ',
192 | 'TH' => '\\d{5}',
193 | 'TJ' => '\\d{6}',
194 | 'TM' => '\\d{6}',
195 | 'TN' => '\\d{4}',
196 | 'TR' => '\\d{5}',
197 | 'TW' => '\\d{3}(?:\\d{2})?',
198 | 'TZ' => '\\d{4,5}',
199 | 'UA' => '\\d{5}',
200 | 'UM' => '96898',
201 | 'US' => '(\\d{5})(?:[ \\-](\\d{4}))?',
202 | 'UY' => '\\d{5}',
203 | 'UZ' => '\\d{6}',
204 | 'VA' => '00120',
205 | 'VC' => 'VC\\d{4}',
206 | 'VE' => '\\d{4}',
207 | 'VG' => 'VG\\d{4}',
208 | 'VI' => '(008(?:(?:[0-4]\\d)|(?:5[01])))(?:[ \\-](\\d{4}))?',
209 | 'VN' => '\\d{6}',
210 | 'WF' => '986\\d{2}',
211 | 'XK' => '[1-7]\\d{4}',
212 | 'YT' => '976\\d{2}',
213 | 'ZA' => '\\d{4}',
214 | 'ZM' => '\\d{5}',
215 | ];
216 |
217 | /**
218 | * {@inheritdoc}
219 | */
220 | public function validate($value, Constraint $constraint): void
221 | {
222 | if (!$constraint instanceof ZipCode) {
223 | throw new UnexpectedTypeException($constraint, ZipCode::class);
224 | }
225 |
226 | if (null === $value || '' === $value) {
227 | return;
228 | }
229 |
230 | if (null === $constraint->iso || !($iso = strtoupper($constraint->iso))) {
231 | // if iso code is not specified, try to fetch it via getter from the object, which is currently validated
232 | $object = $this->context->getObject();
233 | $getter = $constraint->getter;
234 |
235 | if (!is_callable(array($object, $getter))) {
236 | $message = 'Method "%s" used as iso code getter does not exist in class %s';
237 | throw new ConstraintDefinitionException(sprintf($message, $getter, get_class($object)));
238 | }
239 |
240 | $iso = strtoupper($object->$getter() ?? '');
241 | }
242 |
243 | if(empty($iso)){
244 | return;
245 | }
246 |
247 | // check whether pattern for iso code is specified
248 | if (!isset($this->patterns[$iso])) {
249 | if ($constraint->strict) {
250 | throw new ConstraintDefinitionException(sprintf('Invalid iso code "%s" for validation', $iso));
251 | }
252 |
253 | return;
254 | }
255 |
256 | $pattern = $this->patterns[$iso];
257 | $caseSensitive = ($constraint->caseSensitiveCheck) ? '' : 'i';
258 | if (!preg_match("/^{$pattern}$/" . $caseSensitive, $value, $matches)) {
259 | // If you're using the new 2.5 validation API (you probably are!)
260 | $this->context->buildViolation($constraint->message)
261 | ->setParameter('%string%', $value)
262 | ->addViolation();
263 | }
264 | }
265 | }
266 |
--------------------------------------------------------------------------------