-
Notifications
You must be signed in to change notification settings - Fork 150
Expand file tree
/
Copy pathAddressFormat.php
More file actions
376 lines (337 loc) · 11.6 KB
/
AddressFormat.php
File metadata and controls
376 lines (337 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
<?php
namespace CommerceGuys\Addressing\AddressFormat;
/**
* Provides metadata for storing and presenting a country's addresses.
*/
class AddressFormat
{
protected string $countryCode;
protected ?string $locale = null;
protected string $format;
protected ?string $localFormat = null;
protected array $usedFields = [];
/**
* The used fields, grouped by line.
*/
protected array $groupedFields = [];
protected mixed $requiredFields = [];
/**
* The fields that need to be uppercased.
*/
protected array $uppercaseFields = [];
protected array $defaultValues = [];
protected ?string $administrativeAreaType = null;
protected ?string $localityType = null;
protected ?string $dependentLocalityType = null;
protected ?string $postalCodeType = null;
protected ?string $postalCodePattern = null;
protected ?string $postalCodePrefix = null;
/**
* The subdivision depth.
*
* @deprecated Use $subdivisionDataFields instead.
*/
protected int $subdivisionDepth;
/**
* The subdivision data fields.
*
* An array of field names for which there is predefined subdivision data.
* For example: ['administrativeArea', 'locality']
*/
protected array $subdivisionDataFields = [];
/**
* @throws \ReflectionException
*/
public function __construct(array $definition)
{
// Validate the presence of required properties.
foreach (['country_code', 'format'] as $requiredProperty) {
if (empty($definition[$requiredProperty])) {
throw new \InvalidArgumentException(sprintf('Missing required property %s.', $requiredProperty));
}
}
$this->countryCode = $definition['country_code'];
$this->locale = $definition['locale'] ?? null;
$this->format = $definition['format'];
$this->localFormat = $definition['local_format'] ?? null;
if (isset($definition['required_fields'])) {
AddressField::assertAllExist($definition['required_fields']);
$this->requiredFields = $definition['required_fields'];
}
if (isset($definition['default_values'])) {
AddressField::assertAllExist(array_keys($definition['default_values']));
$this->defaultValues = $definition['default_values'];
}
if (isset($definition['uppercase_fields'])) {
AddressField::assertAllExist($definition['uppercase_fields']);
$this->uppercaseFields = $definition['uppercase_fields'];
}
if (isset($definition['subdivision_data_fields'])) {
AddressField::assertAllExist($definition['subdivision_data_fields']);
$this->subdivisionDataFields = $definition['subdivision_data_fields'];
// Set subdivision depth based on the count of data fields present in the format.
$this->subdivisionDepth = count(array_intersect($this->subdivisionDataFields, $this->getUsedSubdivisionFields()));
} elseif (isset($definition['subdivision_depth'])) {
// Backwards compatibility: convert subdivision_depth to subdivision_data_fields.
$this->subdivisionDepth = $definition['subdivision_depth'];
$availableFields = [
AddressField::ADMINISTRATIVE_AREA,
AddressField::LOCALITY,
AddressField::DEPENDENT_LOCALITY,
];
$this->subdivisionDataFields = array_slice($availableFields, 0, $this->subdivisionDepth);
}
$usedFields = $this->getUsedFields();
if (in_array(AddressField::ADMINISTRATIVE_AREA, $usedFields)) {
if (isset($definition['administrative_area_type'])) {
AdministrativeAreaType::assertExists($definition['administrative_area_type']);
$this->administrativeAreaType = $definition['administrative_area_type'];
}
}
if (in_array(AddressField::LOCALITY, $usedFields)) {
if (isset($definition['locality_type'])) {
LocalityType::assertExists($definition['locality_type']);
$this->localityType = $definition['locality_type'];
}
}
if (in_array(AddressField::DEPENDENT_LOCALITY, $usedFields)) {
if (isset($definition['dependent_locality_type'])) {
DependentLocalityType::assertExists($definition['dependent_locality_type']);
$this->dependentLocalityType = $definition['dependent_locality_type'];
}
}
if (in_array(AddressField::POSTAL_CODE, $usedFields)) {
if (isset($definition['postal_code_type'])) {
PostalCodeType::assertExists($definition['postal_code_type']);
$this->postalCodeType = $definition['postal_code_type'];
}
$this->postalCodePattern = $definition['postal_code_pattern'] ?? null;
$this->postalCodePrefix = $definition['postal_code_prefix'] ?? null;
}
}
/**
* Gets the two-letter country code.
*
* This is a CLDR country code, since CLDR includes additional countries
* for addressing purposes, such as Canary Islands (IC).
*
* @return string The two-letter country code.
*/
public function getCountryCode(): string
{
return $this->countryCode;
}
/**
* Gets the locale.
*
* Only defined if the country has a local format.
*
* @return string|null The locale, if defined.
*/
public function getLocale(): ?string
{
return $this->locale;
}
/**
* Gets the format string.
*
* Defines the layout of an address, and consists of tokens (address fields
* prefixed with a '%') separated by unix newlines (\n).
* Example:
* <code>
* %givenName %familyName
* %organization
* %addressLine1
* %addressLine2
* %addressLine3
* %locality %administrativeArea %postalCode
* </code>
*
* @return string The format string.
*/
public function getFormat(): string
{
return $this->format;
}
/**
* Gets the local format string.
*
* Defined for countries that use a different ordering of fields when the
* address is entered in the native script. For example, China uses a
* major-to-minor format (country first, name last) when the address
* is entered in Chinese.
*
* @return string|null The local format string, if defined.
*/
public function getLocalFormat(): ?string
{
return $this->localFormat;
}
/**
* Gets the list of used fields.
*
* @return array An array of address fields.
* @throws \ReflectionException
*/
public function getUsedFields(): array
{
if (empty($this->usedFields)) {
$this->usedFields = [];
foreach (AddressField::getAll() as $field) {
if (str_contains($this->format, '%' . $field)) {
$this->usedFields[] = $field;
}
}
}
return $this->usedFields;
}
/**
* Gets the list of used subdivision fields.
*
* @throws \ReflectionException
*/
public function getUsedSubdivisionFields(): array
{
$fields = [
AddressField::ADMINISTRATIVE_AREA,
AddressField::LOCALITY,
AddressField::DEPENDENT_LOCALITY,
];
// Remove fields not used by the format, and reset the keys.
$fields = array_intersect($fields, $this->getUsedFields());
return array_values($fields);
}
/**
* Gets the subdivision data fields.
*
* Returns the list of subdivision fields for which there is predefined data.
* This is more precise than getUsedSubdivisionFields() which returns all
* subdivision fields used by the format, regardless of whether data exists.
*
* @return array An array of subdivision field names, such as:
* ['administrativeArea']
* ['administrativeArea', 'locality']
* ['administrativeArea', 'locality', 'dependentLocality']
*/
public function getSubdivisionDataFields(): array
{
return $this->subdivisionDataFields;
}
/**
* Gets the list of required fields.
*
* @return AddressField[]
*/
public function getRequiredFields(): array
{
return $this->requiredFields;
}
/**
* Gets the list of fields that need to be uppercased.
*
* @return AddressField[]
*/
public function getUppercaseFields(): array
{
return $this->uppercaseFields;
}
/**
* Gets the default values.
*
* @return array The default values, keyed by field name.
*/
public function getDefaultValues(): array
{
return $this->defaultValues;
}
/**
* Gets the administrative area type.
*
* Used for presenting the correct label to the end-user.
*
* @return string|null The administrative area type, or null if the
* administrative area field isn't used.
*/
public function getAdministrativeAreaType(): ?string
{
return $this->administrativeAreaType;
}
/**
* Gets the locality type.
*
* Used for presenting the correct label to the end-user.
*
* @return string|null The locality type, or null if the locality field
* isn't used.
*/
public function getLocalityType(): ?string
{
return $this->localityType;
}
/**
* Gets the dependent locality type.
*
* Used for presenting the correct label to the end-user.
*
* @return string|null The dependent locality type, or null if the
* dependent locality field isn't used.
*/
public function getDependentLocalityType(): ?string
{
return $this->dependentLocalityType;
}
/**
* Gets the postal code type.
*
* Used for presenting the correct label to the end-user.
*
* @return string|null The postal code type, or null if the postal code
* field isn't used.
*/
public function getPostalCodeType(): ?string
{
return $this->postalCodeType;
}
/**
* Gets the postal code pattern.
*
* This is a regular expression pattern used to validate postal codes.
* Ignored if a subdivision defines its own full postal code pattern
* (E.g. Hong Kong when specified as a Chinese province).
*
* @return string|null The postal code pattern.
*/
public function getPostalCodePattern(): ?string
{
return $this->postalCodePattern;
}
/**
* Gets the postal code prefix.
*
* The prefix is optional and added to postal codes only when formatting
* an address for international mailing, as recommended by postal services.
*
* @return string|null The postal code prefix.
*/
public function getPostalCodePrefix(): ?string
{
return $this->postalCodePrefix;
}
/**
* Gets the subdivision depth.
*
* Indicates the number of levels of predefined subdivisions.
*
* @deprecated Use getSubdivisionDataFields() instead.
*
* @return int The subdivision depth. Possible values:
* 0: no subdivisions have been predefined.
* 1: administrative areas.
* 2: administrative areas, localities.
* 3: administrative areas, localities, dependent localities.
*/
public function getSubdivisionDepth(): int
{
return $this->subdivisionDepth;
}
}