├── .gitignore
├── src
├── Exception
│ ├── ExceptionInterface.php
│ └── UnknownZoneException.php
├── Repository
│ ├── ZoneRepositoryInterface.php
│ └── ZoneRepository.php
├── Model
│ ├── ZoneMemberEntityInterface.php
│ ├── ZoneMemberInterface.php
│ ├── ZoneMemberEu.php
│ ├── ZoneMemberZone.php
│ ├── ZoneInterface.php
│ ├── ZoneMember.php
│ ├── ZoneEntityInterface.php
│ ├── Zone.php
│ └── ZoneMemberCountry.php
└── Matcher
│ ├── ZoneMatcherInterface.php
│ └── ZoneMatcher.php
├── phpcs.xml
├── .editorconfig
├── .travis.yml
├── phpunit.xml
├── composer.json
├── LICENSE
├── tests
├── Model
│ ├── ZoneMemberTest.php
│ ├── ZoneMemberEuTest.php
│ ├── ZoneMemberZoneTest.php
│ ├── ZoneTest.php
│ └── ZoneMemberCountryTest.php
├── Matcher
│ └── ZoneMatcherTest.php
└── Repository
│ └── ZoneRepositoryTest.php
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | composer.lock
3 |
--------------------------------------------------------------------------------
/src/Exception/ExceptionInterface.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PSR2 excluding line length
5 |
6 |
7 |
8 |
9 |
10 | 0
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; top-most EditorConfig file
2 | root = true
3 |
4 | ; Unix-style newlines
5 | [*]
6 | charset = utf-8
7 | end_of_line = LF
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.{php,html,twig}]
12 | indent_style = space
13 | indent_size = 4
14 |
15 | [*.md]
16 | max_line_length = 80
17 |
18 | [COMMIT_EDITMSG]
19 | max_line_length = 0
20 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: php
3 |
4 | php:
5 | - 7.1
6 | - 7.0
7 | - 5.6
8 | - 5.5
9 |
10 | install:
11 | - composer self-update
12 | - composer install
13 |
14 | script:
15 | - ./vendor/bin/phpunit -c ./phpunit.xml --coverage-text --strict
16 | - ./vendor/bin/phpcs --standard=phpcs.xml src -s
17 | - ./vendor/bin/phpcs --standard=phpcs.xml tests -s
18 |
19 | matrix:
20 | fast_finish: true
21 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 | ./tests/
15 |
16 |
17 |
18 |
19 |
20 | ./src/
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/Repository/ZoneRepositoryInterface.php:
--------------------------------------------------------------------------------
1 | =5.5.0",
8 | "commerceguys/addressing": "~1.0"
9 | },
10 | "require-dev": {
11 | "phpunit/phpunit": "~4.0",
12 | "mikey179/vfsstream": "1.*",
13 | "squizlabs/php_codesniffer": "2.*"
14 | },
15 | "autoload": {
16 | "psr-4": {
17 | "CommerceGuys\\Zone\\": "src"
18 | }
19 | },
20 | "autoload-dev": {
21 | "psr-4": {
22 | "CommerceGuys\\Zone\\Tests\\": "tests"
23 | }
24 | },
25 | "authors": [
26 | {
27 | "name": "Bojan Zivanovic"
28 | }
29 | ],
30 | "extra": {
31 | "branch-alias": {
32 | "dev-master": "1.x-dev"
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Model/ZoneMemberInterface.php:
--------------------------------------------------------------------------------
1 | name = 'EU';
21 | }
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function match(AddressInterface $address)
27 | {
28 | $euCountries = [
29 | 'AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI',
30 | 'FR', 'GB', 'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV',
31 | 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK',
32 | ];
33 | $countryCode = $address->getCountryCode();
34 |
35 | return in_array($countryCode, $euCountries);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Commerce Guys
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 |
--------------------------------------------------------------------------------
/src/Model/ZoneMemberZone.php:
--------------------------------------------------------------------------------
1 | zone->getName();
25 | }
26 |
27 | /**
28 | * Gets the zone.
29 | *
30 | * @return ZoneInterface The zone matched by the zone member.
31 | */
32 | public function getZone()
33 | {
34 | return $this->zone;
35 | }
36 |
37 | /**
38 | * Sets the zone.
39 | *
40 | * @param ZoneEntityInterface $zone The zone matched by the zone member.
41 | *
42 | * @return self
43 | */
44 | public function setZone(ZoneEntityInterface $zone)
45 | {
46 | $this->zone = $zone;
47 |
48 | return $this;
49 | }
50 |
51 | /**
52 | * {@inheritdoc}
53 | */
54 | public function match(AddressInterface $address)
55 | {
56 | return $this->zone->match($address);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Model/ZoneMemberTest.php:
--------------------------------------------------------------------------------
1 | zoneMember = $this->getMockForAbstractClass('\CommerceGuys\Zone\Model\ZoneMember');
21 | }
22 |
23 | /**
24 | * @covers ::getId
25 | * @covers ::setId
26 | */
27 | public function testId()
28 | {
29 | $this->zoneMember->setId('fr_tax');
30 | $this->assertEquals('fr_tax', $this->zoneMember->getId());
31 | }
32 |
33 | /**
34 | * @covers ::getName
35 | * @covers ::setName
36 | */
37 | public function testName()
38 | {
39 | $this->zoneMember->setName('France');
40 | $this->assertEquals('France', $this->zoneMember->getName());
41 | }
42 |
43 | /**
44 | * @covers ::getParentZone
45 | * @covers ::setParentZone
46 | */
47 | public function testParentZone()
48 | {
49 | $zone = $this
50 | ->getMockBuilder('CommerceGuys\Zone\Model\Zone')
51 | ->disableOriginalConstructor()
52 | ->getMock();
53 |
54 | $this->zoneMember->setParentZone($zone);
55 | $this->assertEquals($zone, $this->zoneMember->getParentZone());
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Model/ZoneInterface.php:
--------------------------------------------------------------------------------
1 | id;
39 | }
40 |
41 | /**
42 | * {@inheritdoc}
43 | */
44 | public function setId($id)
45 | {
46 | $this->id = $id;
47 |
48 | return $this;
49 | }
50 |
51 | /**
52 | * {@inheritdoc}
53 | */
54 | public function getName()
55 | {
56 | return $this->name;
57 | }
58 |
59 | /**
60 | * {@inheritdoc}
61 | */
62 | public function setName($name)
63 | {
64 | $this->name = $name;
65 |
66 | return $this;
67 | }
68 |
69 | /**
70 | * {@inheritdoc}
71 | */
72 | public function getParentZone()
73 | {
74 | return $this->parentZone;
75 | }
76 |
77 | /**
78 | * {@inheritdoc}
79 | */
80 | public function setParentZone(ZoneEntityInterface $parentZone = null)
81 | {
82 | $this->parentZone = $parentZone;
83 |
84 | return $this;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/tests/Model/ZoneMemberEuTest.php:
--------------------------------------------------------------------------------
1 | zoneMember = new ZoneMemberEu();
23 | }
24 |
25 | /**
26 | * @covers ::__construct
27 | *
28 | * @uses \CommerceGuys\Zone\Model\ZoneMemberEu::getName
29 | */
30 | public function testConstructor()
31 | {
32 | $this->assertEquals('EU', $this->zoneMember->getName());
33 | }
34 |
35 | /**
36 | * @covers ::match
37 | *
38 | * @uses \CommerceGuys\Zone\Model\ZoneMemberEu::__construct
39 | */
40 | public function testMatch()
41 | {
42 | $mockBuilder = $this
43 | ->getMockBuilder('CommerceGuys\Addressing\Address')
44 | ->disableOriginalConstructor();
45 |
46 | $frenchAddress = $mockBuilder->getMock();
47 | $frenchAddress
48 | ->expects($this->any())
49 | ->method('getCountryCode')
50 | ->will($this->returnValue('FR'));
51 | $serbianAddress = $mockBuilder->getMock();
52 | $serbianAddress
53 | ->expects($this->any())
54 | ->method('getCountryCode')
55 | ->will($this->returnValue('RS'));
56 |
57 | $this->assertEquals(true, $this->zoneMember->match($frenchAddress));
58 | $this->assertEquals(false, $this->zoneMember->match($serbianAddress));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Matcher/ZoneMatcher.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
25 | }
26 |
27 | /**
28 | * {@inheritdoc}
29 | */
30 | public function match(AddressInterface $address, $scope = null)
31 | {
32 | $zones = $this->matchAll($address, $scope);
33 |
34 | return count($zones) ? $zones[0] : null;
35 | }
36 |
37 | /**
38 | * {@inheritdoc}
39 | */
40 | public function matchAll(AddressInterface $address, $scope = null)
41 | {
42 | // Find all matching zones.
43 | $results = [];
44 | foreach ($this->repository->getAll($scope) as $zone) {
45 | if ($zone->match($address)) {
46 | $results[] = [
47 | 'priority' => (int) $zone->getPriority(),
48 | 'zone' => $zone,
49 | ];
50 | }
51 | }
52 | // Sort the matched zones by priority.
53 | usort($results, function ($a, $b) {
54 | if ($a['priority'] == $b['priority']) {
55 | return 0;
56 | }
57 |
58 | return ($a['priority'] > $b['priority']) ? -1 : 1;
59 | });
60 | // Create the final zone array from the results.
61 | $zones = [];
62 | foreach ($results as $result) {
63 | $zones[] = $result['zone'];
64 | }
65 |
66 | return $zones;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Model/ZoneEntityInterface.php:
--------------------------------------------------------------------------------
1 | zoneMember = new ZoneMemberZone();
23 | }
24 |
25 | /**
26 | * @covers ::getName
27 | *
28 | * @uses \CommerceGuys\Zone\Model\ZoneMemberZone::setZone
29 | */
30 | public function testName()
31 | {
32 | $zone = $this
33 | ->getMockBuilder('CommerceGuys\Zone\Model\Zone')
34 | ->disableOriginalConstructor()
35 | ->getMock();
36 | $zone
37 | ->expects($this->any())
38 | ->method('getName')
39 | ->will($this->returnValue('Test'));
40 |
41 | $this->zoneMember->setZone($zone);
42 | $this->assertEquals('Test', $this->zoneMember->getName());
43 | }
44 |
45 | /**
46 | * @covers ::getZone
47 | * @covers ::setZone
48 | */
49 | public function testZone()
50 | {
51 | $zone = $this
52 | ->getMockBuilder('CommerceGuys\Zone\Model\Zone')
53 | ->disableOriginalConstructor()
54 | ->getMock();
55 |
56 | $this->zoneMember->setZone($zone);
57 | $this->assertEquals($zone, $this->zoneMember->getZone());
58 | }
59 |
60 | /**
61 | * @covers ::match
62 | *
63 | * @uses \CommerceGuys\Zone\Model\ZoneMemberZone::setZone
64 | */
65 | public function testMatch()
66 | {
67 | $address = $this
68 | ->getMockBuilder('CommerceGuys\Addressing\Address')
69 | ->disableOriginalConstructor()
70 | ->getMock();
71 | $matchingZone = $this
72 | ->getMockBuilder('CommerceGuys\Zone\Model\Zone')
73 | ->disableOriginalConstructor()
74 | ->getMock();
75 | $matchingZone
76 | ->expects($this->any())
77 | ->method('match')
78 | ->with($address)
79 | ->will($this->returnValue(true));
80 | $nonMatchingZone = $this
81 | ->getMockBuilder('CommerceGuys\Zone\Model\Zone')
82 | ->disableOriginalConstructor()
83 | ->getMock();
84 | $nonMatchingZone
85 | ->expects($this->any())
86 | ->method('match')
87 | ->with($address)
88 | ->will($this->returnValue(false));
89 |
90 | $this->zoneMember->setZone($matchingZone);
91 | $this->assertEquals(true, $this->zoneMember->match($address));
92 |
93 | $this->zoneMember->setZone($nonMatchingZone);
94 | $this->assertEquals(false, $this->zoneMember->match($address));
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | zone
2 | =====
3 |
4 | *Important:* A newer version of the zone functionality is now included directly in the [commerceguys/addressing](https://github.com/commerceguys/addressing) library. This library is deprecated.
5 |
6 | [](https://travis-ci.org/commerceguys/zone)
7 |
8 | A PHP 5.5+ zone management library. Requires [commerceguys/addressing](https://github.com/commerceguys/addressing).
9 |
10 | Zones are territorial groupings mostly used for shipping or tax purposes.
11 | For example, a set of shipping rates associated with a zone where the rates
12 | become available only if the customer's address matches the zone.
13 |
14 | A zone can match other zones, countries, subdivisions (states/provinces/municipalities), postal codes.
15 | Postal codes can also be expressed using ranges or regular expressions.
16 |
17 | Examples of zones:
18 | - California and Nevada
19 | - Belgium, Netherlands, Luxemburg
20 | - European Union
21 | - Germany and a set of Austrian postal codes (6691, 6991, 6992, 6993)
22 | - Austria without specific postal codes (6691, 6991, 6992, 6993)
23 |
24 | # Data model
25 |
26 | Each [zone](https://github.com/commerceguys/zone/blob/master/src/Model/ZoneInterface.php) has [zone members](https://github.com/commerceguys/zone/blob/master/src/Model/ZoneMemberInterface.php).
27 | A zone matches the provided address if one of its zone members matches the provided address.
28 |
29 | The base interfaces don't impose setters, since they aren't needed by the service classes.
30 | Extended interfaces ([ZoneEntityInterface](https://github.com/commerceguys/zone/blob/master/src/Model/ZoneEntityInterface.php), [ZoneMemberEntityInterface](https://github.com/commerceguys/zone/blob/master/src/Model/ZoneMemberEntityInterface.php)) are provided for that purpose,
31 | as well as matching [Zone](https://github.com/commerceguys/zone/blob/master/src/Model/Zone.php) and [ZoneMember](https://github.com/commerceguys/zone/blob/master/src/Model/ZoneMember.php) classes that can be used as examples or mapped by Doctrine.
32 |
33 | The library contains two types of zone members:
34 | - [country](https://github.com/commerceguys/zone/blob/master/src/Model/ZoneMemberCountry.php) (matches a country, its subdivisions, included/excluded postal codes)
35 | - [zone](https://github.com/commerceguys/zone/blob/master/src/Model/ZoneMemberZone.php) (matches a zone)
36 |
37 | ```php
38 | use CommerceGuys\Addressing\Address;
39 | use CommerceGuys\Zone\Model\Zone;
40 | use CommerceGuys\Zone\Model\ZoneMemberCountry;
41 |
42 | $zone = new Zone();
43 | $zone->setId('german_vat');
44 | $zone->setName('German VAT');
45 | $zone->setScope('tax');
46 |
47 | // Create the German VAT zone (Germany and 4 Austrian postal codes).
48 | $germanyZoneMember = new ZoneMemberCountry();
49 | $germanyZoneMember->setCountryCode('DE');
50 | $zone->addMember($germanyZoneMember);
51 |
52 | $austriaZoneMember = new ZoneMemberCountry();
53 | $austriaZoneMember->setCountryCode('AT');
54 | $austriaZoneMember->setIncludedPostalCodes('6691, 6991:6993');
55 | $zone->addMember($austriaZoneMember);
56 |
57 | // Check if the provided austrian address matches the German VAT zone.
58 | $austrianAddress = new Address();
59 | $austrianAddress = $austrianAddress
60 | ->withCountryCode('AT')
61 | ->withPostalCode('6692');
62 | echo $zone->match($austrianAddress); // true
63 | ```
64 |
65 | # Matcher
66 |
67 | A matcher class is provided for the use case where an address should be matched
68 | against all zones in the system, with the matched zones ordered by priority.
69 |
70 | ```php
71 | use CommerceGuys\Addressing\Address;
72 | use CommerceGuys\Zone\Matcher\ZoneMatcher;
73 | use CommerceGuys\Zone\Repository\ZoneRepository;
74 |
75 | // Initialize the default repository which loads zones from json files stored in
76 | // resources/zone. A different repository might load them from the database, etc.
77 | $repository = new ZoneRepository('resources/zone');
78 | $matcher = new ZoneMatcher($repository);
79 |
80 | $austrianAddress = new Address();
81 | $austrianAddress = $austrianAddress
82 | ->withCountryCode('AT')
83 | ->withPostalCode('6692');
84 |
85 | // Get all matching zones.
86 | $zones = $matcher->matchAll($austrianAddress);
87 | // Get all matching zones for the 'tax' scope.
88 | $zones = $matcher->matchAll($austrianAddress, 'tax');
89 |
90 | // Get the best matching zone.
91 | $zone = $matcher->match($austrianAddress);
92 | // Get the best matching zone for the 'shipping' scope.
93 | $zone = $matcher->match($austrianAddress, 'shipping');
94 | ```
95 |
--------------------------------------------------------------------------------
/src/Model/Zone.php:
--------------------------------------------------------------------------------
1 | members = new ArrayCollection();
57 | }
58 |
59 | /**
60 | * Returns the string representation of the zone.
61 | *
62 | * @return string
63 | */
64 | public function __toString()
65 | {
66 | return $this->name;
67 | }
68 |
69 | /**
70 | * {@inheritdoc}
71 | */
72 | public function getId()
73 | {
74 | return $this->id;
75 | }
76 |
77 | /**
78 | * {@inheritdoc}
79 | */
80 | public function setId($id)
81 | {
82 | $this->id = $id;
83 |
84 | return $this;
85 | }
86 |
87 | /**
88 | * {@inheritdoc}
89 | */
90 | public function getName()
91 | {
92 | return $this->name;
93 | }
94 |
95 | /**
96 | * {@inheritdoc}
97 | */
98 | public function setName($name)
99 | {
100 | $this->name = $name;
101 |
102 | return $this;
103 | }
104 |
105 | /**
106 | * {@inheritdoc}
107 | */
108 | public function getScope()
109 | {
110 | return $this->scope;
111 | }
112 |
113 | /**
114 | * {@inheritdoc}
115 | */
116 | public function setScope($scope)
117 | {
118 | $this->scope = $scope;
119 |
120 | return $this;
121 | }
122 |
123 | /**
124 | * {@inheritdoc}
125 | */
126 | public function getPriority()
127 | {
128 | return $this->priority;
129 | }
130 |
131 | /**
132 | * {@inheritdoc}
133 | */
134 | public function setPriority($priority)
135 | {
136 | $this->priority = $priority;
137 |
138 | return $this;
139 | }
140 |
141 | /**
142 | * {@inheritdoc}
143 | */
144 | public function getMembers()
145 | {
146 | return $this->members;
147 | }
148 |
149 | /**
150 | * {@inheritdoc}
151 | */
152 | public function setMembers(Collection $members)
153 | {
154 | $this->members = $members;
155 |
156 | return $this;
157 | }
158 |
159 | /**
160 | * {@inheritdoc}
161 | */
162 | public function hasMembers()
163 | {
164 | return !$this->members->isEmpty();
165 | }
166 |
167 | /**
168 | * {@inheritdoc}
169 | */
170 | public function addMember(ZoneMemberEntityInterface $member)
171 | {
172 | if (!$this->hasMember($member)) {
173 | $member->setParentZone($this);
174 | $this->members->add($member);
175 | }
176 |
177 | return $this;
178 | }
179 |
180 | /**
181 | * {@inheritdoc}
182 | */
183 | public function removeMember(ZoneMemberEntityInterface $member)
184 | {
185 | if ($this->hasMember($member)) {
186 | $member->setParentZone(null);
187 | $this->members->removeElement($member);
188 | }
189 |
190 | return $this;
191 | }
192 |
193 | /**
194 | * {@inheritdoc}
195 | */
196 | public function hasMember(ZoneMemberEntityInterface $member)
197 | {
198 | return $this->members->contains($member);
199 | }
200 |
201 | /**
202 | * {@inheritdoc}
203 | */
204 | public function match(AddressInterface $address)
205 | {
206 | foreach ($this->members as $member) {
207 | if ($member->match($address)) {
208 | return true;
209 | }
210 | }
211 |
212 | return false;
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/tests/Model/ZoneTest.php:
--------------------------------------------------------------------------------
1 | zone = new Zone();
24 | }
25 |
26 | /**
27 | * @covers ::getId
28 | * @covers ::setId
29 | *
30 | * @uses \CommerceGuys\Zone\Model\Zone::__construct
31 | */
32 | public function testId()
33 | {
34 | $this->zone->setId('north_america');
35 | $this->assertEquals('north_america', $this->zone->getId());
36 | }
37 |
38 | /**
39 | * @covers ::getName
40 | * @covers ::setName
41 | * @covers ::__toString
42 | *
43 | * @uses \CommerceGuys\Zone\Model\Zone::__construct
44 | */
45 | public function testName()
46 | {
47 | $this->zone->setName('North America');
48 | $this->assertEquals('North America', $this->zone->getName());
49 | $this->assertEquals('North America', (string) $this->zone);
50 | }
51 |
52 | /**
53 | * @covers ::getScope
54 | * @covers ::setScope
55 | *
56 | * @uses \CommerceGuys\Zone\Model\Zone::__construct
57 | */
58 | public function testScope()
59 | {
60 | $this->zone->setScope('shipping');
61 | $this->assertEquals('shipping', $this->zone->getScope());
62 | }
63 |
64 | /**
65 | * @covers ::getPriority
66 | * @covers ::setPriority
67 | *
68 | * @uses \CommerceGuys\Zone\Model\Zone::__construct
69 | */
70 | public function testPriority()
71 | {
72 | $this->zone->setPriority(10);
73 | $this->assertEquals(10, $this->zone->getPriority());
74 | }
75 |
76 | /**
77 | * @covers ::__construct
78 | * @covers ::getMembers
79 | * @covers ::setMembers
80 | * @covers ::hasMembers
81 | * @covers ::addMember
82 | * @covers ::removeMember
83 | * @covers ::hasMember
84 | *
85 | * @uses \CommerceGuys\Zone\Model\Zone::__construct
86 | * @uses \CommerceGuys\Zone\Model\ZoneMember::setParentZone
87 | */
88 | public function testMembers()
89 | {
90 | $firstZoneMember = $this
91 | ->getMockBuilder('CommerceGuys\Zone\Model\ZoneMember')
92 | ->getMock();
93 | $secondZoneMember = $this
94 | ->getMockBuilder('CommerceGuys\Zone\Model\ZoneMember')
95 | ->getMock();
96 | $empty = new ArrayCollection();
97 | $members = new ArrayCollection([$firstZoneMember, $secondZoneMember]);
98 |
99 | $this->assertEquals(false, $this->zone->hasMembers());
100 | $this->assertEquals($empty, $this->zone->getMembers());
101 | $members = new ArrayCollection([$firstZoneMember, $secondZoneMember]);
102 | $this->zone->setMembers($members);
103 | $this->assertEquals($members, $this->zone->getMembers());
104 | $this->assertEquals(true, $this->zone->hasMembers());
105 | $this->zone->removeMember($secondZoneMember);
106 | $this->assertEquals(false, $this->zone->hasMember($secondZoneMember));
107 | $this->assertEquals(true, $this->zone->hasMember($firstZoneMember));
108 | $this->zone->addMember($secondZoneMember);
109 | $this->assertEquals($members, $this->zone->getMembers());
110 | }
111 |
112 | /**
113 | * @covers ::match
114 | *
115 | * @uses \CommerceGuys\Zone\Model\Zone::__construct
116 | * @uses \CommerceGuys\Zone\Model\Zone::setMembers
117 | */
118 | public function testMatch()
119 | {
120 | $address = $this
121 | ->getMockBuilder('CommerceGuys\Addressing\Address')
122 | ->getMock();
123 | $matchingZoneMember = $this
124 | ->getMockBuilder('CommerceGuys\Zone\Model\ZoneMember')
125 | ->getMock();
126 | $matchingZoneMember
127 | ->expects($this->any())
128 | ->method('match')
129 | ->with($address)
130 | ->will($this->returnValue(true));
131 | $nonMatchingZoneMember = $this
132 | ->getMockBuilder('CommerceGuys\Zone\Model\ZoneMember')
133 | ->getMock();
134 | $nonMatchingZoneMember
135 | ->expects($this->any())
136 | ->method('match')
137 | ->with($address)
138 | ->will($this->returnValue(false));
139 |
140 | $members = new ArrayCollection([$matchingZoneMember, $nonMatchingZoneMember]);
141 | $this->zone->setMembers($members);
142 | $this->assertEquals(true, $this->zone->match($address));
143 |
144 | $members = new ArrayCollection([$nonMatchingZoneMember]);
145 | $this->zone->setMembers($members);
146 | $this->assertEquals(false, $this->zone->match($address));
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/tests/Matcher/ZoneMatcherTest.php:
--------------------------------------------------------------------------------
1 | [
22 | 'id' => 'fr',
23 | 'priority' => 0,
24 | 'match' => false,
25 | ],
26 | 'de' => [
27 | 'id' => 'de',
28 | 'priority' => 0,
29 | 'match' => true,
30 | ],
31 | 'de2' => [
32 | 'id' => 'de2',
33 | 'priority' => 0,
34 | 'match' => true,
35 | ],
36 | 'de3' => [
37 | 'id' => 'de3',
38 | 'priority' => 2,
39 | 'match' => true,
40 | ],
41 | ];
42 |
43 | /**
44 | * The zone matcher.
45 | *
46 | * @var ZoneMatcher
47 | */
48 | protected $matcher;
49 |
50 | /**
51 | * {@inheritdoc}
52 | */
53 | public function setUp()
54 | {
55 | $zones = [];
56 | foreach ($this->zones as $definition) {
57 | $zones[] = $this->getZone($definition['id'], $definition['priority'], $definition['match']);
58 | }
59 | $repository = $this
60 | ->getMockBuilder('CommerceGuys\Zone\Repository\ZoneRepository')
61 | ->disableOriginalConstructor()
62 | ->getMock();
63 | $repository
64 | ->expects($this->any())
65 | ->method('getAll')
66 | ->will($this->returnValue($zones));
67 | $this->matcher = new ZoneMatcher($repository);
68 | }
69 |
70 | /**
71 | * @covers ::__construct
72 | */
73 | public function testConstructor()
74 | {
75 | // Note: other tests use $this->matcher instead of depending on
76 | // testConstructor because of a phpunit bug with dependencies and mocks:
77 | // https://github.com/sebastianbergmann/phpunit-mock-objects/issues/127
78 | $repository = $this
79 | ->getMockBuilder('CommerceGuys\Zone\Repository\ZoneRepository')
80 | ->disableOriginalConstructor()
81 | ->getMock();
82 | $matcher = new ZoneMatcher($repository);
83 | // Confirm that the repository was properly set.
84 | $this->assertSame($repository, $this->getObjectAttribute($matcher, 'repository'));
85 | }
86 |
87 | /**
88 | * @covers ::match
89 | *
90 | * @uses \CommerceGuys\Zone\Matcher\ZoneMatcher::__construct
91 | * @uses \CommerceGuys\Zone\Matcher\ZoneMatcher::matchAll
92 | */
93 | public function testMatch()
94 | {
95 | $address = $this
96 | ->getMockBuilder('CommerceGuys\Addressing\Address')
97 | ->disableOriginalConstructor()
98 | ->getMock();
99 | $zone = $this->matcher->match($address);
100 | $this->assertInstanceOf('CommerceGuys\Zone\Model\Zone', $zone);
101 | $this->assertEquals('de3', $zone->getId());
102 | }
103 |
104 | /**
105 | * @covers ::matchAll
106 | *
107 | * @uses \CommerceGuys\Zone\Matcher\ZoneMatcher::__construct
108 | */
109 | public function testMatchAll()
110 | {
111 | $address = $this
112 | ->getMockBuilder('CommerceGuys\Addressing\Address')
113 | ->disableOriginalConstructor()
114 | ->getMock();
115 | $zones = $this->matcher->matchAll($address);
116 | $this->assertCount(3, $zones);
117 | // de3 must come first because it has the highest priority.
118 | $this->assertEquals('de3', $zones[0]->getId());
119 | // The other two zones have the same priority, so their order is
120 | // undefined and different between PHP and HHVM.
121 | $otherIds = [];
122 | $otherIds[] = $zones[1]->getId();
123 | $otherIds[] = $zones[2]->getId();
124 | $this->assertContains('de2', $otherIds);
125 | $this->assertContains('de', $otherIds);
126 | }
127 |
128 | /**
129 | * Returns a mock zone based on the provided data.
130 | *
131 | * @param string $id The zone id.
132 | * @param int $priority The zone priority.
133 | * @param bool $match Whether the zone should match.
134 | *
135 | * @return \CommerceGuys\Zone\Model\Zone
136 | */
137 | protected function getZone($id, $priority, $match)
138 | {
139 | $zone = $this
140 | ->getMockBuilder('CommerceGuys\Zone\Model\Zone')
141 | ->disableOriginalConstructor()
142 | ->getMock();
143 | $zone
144 | ->expects($this->any())
145 | ->method('getId')
146 | ->will($this->returnValue($id));
147 | $zone
148 | ->expects($this->any())
149 | ->method('getPriority')
150 | ->will($this->returnValue($priority));
151 | $zone
152 | ->expects($this->any())
153 | ->method('match')
154 | ->with($this->anything())
155 | ->will($this->returnValue($match));
156 |
157 | return $zone;
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/Model/ZoneMemberCountry.php:
--------------------------------------------------------------------------------
1 | countryCode;
69 | }
70 |
71 | /**
72 | * Sets the country code.
73 | *
74 | * @param string $countryCode The country code.
75 | *
76 | * @return self
77 | */
78 | public function setCountryCode($countryCode)
79 | {
80 | $this->countryCode = $countryCode;
81 |
82 | return $this;
83 | }
84 |
85 | /**
86 | * Gets the administrative area.
87 | *
88 | * @return string|null The administrative area, or null if all should match.
89 | */
90 | public function getAdministrativeArea()
91 | {
92 | return $this->administrativeArea;
93 | }
94 |
95 | /**
96 | * Sets the administrative area.
97 | *
98 | * @param string|null $administrativeArea The administrative area.
99 | *
100 | * @return self
101 | */
102 | public function setAdministrativeArea($administrativeArea = null)
103 | {
104 | $this->administrativeArea = $administrativeArea;
105 |
106 | return $this;
107 | }
108 |
109 | /**
110 | * Gets the locality.
111 | *
112 | * @return string|null The locality, or null if all should match.
113 | */
114 | public function getLocality()
115 | {
116 | return $this->locality;
117 | }
118 |
119 | /**
120 | * Sets the locality.
121 | *
122 | * @param string|null $locality The locality.
123 | *
124 | * @return self
125 | */
126 | public function setLocality($locality = null)
127 | {
128 | $this->locality = $locality;
129 |
130 | return $this;
131 | }
132 |
133 | /**
134 | * Gets the dependent locality.
135 | *
136 | * @return string|null The dependent locality, or null if all should match.
137 | */
138 | public function getDependentLocality()
139 | {
140 | return $this->dependentLocality;
141 | }
142 |
143 | /**
144 | * Sets the dependent locality.
145 | *
146 | * @param string|null $dependentLocality The dependent locality.
147 | *
148 | * @return self
149 | */
150 | public function setDependentLocality($dependentLocality = null)
151 | {
152 | $this->dependentLocality = $dependentLocality;
153 |
154 | return $this;
155 | }
156 |
157 | /**
158 | * Gets the included postal codes.
159 | *
160 | * @return string The included postal codes.
161 | */
162 | public function getIncludedPostalCodes()
163 | {
164 | return $this->includedPostalCodes;
165 | }
166 |
167 | /**
168 | * Sets the included postal codes.
169 | *
170 | * @param string $includedPostalCodes The included postal codes.
171 | *
172 | * @return self
173 | */
174 | public function setIncludedPostalCodes($includedPostalCodes)
175 | {
176 | $this->includedPostalCodes = $includedPostalCodes;
177 |
178 | return $this;
179 | }
180 |
181 | /**
182 | * Gets the excluded postal codes.
183 | *
184 | * @return string The excluded postal codes.
185 | */
186 | public function getExcludedPostalCodes()
187 | {
188 | return $this->excludedPostalCodes;
189 | }
190 |
191 | /**
192 | * Sets the excluded postal codes.
193 | *
194 | * @param string $excludedPostalCodes The excluded postal codes.
195 | *
196 | * @return self
197 | */
198 | public function setExcludedPostalCodes($excludedPostalCodes)
199 | {
200 | $this->excludedPostalCodes = $excludedPostalCodes;
201 |
202 | return $this;
203 | }
204 |
205 | /**
206 | * {@inheritdoc}
207 | */
208 | public function match(AddressInterface $address)
209 | {
210 | if ($address->getCountryCode() != $this->countryCode) {
211 | return false;
212 | }
213 | if ($this->administrativeArea && $this->administrativeArea != $address->getAdministrativeArea()) {
214 | return false;
215 | }
216 | if ($this->locality && $this->locality != $address->getLocality()) {
217 | return false;
218 | }
219 | if ($this->dependentLocality && $this->dependentLocality != $address->getDependentLocality()) {
220 | return false;
221 | }
222 | if (!PostalCodeHelper::match($address->getPostalCode(), $this->includedPostalCodes, $this->excludedPostalCodes)) {
223 | return false;
224 | }
225 |
226 | return true;
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/src/Repository/ZoneRepository.php:
--------------------------------------------------------------------------------
1 | definitionPath = $definitionPath;
44 | }
45 |
46 | /**
47 | * {@inheritdoc}
48 | */
49 | public function get($id)
50 | {
51 | if (!isset($this->zones[$id])) {
52 | $definition = $this->loadDefinition($id);
53 | $this->zones[$id] = $this->createZoneFromDefinition($definition);
54 | }
55 |
56 | return $this->zones[$id];
57 | }
58 |
59 | /**
60 | * {@inheritdoc}
61 | */
62 | public function getAll($scope = null)
63 | {
64 | // Build the list of all available zones.
65 | if (empty($this->zoneIndex)) {
66 | if ($handle = opendir($this->definitionPath)) {
67 | while (false !== ($entry = readdir($handle))) {
68 | if (substr($entry, 0, 1) != '.') {
69 | $id = strtok($entry, '.');
70 | $this->zoneIndex[] = $id;
71 | }
72 | }
73 | closedir($handle);
74 | }
75 | }
76 |
77 | // Load each zone, filter by scope if needed.
78 | $zones = [];
79 | foreach ($this->zoneIndex as $id) {
80 | $zone = $this->get($id);
81 | if (is_null($scope) || ($zone->getScope() == $scope)) {
82 | $zones[$id] = $this->get($id);
83 | }
84 | }
85 |
86 | return $zones;
87 | }
88 |
89 | /**
90 | * Loads the zone definition for the provided id.
91 | *
92 | * @param string $id The zone id.
93 | *
94 | * @return array The zone definition.
95 | */
96 | protected function loadDefinition($id)
97 | {
98 | $filename = $this->definitionPath . $id . '.json';
99 | $definition = @file_get_contents($filename);
100 | if (empty($definition)) {
101 | throw new UnknownZoneException($id);
102 | }
103 | $definition = json_decode($definition, true);
104 | $definition['id'] = $id;
105 |
106 | return $definition;
107 | }
108 |
109 | /**
110 | * Creates a Zone instance from the provided definition.
111 | *
112 | * @param array $definition The zone definition.
113 | *
114 | * @return Zone
115 | */
116 | protected function createZoneFromDefinition(array $definition)
117 | {
118 | $zone = new Zone();
119 | // Bind the closure to the Zone object, giving it access to
120 | // its protected properties. Faster than both setters and reflection.
121 | $setValues = \Closure::bind(function ($definition) {
122 | $this->id = $definition['id'];
123 | $this->name = $definition['name'];
124 | if (isset($definition['scope'])) {
125 | $this->scope = $definition['scope'];
126 | }
127 | if (isset($definition['priority'])) {
128 | $this->priority = $definition['priority'];
129 | }
130 | }, $zone, '\CommerceGuys\Zone\Model\Zone');
131 | $setValues($definition);
132 |
133 | // Add the zone members.
134 | foreach ($definition['members'] as $memberDefinition) {
135 | if ($memberDefinition['type'] == 'country') {
136 | $zoneMember = $this->createZoneMemberCountryFromDefinition($memberDefinition);
137 | $zone->addMember($zoneMember);
138 | } elseif ($memberDefinition['type'] == 'zone') {
139 | $zoneMember = $this->createZoneMemberZoneFromDefinition($memberDefinition);
140 | $zone->addMember($zoneMember);
141 | }
142 | }
143 |
144 | return $zone;
145 | }
146 |
147 | /**
148 | * Creates a ZoneMemberCountry instance from the provided definition.
149 | *
150 | * @param array $definition The zone member definition.
151 | *
152 | * @return ZoneMemberCountry
153 | */
154 | protected function createZoneMemberCountryFromDefinition(array $definition)
155 | {
156 | $zoneMember = new ZoneMemberCountry();
157 | $setValues = \Closure::bind(function ($definition) {
158 | $this->id = $definition['id'];
159 | $this->name = $definition['name'];
160 | $this->countryCode = $definition['country_code'];
161 | if (isset($definition['administrative_area'])) {
162 | $this->administrativeArea = $definition['administrative_area'];
163 | }
164 | if (isset($definition['locality'])) {
165 | $this->locality = $definition['locality'];
166 | }
167 | if (isset($definition['dependent_locality'])) {
168 | $this->dependentLocality = $definition['dependent_locality'];
169 | }
170 | if (isset($definition['included_postal_codes'])) {
171 | $this->includedPostalCodes = $definition['included_postal_codes'];
172 | }
173 | if (isset($definition['excluded_postal_codes'])) {
174 | $this->excludedPostalCodes = $definition['excluded_postal_codes'];
175 | }
176 | }, $zoneMember, '\CommerceGuys\Zone\Model\ZoneMemberCountry');
177 | $setValues($definition);
178 |
179 | return $zoneMember;
180 | }
181 |
182 | /**
183 | * Creates a ZoneMemberZone instance from the provided definition.
184 | *
185 | * @param array $definition The zone member definition.
186 | *
187 | * @return ZoneMemberZone
188 | */
189 | protected function createZoneMemberZoneFromDefinition(array $definition)
190 | {
191 | $zone = $this->get($definition['zone']);
192 | $zoneMember = new ZoneMemberZone();
193 | $zoneMember->setZone($zone);
194 | $setValues = \Closure::bind(function ($definition) {
195 | $this->id = $definition['id'];
196 | }, $zoneMember, '\CommerceGuys\Zone\Model\ZoneMemberZone');
197 | $setValues($definition);
198 |
199 | return $zoneMember;
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/tests/Repository/ZoneRepositoryTest.php:
--------------------------------------------------------------------------------
1 | [
22 | 'name' => 'Germany',
23 | 'scope' => 'shipping',
24 | 'members' => [
25 | [
26 | 'type' => 'country',
27 | 'id' => '1',
28 | 'name' => 'Germany',
29 | 'country_code' => 'DE',
30 | ],
31 | ],
32 | ],
33 | 'de_vat' => [
34 | 'name' => 'Germany',
35 | 'scope' => 'tax',
36 | 'priority' => 1,
37 | // A real zone wouldn't reference a zone of a different
38 | // scope (like here with de_vat -> de), but it decreases the
39 | // amount of data in this test.
40 | 'members' => [
41 | [
42 | 'type' => 'zone',
43 | 'id' => '2',
44 | 'zone' => 'de',
45 | ],
46 | [
47 | 'type' => 'country',
48 | 'id' => '3',
49 | 'name' => 'Austria',
50 | 'country_code' => 'AT',
51 | 'included_postal_codes' => '6691, 6991:6993',
52 | // Dummy data to ensure all fields get tested.
53 | 'administrative_area' => 'dummy',
54 | 'locality' => 'dummy',
55 | 'dependent_locality' => 'dummy',
56 | 'excluded_postal_codes' => '123456',
57 | ],
58 | ],
59 | ],
60 | ];
61 |
62 | /**
63 | * @covers ::__construct
64 | */
65 | public function testConstructor()
66 | {
67 | // Mock the existence of JSON definitions on the filesystem.
68 | $root = vfsStream::setup('resources');
69 | $directory = vfsStream::newDirectory('zone')->at($root);
70 | foreach ($this->zones as $id => $definition) {
71 | $filename = $id . '.json';
72 | vfsStream::newFile($filename)->at($directory)->setContent(json_encode($definition));
73 | }
74 |
75 | // Instantiate the zone repository and confirm that the
76 | // definition path was properly set.
77 | $zoneRepository = new ZoneRepository('vfs://resources/zone/');
78 | $definitionPath = $this->getObjectAttribute($zoneRepository, 'definitionPath');
79 | $this->assertEquals('vfs://resources/zone/', $definitionPath);
80 |
81 | return $zoneRepository;
82 | }
83 |
84 | /**
85 | * @covers ::get
86 | * @covers ::loadDefinition
87 | * @covers ::createZoneFromDefinition
88 | * @covers ::createZoneMemberCountryFromDefinition
89 | * @covers ::createZoneMemberZoneFromDefinition
90 | *
91 | * @uses \CommerceGuys\Zone\Model\Zone
92 | * @uses \CommerceGuys\Zone\Model\ZoneMember
93 | * @uses \CommerceGuys\Zone\Model\ZoneMemberCountry
94 | * @uses \CommerceGuys\Zone\Model\ZoneMemberZone
95 | * @uses \CommerceGuys\Addressing\PostalCodeHelper
96 | * @depends testConstructor
97 | */
98 | public function testGet($zoneRepository)
99 | {
100 | $zone = $zoneRepository->get('de_vat');
101 | $this->assertInstanceOf('CommerceGuys\Zone\Model\Zone', $zone);
102 | $this->assertEquals('de_vat', $zone->getId());
103 | $this->assertEquals('Germany', $zone->getName());
104 | $this->assertEquals('tax', $zone->getScope());
105 | $this->assertEquals('1', $zone->getPriority());
106 | $members = $zone->getMembers();
107 | $this->assertCount(2, $members);
108 |
109 | $germanyMember = $members[0];
110 | $this->assertInstanceOf('CommerceGuys\Zone\Model\ZoneMemberZone', $germanyMember);
111 | $this->assertEquals('2', $germanyMember->getId());
112 | $this->assertEquals($zone, $germanyMember->getParentZone());
113 | $this->assertEquals($zoneRepository->get('de'), $germanyMember->getZone());
114 |
115 | $austriaMember = $members[1];
116 | $this->assertInstanceOf('CommerceGuys\Zone\Model\ZoneMemberCountry', $austriaMember);
117 | $this->assertEquals('3', $austriaMember->getId());
118 | $this->assertEquals('Austria', $austriaMember->getName());
119 | $this->assertEquals($zone, $austriaMember->getParentZone());
120 | $this->assertEquals('AT', $austriaMember->getCountryCode());
121 | $this->assertEquals('6691, 6991:6993', $austriaMember->getIncludedPostalCodes());
122 | $this->assertEquals('123456', $austriaMember->getExcludedPostalCodes());
123 | $this->assertEquals('dummy', $austriaMember->getAdministrativeArea());
124 | $this->assertEquals('dummy', $austriaMember->getLocality());
125 | $this->assertEquals('dummy', $austriaMember->getDependentLocality());
126 |
127 | // Test the static cache.
128 | $sameZone = $zoneRepository->get('de_vat');
129 | $this->assertSame($zone, $sameZone);
130 | }
131 |
132 | /**
133 | * @covers ::get
134 | * @covers ::loadDefinition
135 | * @covers ::createZoneFromDefinition
136 | * @expectedException \CommerceGuys\Zone\Exception\UnknownZoneException
137 | * @depends testConstructor
138 | */
139 | public function testGetNonExistingZone($zoneRepository)
140 | {
141 | $zone = $zoneRepository->get('rs');
142 | }
143 |
144 | /**
145 | * @covers ::getAll
146 | * @covers ::loadDefinition
147 | * @covers ::createZoneFromDefinition
148 | * @covers ::createZoneMemberCountryFromDefinition
149 | * @covers ::createZoneMemberZoneFromDefinition
150 | *
151 | * @uses \CommerceGuys\Zone\Repository\ZoneRepository::get
152 | * @uses \CommerceGuys\Zone\Model\Zone
153 | * @uses \CommerceGuys\Zone\Model\ZoneMember
154 | * @uses \CommerceGuys\Zone\Model\ZoneMemberCountry
155 | * @uses \CommerceGuys\Zone\Model\ZoneMemberZone
156 | * @uses \CommerceGuys\Addressing\PostalCodeHelper
157 | * @depends testConstructor
158 | */
159 | public function testGetAll($zoneRepository)
160 | {
161 | $zones = $zoneRepository->getAll();
162 | $this->assertCount(2, $zones);
163 | $this->assertArrayHasKey('de', $zones);
164 | $this->assertArrayHasKey('de_vat', $zones);
165 | $this->assertEquals($zones['de']->getId(), 'de');
166 | $this->assertEquals($zones['de_vat']->getId(), 'de_vat');
167 |
168 | $zones = $zoneRepository->getAll('tax');
169 | $this->assertCount(1, $zones);
170 | $this->assertArrayHasKey('de_vat', $zones);
171 | $this->assertEquals($zones['de_vat']->getId(), 'de_vat');
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/tests/Model/ZoneMemberCountryTest.php:
--------------------------------------------------------------------------------
1 | zoneMember = new ZoneMemberCountry();
23 | }
24 |
25 | /**
26 | * @covers ::getCountryCode
27 | * @covers ::setCountryCode
28 | */
29 | public function testCountryCode()
30 | {
31 | $this->zoneMember->setCountryCode('CN');
32 | $this->assertEquals('CN', $this->zoneMember->getCountryCode());
33 | }
34 |
35 | /**
36 | * @covers ::getAdministrativeArea
37 | * @covers ::setAdministrativeArea
38 | */
39 | public function testAdministrativeArea()
40 | {
41 | $administrativeArea = $this
42 | ->getMockBuilder('CommerceGuys\Addressing\Subdivision\Subdivision')
43 | ->disableOriginalConstructor()
44 | ->getMock();
45 |
46 | $this->zoneMember->setAdministrativeArea($administrativeArea);
47 | $this->assertSame($administrativeArea, $this->zoneMember->getAdministrativeArea());
48 | }
49 |
50 | /**
51 | * @covers ::getLocality
52 | * @covers ::setLocality
53 | */
54 | public function testLocality()
55 | {
56 | $locality = $this
57 | ->getMockBuilder('CommerceGuys\Addressing\Subdivision\Subdivision')
58 | ->disableOriginalConstructor()
59 | ->getMock();
60 |
61 | $this->zoneMember->setLocality($locality);
62 | $this->assertSame($locality, $this->zoneMember->getLocality());
63 | }
64 |
65 | /**
66 | * @covers ::getDependentLocality
67 | * @covers ::setDependentLocality
68 | */
69 | public function testDependentLocality()
70 | {
71 | $dependentLocality = $this
72 | ->getMockBuilder('CommerceGuys\Addressing\Subdivision\Subdivision')
73 | ->disableOriginalConstructor()
74 | ->getMock();
75 |
76 | $this->zoneMember->setDependentLocality($dependentLocality);
77 | $this->assertSame($dependentLocality, $this->zoneMember->getDependentLocality());
78 | }
79 |
80 | /**
81 | * @covers ::getIncludedPostalCodes
82 | * @covers ::setIncludedPostalCodes
83 | */
84 | public function testIncludedPostalCodes()
85 | {
86 | $this->zoneMember->setIncludedPostalCodes('123, 456, 789');
87 | $this->assertEquals('123, 456, 789', $this->zoneMember->getIncludedPostalCodes());
88 | }
89 |
90 | /**
91 | * @covers ::getExcludedPostalCodes
92 | * @covers ::setExcludedPostalCodes
93 | */
94 | public function testExcludedPostalCodes()
95 | {
96 | $this->zoneMember->setExcludedPostalCodes('123, 456, 789');
97 | $this->assertEquals('123, 456, 789', $this->zoneMember->getExcludedPostalCodes());
98 | }
99 |
100 | /**
101 | * @covers ::match
102 | *
103 | * @uses \CommerceGuys\Zone\Model\ZoneMemberCountry::setCountryCode
104 | * @uses \CommerceGuys\Zone\Model\ZoneMemberCountry::setAdministrativeArea
105 | * @uses \CommerceGuys\Zone\Model\ZoneMemberCountry::setLocality
106 | * @uses \CommerceGuys\Zone\Model\ZoneMemberCountry::setDependentLocality
107 | * @uses \CommerceGuys\Zone\Model\ZoneMemberCountry::setIncludedPostalCodes
108 | * @uses \CommerceGuys\Zone\Model\ZoneMemberCountry::setExcludedPostalCodes
109 | * @uses \CommerceGuys\Addressing\PostalCodeHelper
110 | * @dataProvider addressProvider
111 | */
112 | public function testMatch($address, $expectedResult)
113 | {
114 | $this->zoneMember->setCountryCode('CN');
115 | $this->zoneMember->setAdministrativeArea('Hebei Sheng');
116 | $this->zoneMember->setLocality('Handan Shi');
117 | $this->zoneMember->setDependentLocality('Ci Xian');
118 | $this->zoneMember->setIncludedPostalCodes('123456');
119 |
120 | $this->assertEquals($expectedResult, $this->zoneMember->match($address));
121 | }
122 |
123 | /**
124 | * Provides addresses and the expected match results.
125 | */
126 | public function addressProvider()
127 | {
128 | $emptyAddress = $this->getAddress();
129 | $countryAddress = $this->getAddress('CN');
130 | $administrativeAreaAddress = $this->getAddress('CN', 'Hebei Sheng');
131 | $localityAddress = $this->getAddress('CN', 'Hebei Sheng', 'Handan Shi');
132 | $dependentLocalityAddress = $this->getAddress('CN', 'Hebei Sheng', 'Handan Shi', 'Ci Xian');
133 | $fullAddress = $this->getAddress('CN', 'Hebei Sheng', 'Handan Shi', 'Ci Xian', '123456');
134 |
135 | return [
136 | [$emptyAddress, false],
137 | [$countryAddress, false],
138 | [$administrativeAreaAddress, false],
139 | [$localityAddress, false],
140 | [$dependentLocalityAddress, false],
141 | [$fullAddress, true],
142 | ];
143 | }
144 |
145 | /**
146 | * Returns a mock address.
147 | *
148 | * @param string $countryCode The country code.
149 | * @param string $administrativeArea The administrative area id.
150 | * @param string $locality The locality id.
151 | * @param string $dependentLocality The dependent locality id.
152 | * @param string $postalCode The postal code.
153 | *
154 | * @return \CommerceGuys\Addressing\Address
155 | */
156 | protected function getAddress(
157 | $countryCode = null,
158 | $administrativeArea = null,
159 | $locality = null,
160 | $dependentLocality = null,
161 | $postalCode = null
162 | ) {
163 | $address = $this
164 | ->getMockBuilder('CommerceGuys\Addressing\Address')
165 | ->disableOriginalConstructor()
166 | ->getMock();
167 | if ($countryCode) {
168 | $address
169 | ->expects($this->any())
170 | ->method('getCountryCode')
171 | ->will($this->returnValue($countryCode));
172 | }
173 | if ($administrativeArea) {
174 | $address
175 | ->expects($this->any())
176 | ->method('getAdministrativeArea')
177 | ->will($this->returnValue($administrativeArea));
178 | }
179 | if ($locality) {
180 | $address
181 | ->expects($this->any())
182 | ->method('getLocality')
183 | ->will($this->returnValue($locality));
184 | }
185 | if ($dependentLocality) {
186 | $address
187 | ->expects($this->any())
188 | ->method('getDependentLocality')
189 | ->will($this->returnValue($dependentLocality));
190 | }
191 | if ($postalCode) {
192 | $address
193 | ->expects($this->any())
194 | ->method('getPostalCode')
195 | ->will($this->returnValue($postalCode));
196 | }
197 |
198 | return $address;
199 | }
200 | }
201 |
--------------------------------------------------------------------------------