Completed
Pull Request — master (#145)
by Graham
09:30
created

ShortNumberInfo   D

Complexity

Total Complexity 85

Size/Duplication

Total Lines 620
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 9

Test Coverage

Coverage 91.92%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 85
lcom 2
cbo 9
dl 0
loc 620
rs 4.7916
c 2
b 0
f 0
ccs 182
cts 198
cp 0.9192

23 Methods

Rating   Name   Duplication   Size   Complexity  
A resetInstance() 0 4 1
A getSupportedRegions() 0 4 1
A getExampleShortNumber() 0 14 4
A getMetadataForRegion() 0 14 4
A loadMetadataFromFile() 0 17 4
A getRegionCodesForCountryCode() 0 10 3
A regionDialingFromMatchesNumber() 0 6 1
A connectsToEmergencyNumber() 0 4 1
B matchesEmergencyNumberHelper() 0 25 5
A getInstance() 0 8 2
C getExampleShortNumberForCost() 0 30 7
A isCarrierSpecific() 0 12 2
B getRegionCodeForShortNumberFromRegionList() 0 21 6
A __construct() 0 12 1
A isPossibleShortNumber() 0 19 4
B isPossibleShortNumberForRegion() 0 28 5
A isValidShortNumber() 0 12 3
B isValidShortNumberForRegion() 0 36 6
C getExpectedCostForRegion() 0 54 10
D getExpectedCost() 0 32 9
A isEmergencyNumber() 0 4 1
A getNationalSignificantNumber() 0 13 2
A matchesPossibleNumberAndNationalNumber() 0 8 3

How to fix   Complexity   

Complex Class

Complex classes like ShortNumberInfo often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ShortNumberInfo, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Methods for getting information about short phone numbers, such as short codes and emergency
4
 * numbers. Note that most commercial short numbers are not handled here, but by the
5
 * {@link PhoneNumberUtil}.
6
 *
7
 * @author Shaopeng Jia
8
 * @author David Yonge-Mallo
9
 * @since 5.8
10
 */
11
12
namespace libphonenumber;
13
14
class ShortNumberInfo
15
{
16
    const META_DATA_FILE_PREFIX = 'ShortNumberMetadata';
17
    /**
18
     * @var ShortNumberInfo
19
     */
20
    protected static $instance = null;
21
    /**
22
     * @var MatcherAPIInterface
23
     */
24
    protected $matcherAPI;
25
    protected $currentFilePrefix;
26
    protected $regionToMetadataMap = array();
27
    protected $countryCallingCodeToRegionCodeMap = array();
28
    protected $countryCodeToNonGeographicalMetadataMap = array();
29
    protected static $regionsWhereEmergencyNumbersMustBeExact = array(
30
        'BR',
31
        'CL',
32
        'NI',
33
    );
34
35 28
    protected function __construct(MatcherAPIInterface $matcherAPI)
36
    {
37 28
        $this->matcherAPI = $matcherAPI;
38
39
        // TODO: Create ShortNumberInfo for a given map
40 28
        $this->countryCallingCodeToRegionCodeMap = CountryCodeToRegionCodeMap::$countryCodeToRegionCodeMap;
41
42 28
        $this->currentFilePrefix = dirname(__FILE__) . '/data/' . static::META_DATA_FILE_PREFIX;
43
44
        // Initialise PhoneNumberUtil to make sure regex's are setup correctly
45 28
        PhoneNumberUtil::getInstance();
46 28
    }
47
48
    /**
49
     * Returns the singleton instance of ShortNumberInfo
50
     *
51
     * @return \libphonenumber\ShortNumberInfo
52
     */
53 5388
    public static function getInstance()
54
    {
55 5388
        if (null === static::$instance) {
56 28
            static::$instance = new self(RegexBasedMatcher::create());
57
        }
58
59 5388
        return static::$instance;
60
    }
61
62 27
    public static function resetInstance()
63
    {
64 27
        static::$instance = null;
65 27
    }
66
67
    /**
68
     * Returns a list with teh region codes that match the specific country calling code. For
69
     * non-geographical country calling codes, the region code 001 is returned. Also, in the case
70
     * of no region code being found, an empty list is returned.
71
     *
72
     * @param int $countryCallingCode
73
     * @return array
74
     */
75 594
    protected function getRegionCodesForCountryCode($countryCallingCode)
76
    {
77 594
        if (!array_key_exists($countryCallingCode, $this->countryCallingCodeToRegionCodeMap)) {
78 1
            $regionCodes = null;
79
        } else {
80 594
            $regionCodes = $this->countryCallingCodeToRegionCodeMap[$countryCallingCode];
81
        }
82
83 594
        return ($regionCodes === null) ? array() : $regionCodes;
84
    }
85
86
    /**
87
     * Helper method to check that the country calling code of the number matches the region it's
88
     * being dialed from.
89
     * @param PhoneNumber $number
90
     * @param string $regionDialingFrom
91
     * @return bool
92
     */
93 593
    protected function regionDialingFromMatchesNumber(PhoneNumber $number, $regionDialingFrom)
94
    {
95 593
        $regionCodes = $this->getRegionCodesForCountryCode($number->getCountryCode());
96
97 593
        return in_array($regionDialingFrom, $regionCodes);
98
    }
99
100
    public function getSupportedRegions()
101
    {
102
        return ShortNumbersRegionCodeSet::$shortNumbersRegionCodeSet;
103
    }
104
105
    /**
106
     * Gets a valid short number for the specified region.
107
     *
108
     * @param $regionCode String the region for which an example short number is needed
109
     * @return string a valid short number for the specified region. Returns an empty string when the
110
     *      metadata does not contain such information.
111
     */
112 239
    public function getExampleShortNumber($regionCode)
113
    {
114 239
        $phoneMetadata = $this->getMetadataForRegion($regionCode);
115 239
        if ($phoneMetadata === null) {
116 1
            return "";
117
        }
118
119
        /** @var PhoneNumberDesc $desc */
120 239
        $desc = $phoneMetadata->getShortCode();
121 239
        if ($desc !== null && $desc->hasExampleNumber()) {
122 239
            return $desc->getExampleNumber();
123
        }
124
        return "";
125
    }
126
127
    /**
128
     * @param $regionCode
129
     * @return PhoneMetadata|null
130
     */
131 1947
    public function getMetadataForRegion($regionCode)
132
    {
133 1947
        if (!in_array($regionCode, ShortNumbersRegionCodeSet::$shortNumbersRegionCodeSet)) {
134 1
            return null;
135
        }
136
137 1947
        if (!isset($this->regionToMetadataMap[$regionCode])) {
138
            // The regionCode here will be valid and won't be '001', so we don't need to worry about
139
            // what to pass in for the country calling code.
140 262
            $this->loadMetadataFromFile($this->currentFilePrefix, $regionCode, 0);
141
        }
142
143 1947
        return isset($this->regionToMetadataMap[$regionCode]) ? $this->regionToMetadataMap[$regionCode] : null;
144
    }
145
146 262
    protected function loadMetadataFromFile($filePrefix, $regionCode, $countryCallingCode)
147
    {
148 262
        $isNonGeoRegion = PhoneNumberUtil::REGION_CODE_FOR_NON_GEO_ENTITY === $regionCode;
149 262
        $fileName = $filePrefix . '_' . ($isNonGeoRegion ? $countryCallingCode : $regionCode) . '.php';
150 262
        if (!is_readable($fileName)) {
151
            throw new \Exception('missing metadata: ' . $fileName);
152
        } else {
153 262
            $data = include $fileName;
154 262
            $metadata = new PhoneMetadata();
155 262
            $metadata->fromArray($data);
156 262
            if ($isNonGeoRegion) {
157
                $this->countryCodeToNonGeographicalMetadataMap[$countryCallingCode] = $metadata;
158
            } else {
159 262
                $this->regionToMetadataMap[$regionCode] = $metadata;
160
            }
161
        }
162 262
    }
163
164
    /**
165
     *  Gets a valid short number for the specified cost category.
166
     *
167
     * @param string $regionCode the region for which an example short number is needed
168
     * @param int $cost the cost category of number that is needed
169
     * @return string a valid short number for the specified region and cost category. Returns an empty string
170
     * when the metadata does not contain such information, or the cost is UNKNOWN_COST.
171
     */
172 954
    public function getExampleShortNumberForCost($regionCode, $cost)
173
    {
174 954
        $phoneMetadata = $this->getMetadataForRegion($regionCode);
175 954
        if ($phoneMetadata === null) {
176
            return "";
177
        }
178
179
        /** @var PhoneNumberDesc $desc */
180 954
        $desc = null;
181
        switch ($cost) {
182 954
            case ShortNumberCost::TOLL_FREE:
183 240
                $desc = $phoneMetadata->getTollFree();
184 240
                break;
185 716
            case ShortNumberCost::STANDARD_RATE:
186 240
                $desc = $phoneMetadata->getStandardRate();
187 240
                break;
188 478
            case ShortNumberCost::PREMIUM_RATE:
189 240
                $desc = $phoneMetadata->getPremiumRate();
190 240
                break;
191
            default:
192
                // UNKNOWN_COST numbers are computed by the process of elimination from the other cost categories
193 239
                break;
194
        }
195
196 954
        if ($desc !== null && $desc->hasExampleNumber()) {
197 80
            return $desc->getExampleNumber();
198
        }
199
200 875
        return "";
201
    }
202
203
    /**
204
     * Returns true if the given number, exactly as dialed, might be used to connect to an emergency
205
     * service in the given region.
206
     * <p>
207
     * This method accepts a string, rather than a PhoneNumber, because it needs to distinguish
208
     * cases such as "+1 911" and "911", where the former may not connect to an emergency service in
209
     * all cases but the latter would. This method takes into account cases where the number might
210
     * contain formatting, or might have additional digits appended (when it is okay to do that in
211
     * the specified region).
212
     *
213
     * @param string $number the phone number to test
214
     * @param string $regionCode the region where the phone number if being dialled
215
     * @return boolean whether the number might be used to connect to an emergency service in the given region
216
     */
217 18
    public function connectsToEmergencyNumber($number, $regionCode)
218
    {
219 18
        return $this->matchesEmergencyNumberHelper($number, $regionCode, true /* allows prefix match */);
220
    }
221
222
    /**
223
     * @param string $number
224
     * @param string $regionCode
225
     * @param bool $allowPrefixMatch
226
     * @return bool
227
     */
228 278
    protected function matchesEmergencyNumberHelper($number, $regionCode, $allowPrefixMatch)
229
    {
230 278
        $number = PhoneNumberUtil::extractPossibleNumber($number);
231 278
        $matcher = new Matcher(PhoneNumberUtil::$PLUS_CHARS_PATTERN, $number);
232 278
        if ($matcher->lookingAt()) {
233
            // Returns false if the number starts with a plus sign. We don't believe dialing the country
234
            // code before emergency numbers (e.g. +1911) works, but later, if that proves to work, we can
235
            // add additional logic here to handle it.
236 4
            return false;
237
        }
238
239 274
        $metadata = $this->getMetadataForRegion($regionCode);
240 274
        if ($metadata === null || !$metadata->hasEmergency()) {
241
            return false;
242
        }
243
244 274
        $normalizedNumber = PhoneNumberUtil::normalizeDigitsOnly($number);
245 274
        $emergencyDesc = $metadata->getEmergency();
246
247 274
        $allowPrefixMatchForRegion = ($allowPrefixMatch
248 274
            && !in_array($regionCode, static::$regionsWhereEmergencyNumbersMustBeExact)
249
        );
250
251 274
        return $this->matcherAPI->matchesNationalNumber($normalizedNumber, $emergencyDesc, $allowPrefixMatchForRegion);
252
    }
253
254
    /**
255
     * Given a valid short number, determines whether it is carrier-specific (however, nothing is
256
     * implied about its validity). If it is important that the number is valid, then its validity
257
     * must first be checked using {@link isValidShortNumber} or
258
     * {@link #isValidShortNumberForRegion}.
259
     *
260
     * @param PhoneNumber $number the valid short number to check
261
     * @return boolean whether the short number is carrier-specific (assuming the input was a valid short
262
     *     number).
263
     */
264 32
    public function isCarrierSpecific(PhoneNumber $number)
265
    {
266 32
        $regionCodes = $this->getRegionCodesForCountryCode($number->getCountryCode());
267 32
        $regionCode = $this->getRegionCodeForShortNumberFromRegionList($number, $regionCodes);
268 32
        $nationalNumber = $this->getNationalSignificantNumber($number);
269 32
        $phoneMetadata = $this->getMetadataForRegion($regionCode);
270
271 32
        return ($phoneMetadata !== null) && ($this->matchesPossibleNumberAndNationalNumber(
272
            $nationalNumber,
273 32
            $phoneMetadata->getCarrierSpecific()
274
        ));
275
    }
276
277
    /**
278
     * Helper method to get the region code for a given phone number, from a list of possible region
279
     * codes. If the list contains more than one region, the first region for which the number is
280
     * valid is returned.
281
     *
282
     * @param PhoneNumber $number
283
     * @param $regionCodes
284
     * @return String|null Region Code (or null if none are found)
285
     */
286 274
    protected function getRegionCodeForShortNumberFromRegionList(PhoneNumber $number, $regionCodes)
287
    {
288 274
        if (count($regionCodes) == 0) {
289
            return null;
290 274
        } elseif (count($regionCodes) == 1) {
291 216
            return $regionCodes[0];
292
        }
293
294 59
        $nationalNumber = $this->getNationalSignificantNumber($number);
295
296 59
        foreach ($regionCodes as $regionCode) {
297 59
            $phoneMetadata = $this->getMetadataForRegion($regionCode);
298 59
            if ($phoneMetadata !== null
299 59
                && $this->matchesPossibleNumberAndNationalNumber($nationalNumber, $phoneMetadata->getShortCode())
300
            ) {
301
                // The number is valid for this region.
302 59
                return $regionCode;
303
            }
304
        }
305
        return null;
306
    }
307
308
    /**
309
     * Check whether a short number is a possible number. If a country calling code is shared by
310
     * multiple regions, this returns true if it's possible in any of them. This provides a more
311
     * lenient check than {@link #isValidShortNumber}. See {@link
312
     * #IsPossibleShortNumberForRegion(PhoneNumber, String)} for details.
313
     *
314
     * @param $number PhoneNumber the short number to check
315
     * @return boolean whether the number is a possible short number
316
     */
317 2
    public function isPossibleShortNumber(PhoneNumber $number)
318
    {
319 2
        $regionCodes = $this->getRegionCodesForCountryCode($number->getCountryCode());
320 2
        $shortNumberLength = strlen($this->getNationalSignificantNumber($number));
321
322 2
        foreach ($regionCodes as $region) {
323 2
            $phoneMetadata = $this->getMetadataForRegion($region);
324
325 2
            if ($phoneMetadata === null) {
326
                continue;
327
            }
328
329 2
            if (in_array($shortNumberLength, $phoneMetadata->getGeneralDesc()->getPossibleLength())) {
330 2
                return true;
331
            }
332
        }
333
334 2
        return false;
335
    }
336
337
    /**
338
     * Check whether a short number is a possible number when dialled from a region, given the number
339
     * in the form of a string, and the region where the number is dialled from. This provides a more
340
     * lenient check than {@link #isValidShortNumber}.
341
     *
342
     * @param PhoneNumber|string $shortNumber The short number to check
343
     * @param string $regionDialingFrom Region dialing From
344
     * @return boolean whether the number is a possible short number
345
     */
346 272
    public function isPossibleShortNumberForRegion($shortNumber, $regionDialingFrom)
347
    {
348 272
        if ($shortNumber instanceof PhoneNumber) {
349 272
            if (!$this->regionDialingFromMatchesNumber($shortNumber, $regionDialingFrom)) {
350 1
                return false;
351
            }
352
        }
353 271
        $phoneMetadata = $this->getMetadataForRegion($regionDialingFrom);
354
355 271
        if ($phoneMetadata === null) {
356
            return false;
357
        }
358
359 271
        if ($shortNumber instanceof PhoneNumber) {
360 271
            $numberLength = strlen($this->getNationalSignificantNumber($shortNumber));
361 271
            return in_array($numberLength, $phoneMetadata->getGeneralDesc()->getPossibleLength());
362
        } else {
363
            /**
364
             * @deprecated Anyone who was using it and passing in a string with whitespace (or other
365
             *        formatting characters) would have been getting the wrong result. You should parse
366
             *        the string to PhoneNumber and use the method
367
             *        {@code #isPossibleShortNumberForRegion(PhoneNumber, String)}. This method will be
368
             *        removed in the next release.
369
             */
370
371
            return in_array(strlen($shortNumber), $phoneMetadata->getGeneralDesc()->getPossibleLength());
372
        }
373
    }
374
375
    /**
376
     * Tests whether a short number matches a valid pattern. If a country calling code is shared by
377
     * multiple regions, this returns true if it's valid in any of them. Note that this doesn't verify
378
     * the number is actually in use, which is impossible to tell by just looking at the number
379
     * itself. See {@link #isValidShortNumberForRegion(PhoneNumber, String)} for details.
380
     *
381
     * @param $number PhoneNumber the short number for which we want to test the validity
382
     * @return boolean whether the short number matches a valid pattern
383
     */
384 242
    public function isValidShortNumber(PhoneNumber $number)
385
    {
386 242
        $regionCodes = $this->getRegionCodesForCountryCode($number->getCountryCode());
387 242
        $regionCode = $this->getRegionCodeForShortNumberFromRegionList($number, $regionCodes);
388 242
        if (count($regionCodes) > 1 && $regionCode !== null) {
389
            // If a matching region had been found for the phone number from among two or more regions,
390
            // then we have already implicitly verified its validity for that region.
391 53
            return true;
392
        }
393
394 190
        return $this->isValidShortNumberForRegion($number, $regionCode);
395
    }
396
397
    /**
398
     * Tests whether a short number matches a valid pattern in a region. Note that this doesn't verify
399
     * the number is actually in use, which is impossible to tell by just looking at the number
400
     * itself.
401
     *
402
     * @param PhoneNumber|string $number The Short number for which we want to test the validity
403
     * @param string $regionDialingFrom the region from which the number is dialed
404
     * @return boolean whether the short number matches a valid pattern
405
     */
406 243
    public function isValidShortNumberForRegion($number, $regionDialingFrom)
407
    {
408 243
        if ($number instanceof PhoneNumber) {
409 243
            if (!$this->regionDialingFromMatchesNumber($number, $regionDialingFrom)) {
410 1
                return false;
411
            }
412
        }
413 242
        $phoneMetadata = $this->getMetadataForRegion($regionDialingFrom);
414
415 242
        if ($phoneMetadata === null) {
416
            return false;
417
        }
418
419 242
        if ($number instanceof PhoneNumber) {
420 242
            $shortNumber = $this->getNationalSignificantNumber($number);
421
        } else {
422
            /**
423
             * @deprecated Anyone who was using it and passing in a string with whitespace (or other
424
             *             formatting characters) would have been getting the wrong result. You should parse
425
             *             the string to PhoneNumber and use the method
426
             *             {@code #isValidShortNumberForRegion(PhoneNumber, String)}. This method will be
427
             *             removed in the next release.
428
             */
429
            $shortNumber = $number;
430
        }
431
432 242
        $generalDesc = $phoneMetadata->getGeneralDesc();
433
434 242
        if (!$this->matchesPossibleNumberAndNationalNumber($shortNumber, $generalDesc)) {
435 1
            return false;
436
        }
437
438 242
        $shortNumberDesc = $phoneMetadata->getShortCode();
439
440 242
        return $this->matchesPossibleNumberAndNationalNumber($shortNumber, $shortNumberDesc);
441
    }
442
443
    /**
444
     * Gets the expected cost category of a short number  when dialled from a region (however, nothing is
445
     * implied about its validity). If it is important that the number is valid, then its validity
446
     * must first be checked using {@link isValidShortNumberForRegion}. Note that emergency numbers
447
     * are always considered toll-free.
448
     * Example usage:
449
     * <pre>{@code
450
     * $shortInfo = ShortNumberInfo::getInstance();
451
     * $shortNumber = "110";
452
     * $regionCode = "FR";
453
     * if ($shortInfo->isValidShortNumberForRegion($shortNumber, $regionCode)) {
454
     *     $cost = $shortInfo->getExpectedCostForRegion($shortNumber, $regionCode);
455
     *    // Do something with the cost information here.
456
     * }}</pre>
457
     *
458
     * @param PhoneNumber|string $number the short number for which we want to know the expected cost category,
459
     *     as a string
460
     * @param string $regionDialingFrom the region from which the number is dialed
461
     * @return int the expected cost category for that region of the short number. Returns UNKNOWN_COST if
462
     *     the number does not match a cost category. Note that an invalid number may match any cost
463
     *     category.
464
     */
465 320
    public function getExpectedCostForRegion($number, $regionDialingFrom)
466
    {
467 320
        if ($number instanceof PhoneNumber) {
468 320
            if (!$this->regionDialingFromMatchesNumber($number, $regionDialingFrom)) {
469 2
                return ShortNumberCost::UNKNOWN_COST;
470
            }
471
        }
472
        // Note that regionDialingFrom may be null, in which case phoneMetadata will also be null.
473 319
        $phoneMetadata = $this->getMetadataForRegion($regionDialingFrom);
474 319
        if ($phoneMetadata === null) {
475
            return ShortNumberCost::UNKNOWN_COST;
476
        }
477
478 319
        if ($number instanceof PhoneNumber) {
479 319
            $shortNumber = $this->getNationalSignificantNumber($number);
480
        } else {
481
            /**
482
             * @deprecated Anyone who was using it and passing in a string with whitespace (or other
483
             *             formatting characters) would have been getting the wrong result. You should parse
484
             *             the string to PhoneNumber and use the method
485
             *             {@code #getExpectedCostForRegion(PhoneNumber, String)}. This method will be
486
             *             removed in the next release.
487
             */
488
            $shortNumber = $number;
489
        }
490
491
        // The possible lengths are not present for a particular sub-type if they match the general
492
        // description; for this reason, we check the possible lengths against the general description
493
        // first to allow an early exit if possible.
494 319
        if (!in_array(strlen($shortNumber), $phoneMetadata->getGeneralDesc()->getPossibleLength())) {
495 1
            return ShortNumberCost::UNKNOWN_COST;
496
        }
497
498
        // The cost categories are tested in order of decreasing expense, since if for some reason the
499
        // patterns overlap the most expensive matching cost category should be returned.
500 319
        if ($this->matchesPossibleNumberAndNationalNumber($shortNumber, $phoneMetadata->getPremiumRate())) {
501 15
            return ShortNumberCost::PREMIUM_RATE;
502
        }
503
504 306
        if ($this->matchesPossibleNumberAndNationalNumber($shortNumber, $phoneMetadata->getStandardRate())) {
505 19
            return ShortNumberCost::STANDARD_RATE;
506
        }
507
508 289
        if ($this->matchesPossibleNumberAndNationalNumber($shortNumber, $phoneMetadata->getTollFree())) {
509 53
            return ShortNumberCost::TOLL_FREE;
510
        }
511
512 239
        if ($this->isEmergencyNumber($shortNumber, $regionDialingFrom)) {
513
            // Emergency numbers are implicitly toll-free.
514 237
            return ShortNumberCost::TOLL_FREE;
515
        }
516
517 3
        return ShortNumberCost::UNKNOWN_COST;
518
    }
519
520
    /**
521
     * Gets the expected cost category of a short number (however, nothing is implied about its
522
     * validity). If the country calling code is unique to a region, this method behaves exactly the
523
     * same as {@link #getExpectedCostForRegion(PhoneNumber, String)}. However, if the country calling
524
     * code is shared by multiple regions, then it returns the highest cost in the sequence
525
     * PREMIUM_RATE, UNKNOWN_COST, STANDARD_RATE, TOLL_FREE. The reason for the position of
526
     * UNKNOWN_COST in this order is that if a number is UNKNOWN_COST in one region but STANDARD_RATE
527
     * or TOLL_FREE in another, its expected cost cannot be estimated as one of the latter since it
528
     * might be a PREMIUM_RATE number.
529
     *
530
     * <p>
531
     * For example, if a number is STANDARD_RATE in the US, but TOLL_FREE in Canada, the expected
532
     * cost returned by this method will be STANDARD_RATE, since the NANPA countries share the same
533
     * country calling code.
534
     * </p>
535
     *
536
     * Note: If the region from which the number is dialed is known, it is highly preferable to call
537
     * {@link #getExpectedCostForRegion(PhoneNumber, String)} instead.
538
     *
539
     * @param PhoneNumber $number the short number for which we want to know the expected cost category
540
     * @return int the highest expected cost category of the short number in the region(s) with the given
541
     *     country calling code
542
     */
543 3
    public function getExpectedCost(PhoneNumber $number)
544
    {
545 3
        $regionCodes = $this->getRegionCodesForCountryCode($number->getCountryCode());
546 3
        if (count($regionCodes) == 0) {
547 1
            return ShortNumberCost::UNKNOWN_COST;
548
        }
549 3
        if (count($regionCodes) == 1) {
550 1
            return $this->getExpectedCostForRegion($number, $regionCodes[0]);
551
        }
552 2
        $cost = ShortNumberCost::TOLL_FREE;
553 2
        foreach ($regionCodes as $regionCode) {
554 2
            $costForRegion = $this->getExpectedCostForRegion($number, $regionCode);
555
            switch ($costForRegion) {
556 2
                case ShortNumberCost::PREMIUM_RATE:
557 1
                    return ShortNumberCost::PREMIUM_RATE;
558
559 2
                case ShortNumberCost::UNKNOWN_COST:
560 1
                    $cost = ShortNumberCost::UNKNOWN_COST;
561 1
                    break;
562
563 2
                case ShortNumberCost::STANDARD_RATE:
564 1
                    if ($cost != ShortNumberCost::UNKNOWN_COST) {
565 1
                        $cost = ShortNumberCost::STANDARD_RATE;
566
                    }
567 1
                    break;
568 2
                case ShortNumberCost::TOLL_FREE:
569
                    // Do nothing
570 2
                    break;
571
            }
572
        }
573 2
        return $cost;
574
    }
575
576
    /**
577
     * Returns true if the given number exactly matches an emergency service number in the given
578
     * region.
579
     * <p>
580
     * This method takes into account cases where the number might contain formatting, but doesn't
581
     * allow additional digits to be appended. Note that {@code isEmergencyNumber(number, region)}
582
     * implies {@code connectsToEmergencyNumber(number, region)}.
583
     *
584
     * @param string $number the phone number to test
585
     * @param string $regionCode the region where the phone number is being dialled
586
     * @return boolean whether the number exactly matches an emergency services number in the given region
587
     */
588 260
    public function isEmergencyNumber($number, $regionCode)
589
    {
590 260
        return $this->matchesEmergencyNumberHelper($number, $regionCode, false /* doesn't allow prefix match */);
591
    }
592
593
    /**
594
     * Gets the national significant number of the a phone number. Note a national significant number
595
     * doesn't contain a national prefix or any formatting.
596
     * <p>
597
     * This is a temporary duplicate of the {@code getNationalSignificantNumber} method from
598
     * {@code PhoneNumberUtil}. Ultimately a canonical static version should exist in a separate
599
     * utility class (to prevent {@code ShortNumberInfo} needing to depend on PhoneNumberUtil).
600
     *
601
     * @param PhoneNumber $number the phone number for which the national significant number is needed
602
     * @return string the national significant number of the PhoneNumber object passed in
603
     */
604 593
    protected function getNationalSignificantNumber(PhoneNumber $number)
605
    {
606
        // If leading zero(s) have been set, we prefix this now. Note this is not a national prefix.
607 593
        $nationalNumber = '';
608 593
        if ($number->isItalianLeadingZero()) {
609 10
            $zeros = str_repeat('0', $number->getNumberOfLeadingZeros());
610 10
            $nationalNumber .= $zeros;
611
        }
612
613 593
        $nationalNumber .= $number->getNationalNumber();
614
615 593
        return $nationalNumber;
616
    }
617
618
    /**
619
     * TODO: Once we have benchmarked ShortnumberInfo, consider if it is worth keeping
620
     * this performance optimization.
621
     * @param string $number
622
     * @param PhoneNumberDesc $numberDesc
623
     * @return bool
624
     */
625 590
    protected function matchesPossibleNumberAndNationalNumber($number, PhoneNumberDesc $numberDesc)
626
    {
627 590
        if (count($numberDesc->getPossibleLength()) > 0 && !in_array(strlen($number), $numberDesc->getPossibleLength())) {
628 293
            return false;
629
        }
630
631 371
        return $this->matcherAPI->matchesNationalNumber($number, $numberDesc, false);
632
    }
633
}
634