Completed
Pull Request — master (#119)
by Joshua
58:13 queued 41:04
created

PhoneNumberOfflineGeocoder::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
3
namespace libphonenumber\geocoding;
4
5
use Giggsey\Locale\Locale;
6
use libphonenumber\NumberParseException;
7
use libphonenumber\PhoneNumber;
8
use libphonenumber\PhoneNumberType;
9
use libphonenumber\PhoneNumberUtil;
10
use libphonenumber\prefixmapper\PrefixFileReader;
11
12
class PhoneNumberOfflineGeocoder
13
{
14
    const MAPPING_DATA_DIRECTORY = '/data';
15
    /**
16
     * @var PhoneNumberOfflineGeocoder
17
     */
18
    protected static $instance;
19
    /**
20
     * @var PhoneNumberUtil
21
     */
22
    protected $phoneUtil;
23
    /**
24
     * @var PrefixFileReader
25
     */
26
    protected $prefixFileReader = null;
27
28 245
    protected function __construct($phonePrefixDataDirectory)
29
    {
30 245
        $this->phoneUtil = PhoneNumberUtil::getInstance();
31
32 245
        $this->prefixFileReader = new PrefixFileReader(dirname(__FILE__) . $phonePrefixDataDirectory);
33 245
    }
34
35
    /**
36
     * Gets a PhoneNumberOfflineGeocoder instance to carry out international phone number geocoding.
37
     *
38
     * <p>The PhoneNumberOfflineGeocoder is implemented as a singleton. Therefore, calling this method
39
     * multiple times will only result in one instance being created.
40
     *
41
     * @param string $mappingDir (Optional) Mapping Data Directory
42
     * @return PhoneNumberOfflineGeocoder
43
     */
44 250
    public static function getInstance($mappingDir = self::MAPPING_DATA_DIRECTORY)
45
    {
46 250
        if (static::$instance === null) {
47 245
            static::$instance = new static($mappingDir);
48 245
        }
49
50 250
        return static::$instance;
51
    }
52
53 245
    public static function resetInstance()
54
    {
55 245
        static::$instance = null;
56 245
    }
57
58
    /**
59
     * As per getDescriptionForValidNumber, but explicitly checks the validity of the number
60
     * passed in.
61
     *
62
     *
63
     * @see getDescriptionForValidNumber
64
     * @param PhoneNumber $number a valid phone number for which we want to get a text description
65
     * @param string $locale the language code for which the description should be written
66
     * @param string $userRegion the region code for a given user. This region will be omitted from the
67
     *     description if the phone number comes from this region. It is a two-letter uppercase ISO
68
     *     country code as defined by ISO 3166-1.
69
     * @return string a text description for the given language code for the given phone number, or empty
70
     *     string if the number passed in is invalid
71
     */
72 16
    public function getDescriptionForNumber(PhoneNumber $number, $locale, $userRegion = null)
73
    {
74 16
        $numberType = $this->phoneUtil->getNumberType($number);
75
76 16
        if ($numberType === PhoneNumberType::UNKNOWN) {
77 3
            return "";
78 15
        } elseif (!$this->canBeGeocoded($numberType)) {
79 2
            return $this->getCountryNameForNumber($number, $locale);
80
        }
81
82 14
        return $this->getDescriptionForValidNumber($number, $locale, $userRegion);
83
    }
84
85
    /**
86
     * A similar method is implemented as PhoneNumberUtil.isNumberGeographical, which performs a
87
     * stricter check, as it determines if a number has a geographical association. Also, if new
88
     * phone number types were added, we should check if this other method should be updated too.
89
     *
90
     * @param int $numberType
91
     * @return boolean
92
     */
93 15
    protected function canBeGeocoded($numberType)
94
    {
95 15
        return ($numberType === PhoneNumberType::FIXED_LINE || $numberType === PhoneNumberType::MOBILE || $numberType === PhoneNumberType::FIXED_LINE_OR_MOBILE);
96
    }
97
98
    /**
99
     * Returns the customary display name in the given language for the given territory the phone
100
     * number is from. If it could be from many territories, nothing is returned.
101
     *
102
     * @param PhoneNumber $number
103
     * @param $locale
104
     * @return string
105
     */
106 8
    protected function getCountryNameForNumber(PhoneNumber $number, $locale)
107
    {
108 8
        $regionCodes = $this->phoneUtil->getRegionCodesForCountryCode($number->getCountryCode());
109
110 8
        if (count($regionCodes) === 1) {
111 4
            return $this->getRegionDisplayName($regionCodes[0], $locale);
112
        } else {
113 5
            $regionWhereNumberIsValid = 'ZZ';
114 5
            foreach ($regionCodes as $regionCode) {
115 5
                if ($this->phoneUtil->isValidNumberForRegion($number, $regionCode)) {
116 5
                    if ($regionWhereNumberIsValid !== 'ZZ') {
117
                        // If we can't assign the phone number as definitely belonging to only one territory,
118
                        // then we return nothing.
119 1
                        return "";
120
                    }
121 5
                    $regionWhereNumberIsValid = $regionCode;
122 5
                }
123 5
            }
124
125 4
            return $this->getRegionDisplayName($regionWhereNumberIsValid, $locale);
126
        }
127
    }
128
129
    /**
130
     * Returns the customary display name in the given language for the given region.
131
     *
132
     * @param $regionCode
133
     * @param $locale
134
     * @return string
135
     */
136 241
    protected function getRegionDisplayName($regionCode, $locale)
137
    {
138 241
        if ($regionCode === null || $regionCode == 'ZZ' || $regionCode === PhoneNumberUtil::REGION_CODE_FOR_NON_GEO_ENTITY) {
139 1
            return "";
140
        }
141
142 241
        return Locale::getDisplayRegion(
143 241
            '-' . $regionCode,
144
            $locale
145 241
        );
146
    }
147
148
    /**
149
     * Returns a text description for the given phone number, in the language provided. The
150
     * description might consist of the name of the country where the phone number is from, or the
151
     * name of the geographical area the phone number is from if more detailed information is
152
     * available.
153
     *
154
     * <p>This method assumes the validity of the number passed in has already been checked, and that
155
     * the number is suitable for geocoding. We consider fixed-line and mobile numbers possible
156
     * candidates for geocoding.
157
     *
158
     * <p>If $userRegion is set, we also consider the region of the user. If the phone number is from
159
     * the same region as the user, only a lower-level description will be returned, if one exists.
160
     * Otherwise, the phone number's region will be returned, with optionally some more detailed
161
     * information.
162
     *
163
     * <p>For example, for a user from the region "US" (United States), we would show "Mountain View,
164
     * CA" for a particular number, omitting the United States from the description. For a user from
165
     * the United Kingdom (region "GB"), for the same number we may show "Mountain View, CA, United
166
     * States" or even just "United States".
167
     *
168
     * @param PhoneNumber $number a valid phone number for which we want to get a text description
169
     * @param string $locale the language code for which the description should be written
170
     * @param string $userRegion the region code for a given user. This region will be omitted from the
171
     *     description if the phone number comes from this region. It is a two-letter uppercase ISO
172
     *     country code as defined by ISO 3166-1.
173
     * @return string a text description for the given language code for the given phone number
174
     */
175 247
    public function getDescriptionForValidNumber(PhoneNumber $number, $locale, $userRegion = null)
176
    {
177
        // If the user region matches the number's region, then we just show the lower-level
178
        // description, if one exists - if no description exists, we will show the region(country) name
179
        // for the number.
180 247
        $regionCode = $this->phoneUtil->getRegionCodeForNumber($number);
181 247
        if ($userRegion == null || $userRegion == $regionCode) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $userRegion of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
182 14
            $languageStr = Locale::getPrimaryLanguage($locale);
183 14
            $scriptStr = "";
184 14
            $regionStr = Locale::getRegion($locale);
185
186 14
            $mobileToken = $this->phoneUtil->getCountryMobileToken($number->getCountryCode());
187 14
            $nationalNumber = $this->phoneUtil->getNationalSignificantNumber($number);
188 14
            if ($mobileToken !== "" && (!strncmp($nationalNumber, $mobileToken, strlen($mobileToken)))) {
189
                // In some countries, eg. Argentina, mobile numbers have a mobile token before the national
190
                // destination code, this should be removed before geocoding.
191 1
                $nationalNumber = substr($nationalNumber, strlen($mobileToken));
192 1
                $region = $this->phoneUtil->getRegionCodeForCountryCode($number->getCountryCode());
193
                try {
194 1
                    $copiedNumber = $this->phoneUtil->parse($nationalNumber, $region);
195 1
                } catch (NumberParseException $e) {
196
                    // If this happens, just reuse what we had.
197
                    $copiedNumber = $number;
198
                }
199 1
                $areaDescription = $this->prefixFileReader->getDescriptionForNumber($copiedNumber, $languageStr, $scriptStr, $regionStr);
200 1
            } else {
201 13
                $areaDescription = $this->prefixFileReader->getDescriptionForNumber($number, $languageStr, $scriptStr, $regionStr);
202
            }
203
204 14
            return (strlen($areaDescription) > 0) ? $areaDescription : $this->getCountryNameForNumber($number, $locale);
205
        }
206
        // Otherwise, we just show the region(country) name for now.
207 234
        return $this->getRegionDisplayName($regionCode, $locale);
208
        // TODO: Concatenate the lower-level and country-name information in an appropriate
209
        // way for each language.
210
    }
211
}
212