Completed
Pull Request — master (#94)
by Joshua
82:48 queued 60:07
created

ShortNumberInfo   D

Complexity

Total Complexity 83

Size/Duplication

Total Lines 612
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 9

Test Coverage

Coverage 91.84%

Importance

Changes 6
Bugs 1 Features 0
Metric Value
wmc 83
c 6
b 1
f 0
lcom 2
cbo 9
dl 0
loc 612
ccs 180
cts 196
cp 0.9184
rs 4.824

23 Methods

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

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