Passed
Branch 2 (94e713)
by Morven
09:00
created

GeoZonesHelper::getRegionArray()   B

Complexity

Conditions 10
Paths 9

Size

Total Lines 41
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 24
c 1
b 0
f 0
dl 0
loc 41
rs 7.6666
cc 10
nc 9
nop 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverCommerce\GeoZones\Helpers;
4
5
use LogicException;
6
use SilverStripe\i18n\i18n;
7
use SilverStripe\ORM\ArrayList;
8
use SilverStripe\View\ArrayData;
9
use SilverStripe\Core\Config\Configurable;
10
use SilverStripe\Core\Injector\Injectable;
11
12
/**
13
 * Some simple helper methods to interact with country and regions
14
 */
15
class GeoZonesHelper
16
{
17
    use Configurable, Injectable;
18
19
    /**
20
     * loaded via yml config
21
     * 
22
     * @var array
23
     */
24
    private static $iso_3166_regions = [];
25
26
    /**
27
     * List of countries that this helper will filter by
28
     *
29
     * @var array
30
     */
31
    private $countries_list = [];
32
33
    /**
34
     * ISO 3166 subdivision/region codes to limit this list to.
35
     *
36
     * @var array
37
     */
38
    private $limit_region_codes = [];
39
40
    /**
41
     * Cache of regions from previous calls (to save some memory)
42
     *
43
     * @var array
44
     */
45
    private $region_cache = [];
46
47
    /**
48
     * Instantiate this object and setup countries (if provided)
49
     *
50
     * @param array $countries
51
     */
52
    public function __construct(array $countries = [])
53
    {
54
        $this->setCountriesList($countries);
55
    }
56
57
    /**
58
     * Get a list of region codes, possibly filtered by list of
59
     * country codes.
60
     *
61
     * Each region returned is of the format:
62
     *
63
     *  - name - region name
64
     *  - type - region type
65
     *  - code - full region code (2 character country code and 3
66
     *           character region code)
67
     *  - region_code - 3 character region code
68
     *  - country_code - 2 character region code
69
     *
70
     * NOTE: As we are dealing with over 4,000 regions, we cache the
71
     * results to improve performace. If you need this helper to regenerate
72
     * the regions, make sure you call @link clearRegionCache method
73
     * first
74
     * 
75
     * @return array
76
     */
77
    public function getRegionArray()
78
    {
79
        if (count($this->region_cache) > 0) {
80
            return $this->region_cache;
81
        }
82
83
        $countries = $this->getCountriesList();
84
        $limit_regions = $this->getLimitRegionCodes();
85
        $regions = $this->config()->iso_3166_regions;
86
        $results = [];
87
88
        // Filter generate a new list of regions with some more useful
89
        // data and filter by country if relevent
90
        foreach ($regions as $item) {
91
            if (array_key_exists("code", $item) && array_key_exists("name", $item)) {
92
                $codes = explode("-", $item["code"]);
93
                $region_code = substr($codes[1], -3, 3);
94
                $country_code = $codes[0];
95
                $type = array_key_exists("type", $item) ? $item["type"] : "";
96
97
                if (count($countries) > 0 && !in_array($country_code, $countries)) {
98
                    continue;
99
                }
100
101
                if (count($limit_regions) > 0 && !in_array($region_code, $limit_regions)) {
102
                    continue;
103
                }
104
105
                $results[] = [
106
                    "name" => $item["name"],
107
                    "type" => $type,
108
                    "code" => $item["code"],
109
                    "region_code" => $region_code,
110
                    "country_code" => $country_code
111
                ];
112
            }
113
        }
114
115
        $this->region_cache = $results;
116
117
        return $results;
118
    }
119
120
    /**
121
     * Return a list of objects representing relevent regions
122
     * This list can be filtered by a list of country codes to
123
     * output only relevent regions.
124
     *
125
     * @return \SilverStripe\ORM\ArrayList
126
     */
127
    public function getRegionsAsObjects()
128
    {
129
        $regions = $this->getRegionArray();
130
        $results = ArrayList::create();
131
132
        foreach ($regions as $item) {
133
            $results->add(ArrayData::create([
134
                "Name" => $item["name"],
135
                "Type" => $item["type"],
136
                "Code" => $item["code"],
137
                "RegionCode" => $item['region_code'],
138
                "CountryCode" => $item['country_code']
139
            ]));
140
        }
141
142
        return $results;
143
    }
144
145
    /**
146
     * Generate an array of country codes and names that can be used in
147
     * country dropdowns, or for comparison.
148
     *
149
     * @param bool $codes_only Rturn only an array of 2 character codes (no names)
150
     *
151
     * @return array
152
     */
153
    public function getISOCountries(bool $codes_only = false)
154
    {
155
        $countries = array_change_key_case(
156
            i18n::getData()->getCountries(),
157
            CASE_UPPER
158
        );
159
160
        if ($codes_only === true) {
161
            return array_keys($countries);
162
        }
163
164
        return $countries;
165
    }
166
167
    /**
168
     * Check if the provided country code is valid
169
     *
170
     * @return bool
171
     */
172
    protected function validCountryCode(string $code)
173
    {
174
        $countries = array_keys(array_change_key_case(
175
            i18n::getData()->getCountries(),
176
            CASE_UPPER
177
        ));
178
179
        if (strlen($code) !== 2) {
180
            return false;
181
        }
182
183
        if (!in_array($code, $countries)) {
184
            return false;
185
        }
186
187
        return true;
188
    }
189
190
    /**
191
     * Check if the provided region/sub-division code is valid
192
     *
193
     * @return bool
194
     */
195
    protected function validRegionCode(string $code): bool
196
    {
197
        $all = $this->config()->iso_3166_regions;
198
199
        if (!is_array($all)) {
200
            throw new LogicException('Invalid list of regions in config');
201
        }
202
203
        foreach ($all as $region) {
204
            if (!is_array($region)) {
205
                continue;
206
            }
207
208
            if (!array_key_exists('code', $region)) {
209
                continue;
210
            }
211
212
            $check = explode('-', $region['code']);
213
            $check = $check[1];
214
215
            if ($check === $code) {
216
                return true;
217
            }
218
        }
219
220
        return false;
221
    }
222
223
    /**
224
     * Get list of countries that this helper will filter by
225
     *
226
     * @return array
227
     */
228
    public function getCountriesList()
229
    {
230
        return $this->countries_list;
231
    }
232
233
    /**
234
     * Add a country code to the of countries
235
     *
236
     * @param string $code Single country code
237
     *
238
     * @throws LogicException
239
     * 
240
     * @return self
241
     */ 
242
    public function addCountryToList(string $code)
243
    {
244
        if (!$this->validCountryCode($code)) {
245
            throw new LogicException("You must use ISO 3166 2 character country codes");
246
        }
247
        $this->countries_list[] = $code;
248
        return $this;
249
    }
250
251
    /**
252
     * remove a country code to from the country list
253
     *
254
     * @param string $code Single country code
255
     *
256
     * @throws LogicException
257
     * 
258
     * @return self
259
     */ 
260
    public function removeCountryFromList(string $code)
261
    {
262
        if (!$this->validCountryCode($code)) {
263
            throw new LogicException("You must use ISO 3166 2 character country codes");
264
        }
265
266
        $list = $this->countries_list;
267
268
        if(($key = array_search($code, $list)) !== false) {
269
            unset($list[$key]);
270
        }
271
272
        $this->setCountriesList($list);
273
274
        return $this;
275
    }
276
277
    /**
278
     * Set list of countries (and also perform some basic validation)
279
     *
280
     * @param array $countries_list  List of countrie that this helper will filter by
281
     *
282
     * @throws LogicException
283
     *
284
     * @return self
285
     */ 
286
    public function setCountriesList(array $countries)
287
    {
288
        $this->countries_list = [];
289
290
        foreach ($countries as $code) {
291
            if (empty($code)) {
292
                continue;
293
            }
294
295
            if (!$this->validCountryCode((string)$code)) {
296
                throw new LogicException("You must use ISO 3166 2 character country codes");
297
            }
298
            $this->countries_list[] = $code;
299
        }
300
301
        return $this;
302
    }
303
304
    /**
305
     * Get list of regions that the final list needs to be filtered by
306
     *
307
     * @return array
308
     */
309
    public function getLimitRegionCodes()
310
    {
311
        return $this->limit_region_codes;
312
    }
313
314
    /**
315
     * Add a region code to the list of regions to limit the final list by
316
     *
317
     * @param string $code Single region code
318
     *
319
     * @throws LogicException
320
     *
321
     * @return self
322
     */
323
    public function addLimitRegionCodeToList(string $code)
324
    {
325
        if (!$this->validRegionCode($code)) {
326
            throw new LogicException("You must use ISO 3166 subdivision codes");
327
        }
328
        $this->limit_region_codes[] = $code;
329
        return $this;
330
    }
331
332
    /**
333
     * Remove a region code to from the region code limit list
334
     *
335
     * @param string $code Single region code
336
     *
337
     * @throws LogicException
338
     * 
339
     * @return self
340
     */ 
341
    public function removeLimitRegionCodeFromList(string $code)
342
    {
343
        if (!$this->validRegionCode($code)) {
344
            throw new LogicException("You must use ISO 3166 subdivision codes");
345
        }
346
347
        $list = $this->limit_region_codes;
348
349
        if(($key = array_search($code, $list)) !== false) {
350
            unset($list[$key]);
351
        }
352
353
        $this->setLimitRegionCodes($list);
354
355
        return $this;
356
    }
357
358
    /**
359
     * Set list of regions (and also perform some basic validation)
360
     *
361
     * @param array $regions List of countries that this helper will filter by
362
     *
363
     * @throws LogicException
364
     *
365
     * @return self
366
     */
367
    public function setLimitRegionCodes(array $regions)
368
    {
369
        $this->limit_region_codes = [];
370
371
        foreach ($regions as $region) {
372
            if (empty($region)) {
373
                continue;
374
            }
375
376
            if (!$this->validRegionCode((string)$region)) {
377
                throw new LogicException("Subdivision: '$region' not a valid ISO 3166 code");
378
            }
379
380
            $this->limit_region_codes[] = $region;
381
        }
382
383
        return $this;
384
    }
385
386
    /**
387
     * Clear any existing region cache (if the region list needs re-building)
388
     *
389
     * @return self
390
     */
391
    public function clearRegionCache()
392
    {
393
        $this->region_cache = [];
394
        return $this;
395
    }
396
}
397