1 | <?php |
||||||
2 | |||||||
3 | namespace libphonenumber; |
||||||
4 | |||||||
5 | use libphonenumber\Leniency\AbstractLeniency; |
||||||
6 | |||||||
7 | /** |
||||||
8 | * A class that finds and extracts telephone numbers from $text. |
||||||
9 | * Instances can be created using PhoneNumberUtil::findNumbers() |
||||||
10 | * |
||||||
11 | * Vanity numbers (phone numbers using alphabetic digits such as '1-800-SIX-FLAGS' are |
||||||
12 | * not found. |
||||||
13 | * |
||||||
14 | * @package libphonenumber |
||||||
15 | */ |
||||||
16 | class PhoneNumberMatcher implements \Iterator |
||||||
17 | { |
||||||
18 | protected static $initialized = false; |
||||||
19 | |||||||
20 | /** |
||||||
21 | * The phone number pattern used by $this->find(), similar to |
||||||
22 | * PhoneNumberUtil::VALID_PHONE_NUMBER, but with the following differences: |
||||||
23 | * <ul> |
||||||
24 | * <li>All captures are limited in order to place an upper bound to the text matched by the |
||||||
25 | * pattern. |
||||||
26 | * <ul> |
||||||
27 | * <li>Leading punctuation / plus signs are limited. |
||||||
28 | * <li>Consecutive occurrences of punctuation are limited. |
||||||
29 | * <li>Number of digits is limited. |
||||||
30 | * </ul> |
||||||
31 | * <li>No whitespace is allowed at the start or end. |
||||||
32 | * <li>No alpha digits (vanity numbers such as 1-800-SIX-FLAGS) are currently supported. |
||||||
33 | * </ul> |
||||||
34 | * |
||||||
35 | * @var string |
||||||
36 | */ |
||||||
37 | protected static $pattern; |
||||||
38 | |||||||
39 | /** |
||||||
40 | * Matches strings that look like publication pages. Example: |
||||||
41 | * <pre>Computing Complete Answers to Queries in the Presence of Limited Access Patterns. |
||||||
42 | * Chen Li. VLDB J. 12(3): 211-227 (2003).</pre> |
||||||
43 | * |
||||||
44 | * The string "211-227 (2003)" is not a telephone number. |
||||||
45 | * |
||||||
46 | * @var string |
||||||
47 | */ |
||||||
48 | protected static $pubPages = "\\d{1,5}-+\\d{1,5}\\s{0,4}\\(\\d{1,4}"; |
||||||
49 | |||||||
50 | /** |
||||||
51 | * Matches strings that look like dates using "/" as a separator. Examples 3/10/2011, 31/10/2011 or |
||||||
52 | * 08/31/95. |
||||||
53 | * |
||||||
54 | * @var string |
||||||
55 | */ |
||||||
56 | protected static $slashSeparatedDates = "(?:(?:[0-3]?\\d/[01]?\\d)|(?:[01]?\\d/[0-3]?\\d))/(?:[12]\\d)?\\d{2}"; |
||||||
57 | |||||||
58 | /** |
||||||
59 | * Matches timestamps. Examples: "2012-01-02 08:00". Note that the reg-ex does not include the |
||||||
60 | * trailing ":\d\d" -- that is covered by timeStampsSuffix. |
||||||
61 | * |
||||||
62 | * @var string |
||||||
63 | */ |
||||||
64 | protected static $timeStamps = "[12]\\d{3}[-/]?[01]\\d[-/]?[0-3]\\d +[0-2]\\d$"; |
||||||
65 | protected static $timeStampsSuffix = ":[0-5]\\d"; |
||||||
66 | |||||||
67 | /** |
||||||
68 | * Pattern to check that brackets match. Opening brackets should be closed within a phone number. |
||||||
69 | * This also checks that there is something inside the brackets. Having no brackets at all is also |
||||||
70 | * fine. |
||||||
71 | * |
||||||
72 | * @var string |
||||||
73 | */ |
||||||
74 | protected static $matchingBrackets; |
||||||
75 | |||||||
76 | /** |
||||||
77 | * Patterns used to extract phone numbers from a larger phone-number-like pattern. These are |
||||||
78 | * ordered according to specificity. For example, white-space is last since that is frequently |
||||||
79 | * used in numbers, not just to separate two numbers. We have separate patterns since we don't |
||||||
80 | * want to break up the phone-number-like text on more than one different kind of symbol at one |
||||||
81 | * time, although symbols of the same type (e.g. space) can be safely grouped together. |
||||||
82 | * |
||||||
83 | * Note that if there is a match, we will always check any text found up to the first match as |
||||||
84 | * well. |
||||||
85 | * |
||||||
86 | * @var string[] |
||||||
87 | */ |
||||||
88 | protected static $innerMatches = array(); |
||||||
89 | |||||||
90 | /** |
||||||
91 | * Punctuation that may be at the start of a phone number - brackets and plus signs. |
||||||
92 | * |
||||||
93 | * @var string |
||||||
94 | */ |
||||||
95 | protected static $leadClass; |
||||||
96 | |||||||
97 | /** |
||||||
98 | * Prefix of the files |
||||||
99 | * @var string |
||||||
100 | */ |
||||||
101 | protected static $alternateFormatsFilePrefix; |
||||||
102 | const META_DATA_FILE_PREFIX = 'PhoneNumberAlternateFormats'; |
||||||
103 | |||||||
104 | 1 | protected static function init() |
|||||
105 | { |
||||||
106 | 1 | static::$alternateFormatsFilePrefix = \dirname(__FILE__) . '/data/' . static::META_DATA_FILE_PREFIX; |
|||||
107 | |||||||
108 | 1 | static::$innerMatches = array( |
|||||
109 | // Breaks on the slash - e.g. "651-234-2345/332-445-1234" |
||||||
110 | '/+(.*)', |
||||||
111 | // Note that the bracket here is inside the capturing group, since we consider it part of the |
||||||
112 | // phone number. Will match a pattern like "(650) 223 3345 (754) 223 3321". |
||||||
113 | "(\\([^(]*)", |
||||||
114 | // Breaks on a hyphen - e.g. "12345 - 332-445-1234 is my number." |
||||||
115 | // We require a space on either side of the hyphen for it to be considered a separator. |
||||||
116 | "(?:\\p{Z}-|-\\p{Z})\\p{Z}*(.+)", |
||||||
117 | // Various types of wide hyphens. Note we have decided not to enforce a space here, since it's |
||||||
118 | // possible that it's supposed to be used to break two numbers without spaces, and we haven't |
||||||
119 | // seen many instances of it used within a number. |
||||||
120 | "[‒-―-]\\p{Z}*(.+)", |
||||||
121 | // Breaks on a full stop - e.g. "12345. 332-445-1234 is my number." |
||||||
122 | "\\.+\\p{Z}*([^.]+)", |
||||||
123 | // Breaks on space - e.g. "3324451234 8002341234" |
||||||
124 | "\\p{Z}+(\\P{Z}+)" |
||||||
125 | ); |
||||||
126 | |||||||
127 | /* |
||||||
128 | * Builds the matchingBrackets and pattern regular expressions. The building blocks exist |
||||||
129 | * to make the pattern more easily understood. |
||||||
130 | */ |
||||||
131 | |||||||
132 | 1 | $openingParens = "(\\[\xEF\xBC\x88\xEF\xBC\xBB"; |
|||||
133 | 1 | $closingParens = ")\\]\xEF\xBC\x89\xEF\xBC\xBD"; |
|||||
134 | 1 | $nonParens = '[^' . $openingParens . $closingParens . ']'; |
|||||
135 | |||||||
136 | // Limit on the number of pairs of brackets in a phone number. |
||||||
137 | 1 | $bracketPairLimit = static::limit(0, 3); |
|||||
138 | |||||||
139 | /* |
||||||
140 | * An opening bracket at the beginning may not be closed, but subsequent ones should be. It's |
||||||
141 | * also possible that the leading bracket was dropped, so we shouldn't be surprised if we see a |
||||||
142 | * closing bracket first. We limit the sets of brackets in a phone number to four. |
||||||
143 | */ |
||||||
144 | 1 | static::$matchingBrackets = |
|||||
145 | 1 | '(?:[' . $openingParens . '])?' . '(?:' . $nonParens . '+' . '[' . $closingParens . '])?' |
|||||
146 | 1 | . $nonParens . '+' |
|||||
147 | 1 | . '(?:[' . $openingParens . ']' . $nonParens . '+[' . $closingParens . '])' . $bracketPairLimit |
|||||
148 | 1 | . $nonParens . '*'; |
|||||
149 | |||||||
150 | // Limit on the number of leading (plus) characters. |
||||||
151 | 1 | $leadLimit = static::limit(0, 2); |
|||||
152 | |||||||
153 | // Limit on the number of consecutive punctuation characters. |
||||||
154 | 1 | $punctuationLimit = static::limit(0, 4); |
|||||
155 | |||||||
156 | /* |
||||||
157 | * The maximum number of digits allowed in a digit-separated block. As we allow all digits in a |
||||||
158 | * single block, set high enough to accommodate the entire national number and the international |
||||||
159 | * country code |
||||||
160 | */ |
||||||
161 | 1 | $digitBlockLimit = PhoneNumberUtil::MAX_LENGTH_FOR_NSN + PhoneNumberUtil::MAX_LENGTH_COUNTRY_CODE; |
|||||
162 | |||||||
163 | /* |
||||||
164 | * Limit on the number of blocks separated by the punctuation. Uses digitBlockLimit since some |
||||||
165 | * formats use spaces to separate each digit |
||||||
166 | */ |
||||||
167 | 1 | $blockLimit = static::limit(0, $digitBlockLimit); |
|||||
168 | |||||||
169 | // A punctuation sequence allowing white space |
||||||
170 | 1 | $punctuation = '[' . PhoneNumberUtil::VALID_PUNCTUATION . ']' . $punctuationLimit; |
|||||
171 | |||||||
172 | // A digits block without punctuation. |
||||||
173 | 1 | $digitSequence = "\\p{Nd}" . static::limit(1, $digitBlockLimit); |
|||||
174 | |||||||
175 | |||||||
176 | 1 | $leadClassChars = $openingParens . PhoneNumberUtil::PLUS_CHARS; |
|||||
177 | 1 | $leadClass = '[' . $leadClassChars . ']'; |
|||||
178 | 1 | static::$leadClass = $leadClass; |
|||||
179 | |||||||
180 | // Init extension patterns from PhoneNumberUtil |
||||||
181 | 1 | PhoneNumberUtil::initExtnPatterns(); |
|||||
182 | |||||||
183 | // Phone number pattern allowing optional punctuation. |
||||||
184 | 1 | static::$pattern = '(?:' . $leadClass . $punctuation . ')' . $leadLimit |
|||||
185 | 1 | . $digitSequence . '(?:' . $punctuation . $digitSequence . ')' . $blockLimit |
|||||
186 | 1 | . '(?:' . PhoneNumberUtil::$EXTN_PATTERNS_FOR_MATCHING . ')?'; |
|||||
187 | |||||||
188 | 1 | static::$initialized = true; |
|||||
189 | 1 | } |
|||||
190 | |||||||
191 | /** |
||||||
192 | * Helper function to generate regular expression with an upper and lower limit. |
||||||
193 | * |
||||||
194 | * @param int $lower |
||||||
195 | * @param int $upper |
||||||
196 | * @return string |
||||||
197 | */ |
||||||
198 | 1 | protected static function limit($lower, $upper) |
|||||
199 | { |
||||||
200 | 1 | if (($lower < 0) || ($upper <= 0) || ($upper < $lower)) { |
|||||
201 | throw new \InvalidArgumentException(); |
||||||
202 | } |
||||||
203 | |||||||
204 | 1 | return '{' . $lower . ',' . $upper . '}'; |
|||||
205 | } |
||||||
206 | |||||||
207 | /** |
||||||
208 | * The phone number utility. |
||||||
209 | * @var PhoneNumberUtil |
||||||
210 | */ |
||||||
211 | protected $phoneUtil; |
||||||
212 | |||||||
213 | /** |
||||||
214 | * The text searched for phone numbers. |
||||||
215 | * @var string |
||||||
216 | */ |
||||||
217 | protected $text; |
||||||
218 | |||||||
219 | /** |
||||||
220 | * The region (country) to assume for phone numbers without an international prefix, possibly |
||||||
221 | * null. |
||||||
222 | * @var string |
||||||
223 | */ |
||||||
224 | protected $preferredRegion; |
||||||
225 | |||||||
226 | /** |
||||||
227 | * The degrees of validation requested. |
||||||
228 | * @var AbstractLeniency |
||||||
229 | */ |
||||||
230 | protected $leniency; |
||||||
231 | |||||||
232 | /** |
||||||
233 | * The maximum number of retires after matching an invalid number. |
||||||
234 | * @var int |
||||||
235 | */ |
||||||
236 | protected $maxTries; |
||||||
237 | |||||||
238 | /** |
||||||
239 | * One of: |
||||||
240 | * - NOT_READY |
||||||
241 | * - READY |
||||||
242 | * - DONE |
||||||
243 | * @var string |
||||||
244 | */ |
||||||
245 | protected $state = 'NOT_READY'; |
||||||
246 | |||||||
247 | /** |
||||||
248 | * The last successful match, null unless $this->state = READY |
||||||
249 | * @var PhoneNumberMatch |
||||||
250 | */ |
||||||
251 | protected $lastMatch; |
||||||
252 | |||||||
253 | /** |
||||||
254 | * The next index to start searching at. Undefined when $this->state = DONE |
||||||
255 | * @var int |
||||||
256 | */ |
||||||
257 | protected $searchIndex = 0; |
||||||
258 | |||||||
259 | /** |
||||||
260 | * Creates a new instance. See the factory methods in PhoneNumberUtil on how to obtain a new instance. |
||||||
261 | * |
||||||
262 | * |
||||||
263 | * @param PhoneNumberUtil $util The Phone Number Util to use |
||||||
264 | * @param string|null $text The text that we will search, null for no text |
||||||
265 | * @param string|null $country The country to assume for phone numbers not written in international format. |
||||||
266 | * (with a leading plus, or with the international dialling prefix of the specified region). |
||||||
267 | * May be null, or "ZZ" if only numbers with a leading plus should be considered. |
||||||
268 | * @param AbstractLeniency $leniency The leniency to use when evaluating candidate phone numbers |
||||||
269 | * @param int $maxTries The maximum number of invalid numbers to try before giving up on the text. |
||||||
270 | * This is to cover degenerate cases where the text has a lot of false positives in it. Must be >= 0 |
||||||
271 | * @throws \InvalidArgumentException |
||||||
272 | */ |
||||||
273 | 207 | public function __construct(PhoneNumberUtil $util, $text, $country, AbstractLeniency $leniency, $maxTries) |
|||||
274 | { |
||||||
275 | 207 | if ($maxTries < 0) { |
|||||
276 | throw new \InvalidArgumentException(); |
||||||
277 | } |
||||||
278 | |||||||
279 | 207 | $this->phoneUtil = $util; |
|||||
280 | 207 | $this->text = ($text !== null) ? $text : ''; |
|||||
281 | 207 | $this->preferredRegion = $country; |
|||||
282 | 207 | $this->leniency = $leniency; |
|||||
283 | 207 | $this->maxTries = $maxTries; |
|||||
284 | |||||||
285 | 207 | if (static::$initialized === false) { |
|||||
286 | 1 | static::init(); |
|||||
287 | } |
||||||
288 | 207 | } |
|||||
289 | |||||||
290 | /** |
||||||
291 | * Attempts to find the next subsequence in the searched sequence on or after {@code searchIndex} |
||||||
292 | * that represents a phone number. Returns the next match, null if none was found. |
||||||
293 | * |
||||||
294 | * @param int $index The search index to start searching at |
||||||
295 | * @return PhoneNumberMatch|null The Phone Number Match found, null if none can be found |
||||||
296 | */ |
||||||
297 | 201 | protected function find($index) |
|||||
298 | { |
||||||
299 | 201 | $matcher = new Matcher(static::$pattern, $this->text); |
|||||
300 | 201 | while (($this->maxTries > 0) && $matcher->find($index)) { |
|||||
301 | 200 | $start = $matcher->start(); |
|||||
302 | 200 | $cutLength = $matcher->end() - $start; |
|||||
303 | 200 | $candidate = \mb_substr($this->text, $start, $cutLength); |
|||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||||
304 | |||||||
305 | // Check for extra numbers at the end. |
||||||
306 | // TODO: This is the place to start when trying to support extraction of multiple phone number |
||||||
307 | // from split notations (+41 49 123 45 67 / 68). |
||||||
308 | 200 | $candidate = static::trimAfterFirstMatch(PhoneNumberUtil::$SECOND_NUMBER_START_PATTERN, $candidate); |
|||||
309 | |||||||
310 | 200 | $match = $this->extractMatch($candidate, $start); |
|||||
0 ignored issues
–
show
Are you sure the assignment to
$match is correct as $this->extractMatch($candidate, $start) targeting libphonenumber\PhoneNumberMatcher::extractMatch() seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||||
311 | 200 | if ($match !== null) { |
|||||
312 | 126 | return $match; |
|||||
313 | } |
||||||
314 | |||||||
315 | 92 | $index = $start + \mb_strlen($candidate); |
|||||
316 | 92 | $this->maxTries--; |
|||||
317 | } |
||||||
318 | |||||||
319 | 95 | return null; |
|||||
320 | } |
||||||
321 | |||||||
322 | /** |
||||||
323 | * Trims away any characters after the first match of $pattern in $candidate, |
||||||
324 | * returning the trimmed version. |
||||||
325 | * |
||||||
326 | * @param string $pattern |
||||||
327 | * @param string $candidate |
||||||
328 | * @return string |
||||||
329 | */ |
||||||
330 | 200 | protected static function trimAfterFirstMatch($pattern, $candidate) |
|||||
331 | { |
||||||
332 | 200 | $trailingCharsMatcher = new Matcher($pattern, $candidate); |
|||||
333 | 200 | if ($trailingCharsMatcher->find()) { |
|||||
334 | 10 | $startChar = $trailingCharsMatcher->start(); |
|||||
335 | 10 | $candidate = \mb_substr($candidate, 0, $startChar); |
|||||
336 | } |
||||||
337 | 200 | return $candidate; |
|||||
338 | } |
||||||
339 | |||||||
340 | /** |
||||||
341 | * Helper method to determine if a character is a Latin-script letter or not. For our purposes, |
||||||
342 | * combining marks should also return true since we assume they have been added to a preceding |
||||||
343 | * Latin character. |
||||||
344 | * |
||||||
345 | * @param string $letter |
||||||
346 | * @return bool |
||||||
347 | * @internal |
||||||
348 | */ |
||||||
349 | 60 | public static function isLatinLetter($letter) |
|||||
350 | { |
||||||
351 | // Combining marks are a subset of non-spacing-mark. |
||||||
352 | 60 | if (\preg_match('/\p{L}/u', $letter) !== 1 && \preg_match('/\p{Mn}/u', $letter) !== 1) { |
|||||
353 | 54 | return false; |
|||||
354 | } |
||||||
355 | |||||||
356 | 9 | return (\preg_match('/\p{Latin}/u', $letter) === 1) |
|||||
357 | 9 | || (\preg_match('/\pM+/u', $letter) === 1); |
|||||
358 | } |
||||||
359 | |||||||
360 | /** |
||||||
361 | * @param string $character |
||||||
362 | * @return bool |
||||||
363 | */ |
||||||
364 | 49 | protected static function isInvalidPunctuationSymbol($character) |
|||||
365 | { |
||||||
366 | 49 | return $character == '%' || \preg_match('/\p{Sc}/u', $character); |
|||||
367 | } |
||||||
368 | |||||||
369 | /** |
||||||
370 | * Attempts to extract a match from a $candidate. |
||||||
371 | * |
||||||
372 | * @param string $candidate The candidate text that might contain a phone number |
||||||
373 | * @param int $offset The offset of $candidate within $this->text |
||||||
374 | * @return PhoneNumberMatch|null The match found, null if none can be found |
||||||
375 | */ |
||||||
376 | 200 | protected function extractMatch($candidate, $offset) |
|||||
377 | { |
||||||
378 | // Skip a match that is more likely to be a date. |
||||||
379 | 200 | $dateMatcher = new Matcher(static::$slashSeparatedDates, $candidate); |
|||||
380 | 200 | if ($dateMatcher->find()) { |
|||||
381 | 33 | return null; |
|||||
382 | } |
||||||
383 | |||||||
384 | // Skip potential time-stamps. |
||||||
385 | 180 | $timeStampMatcher = new Matcher(static::$timeStamps, $candidate); |
|||||
386 | 180 | if ($timeStampMatcher->find()) { |
|||||
387 | 20 | $followingText = \mb_substr($this->text, $offset + \mb_strlen($candidate)); |
|||||
388 | 20 | $timeStampSuffixMatcher = new Matcher(static::$timeStampsSuffix, $followingText); |
|||||
389 | 20 | if ($timeStampSuffixMatcher->lookingAt()) { |
|||||
390 | 16 | return null; |
|||||
391 | } |
||||||
392 | } |
||||||
393 | |||||||
394 | // Try to come up with a valid match given the entire candidate. |
||||||
395 | 180 | $match = $this->parseAndVerify($candidate, $offset); |
|||||
0 ignored issues
–
show
Are you sure the assignment to
$match is correct as $this->parseAndVerify($candidate, $offset) targeting libphonenumber\PhoneNumb...tcher::parseAndVerify() seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||||
396 | 180 | if ($match !== null) { |
|||||
0 ignored issues
–
show
|
|||||||
397 | 124 | return $match; |
|||||
398 | } |
||||||
399 | |||||||
400 | // If that failed, try to find an "inner match" - there might be a phone number within this |
||||||
401 | // candidate. |
||||||
402 | 76 | return $this->extractInnerMatch($candidate, $offset); |
|||||
0 ignored issues
–
show
Are you sure the usage of
$this->extractInnerMatch($candidate, $offset) targeting libphonenumber\PhoneNumb...er::extractInnerMatch() seems to always return null.
This check looks for function or method calls that always return null and whose return value is used. class A
{
function getObject()
{
return null;
}
}
$a = new A();
if ($a->getObject()) {
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||||
403 | } |
||||||
404 | |||||||
405 | /** |
||||||
406 | * Attempts to extract a match from $candidate if the whole candidate does not qualify as a |
||||||
407 | * match. |
||||||
408 | * |
||||||
409 | * @param string $candidate The candidate text that might contact a phone number |
||||||
410 | * @param int $offset The current offset of $candidate within $this->text |
||||||
411 | * @return PhoneNumberMatch|null The match found, null if none can be found |
||||||
412 | */ |
||||||
413 | 76 | protected function extractInnerMatch($candidate, $offset) |
|||||
414 | { |
||||||
415 | 76 | foreach (static::$innerMatches as $possibleInnerMatch) { |
|||||
416 | 76 | $groupMatcher = new Matcher($possibleInnerMatch, $candidate); |
|||||
417 | 76 | $isFirstMatch = true; |
|||||
418 | |||||||
419 | 76 | while ($groupMatcher->find() && $this->maxTries > 0) { |
|||||
420 | 20 | if ($isFirstMatch) { |
|||||
421 | // We should handle any group before this one too. |
||||||
422 | 20 | $group = static::trimAfterFirstMatch( |
|||||
423 | 20 | PhoneNumberUtil::$UNWANTED_END_CHAR_PATTERN, |
|||||
424 | 20 | \mb_substr($candidate, 0, $groupMatcher->start()) |
|||||
425 | ); |
||||||
426 | |||||||
427 | 20 | $match = $this->parseAndVerify($group, $offset); |
|||||
0 ignored issues
–
show
Are you sure the assignment to
$match is correct as $this->parseAndVerify($group, $offset) targeting libphonenumber\PhoneNumb...tcher::parseAndVerify() seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||||
428 | 20 | if ($match !== null) { |
|||||
429 | 6 | return $match; |
|||||
430 | } |
||||||
431 | 17 | $this->maxTries--; |
|||||
432 | 17 | $isFirstMatch = false; |
|||||
433 | } |
||||||
434 | 17 | $group = static::trimAfterFirstMatch( |
|||||
435 | 17 | PhoneNumberUtil::$UNWANTED_END_CHAR_PATTERN, |
|||||
436 | 17 | $groupMatcher->group(1) |
|||||
437 | ); |
||||||
438 | 17 | $match = $this->parseAndVerify($group, $offset + $groupMatcher->start(1)); |
|||||
0 ignored issues
–
show
Are you sure the assignment to
$match is correct as $this->parseAndVerify($g...groupMatcher->start(1)) targeting libphonenumber\PhoneNumb...tcher::parseAndVerify() seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||||
439 | 17 | if ($match !== null) { |
|||||
440 | 7 | return $match; |
|||||
441 | } |
||||||
442 | 16 | $this->maxTries--; |
|||||
443 | } |
||||||
444 | } |
||||||
445 | 72 | return null; |
|||||
446 | } |
||||||
447 | |||||||
448 | /** |
||||||
449 | * Parses a phone number from the $candidate} using PhoneNumberUtil::parse() and |
||||||
450 | * verifies it matches the requested leniency. If parsing and verification succeed, a |
||||||
451 | * corresponding PhoneNumberMatch is returned, otherwise this method returns null. |
||||||
452 | * |
||||||
453 | * @param string $candidate The candidate match |
||||||
454 | * @param int $offset The offset of $candidate within $this->text |
||||||
455 | * @return PhoneNumberMatch|null The parsed and validated phone number match, or null |
||||||
456 | */ |
||||||
457 | 180 | protected function parseAndVerify($candidate, $offset) |
|||||
458 | { |
||||||
459 | try { |
||||||
460 | // Check the candidate doesn't contain any formatting which would indicate that it really |
||||||
461 | // isn't a phone number |
||||||
462 | 180 | $matchingBracketsMatcher = new Matcher(static::$matchingBrackets, $candidate); |
|||||
463 | 180 | $pubPagesMatcher = new Matcher(static::$pubPages, $candidate); |
|||||
464 | 180 | if (!$matchingBracketsMatcher->matches() || $pubPagesMatcher->find()) { |
|||||
465 | 11 | return null; |
|||||
466 | } |
||||||
467 | |||||||
468 | // If leniency is set to VALID or stricter, we also want to skip numbers that are surrounded |
||||||
469 | // by Latin alphabetic characters, to skip cases like abc8005001234 or 8005001234def. |
||||||
470 | 180 | if ($this->leniency->compareTo(Leniency::VALID()) >= 0) { |
|||||
471 | // If the candidate is not at the start of the text, and does not start with phone-number |
||||||
472 | // punctuation, check the previous character. |
||||||
473 | 137 | $leadClassMatcher = new Matcher(static::$leadClass, $candidate); |
|||||
474 | 137 | if ($offset > 0 && !$leadClassMatcher->lookingAt()) { |
|||||
475 | 44 | $previousChar = \mb_substr($this->text, $offset - 1, 1); |
|||||
476 | // We return null if it is a latin letter or an invalid punctuation symbol. |
||||||
477 | 44 | if (static::isInvalidPunctuationSymbol($previousChar) || static::isLatinLetter($previousChar)) { |
|||||
478 | 2 | return null; |
|||||
479 | } |
||||||
480 | } |
||||||
481 | 137 | $lastCharIndex = $offset + \mb_strlen($candidate); |
|||||
482 | 137 | if ($lastCharIndex < \mb_strlen($this->text)) { |
|||||
483 | 40 | $nextChar = \mb_substr($this->text, $lastCharIndex, 1); |
|||||
484 | 40 | if (static::isInvalidPunctuationSymbol($nextChar) || static::isLatinLetter($nextChar)) { |
|||||
485 | 2 | return null; |
|||||
486 | } |
||||||
487 | } |
||||||
488 | } |
||||||
489 | |||||||
490 | 179 | $number = $this->phoneUtil->parseAndKeepRawInput($candidate, $this->preferredRegion); |
|||||
491 | |||||||
492 | 178 | if ($this->leniency->verify($number, $candidate, $this->phoneUtil)) { |
|||||
493 | // We used parseAndKeepRawInput to create this number, but for now we don't return the extra |
||||||
494 | // values parsed. TODO: stop clearing all values here and switch all users over |
||||||
495 | // to using rawInput() rather than the rawString() of PhoneNumberMatch |
||||||
496 | 126 | $number->clearCountryCodeSource(); |
|||||
497 | 126 | $number->clearRawInput(); |
|||||
498 | 126 | $number->clearPreferredDomesticCarrierCode(); |
|||||
499 | 178 | return new PhoneNumberMatch($offset, $candidate, $number); |
|||||
500 | } |
||||||
501 | 28 | } catch (NumberParseException $e) { |
|||||
502 | // ignore and continue |
||||||
503 | } |
||||||
504 | 74 | return null; |
|||||
505 | } |
||||||
506 | |||||||
507 | /** |
||||||
508 | * @param PhoneNumberUtil $util |
||||||
509 | * @param PhoneNumber $number |
||||||
510 | * @param string $normalizedCandidate |
||||||
511 | * @param string[] $formattedNumberGroups |
||||||
512 | * @return bool |
||||||
513 | */ |
||||||
514 | 27 | public static function allNumberGroupsRemainGrouped( |
|||||
515 | PhoneNumberUtil $util, |
||||||
516 | PhoneNumber $number, |
||||||
517 | $normalizedCandidate, |
||||||
518 | $formattedNumberGroups |
||||||
519 | ) { |
||||||
520 | 27 | $fromIndex = 0; |
|||||
521 | 27 | if ($number->getCountryCodeSource() !== CountryCodeSource::FROM_DEFAULT_COUNTRY) { |
|||||
522 | // First skip the country code if the normalized candidate contained it. |
||||||
523 | 11 | $countryCode = $number->getCountryCode(); |
|||||
524 | 11 | $fromIndex = \mb_strpos($normalizedCandidate, $countryCode) + \mb_strlen($countryCode); |
|||||
0 ignored issues
–
show
It seems like
$countryCode can also be of type null ; however, parameter $needle of mb_strpos() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() It seems like
$countryCode can also be of type null ; however, parameter $string of mb_strlen() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
525 | } |
||||||
526 | |||||||
527 | // Check each group of consecutive digits are not broken into separate groupings in the |
||||||
528 | // $normalizedCandidate string. |
||||||
529 | 27 | $formattedNumberGroupsLength = \count($formattedNumberGroups); |
|||||
530 | 27 | for ($i = 0; $i < $formattedNumberGroupsLength; $i++) { |
|||||
531 | // Fails if the substring of $normalizedCandidate starting from $fromIndex |
||||||
532 | // doesn't contain the consecutive digits in $formattedNumberGroups[$i]. |
||||||
533 | 27 | $fromIndex = \mb_strpos($normalizedCandidate, $formattedNumberGroups[$i], $fromIndex); |
|||||
534 | 27 | if ($fromIndex === false) { |
|||||
535 | 9 | return false; |
|||||
536 | } |
||||||
537 | |||||||
538 | // Moves $fromIndex forward. |
||||||
539 | 26 | $fromIndex += \mb_strlen($formattedNumberGroups[$i]); |
|||||
540 | 26 | if ($i === 0 && $fromIndex < \mb_strlen($normalizedCandidate)) { |
|||||
541 | // We are at the position right after the NDC. We get the region used for formatting |
||||||
542 | // information based on the country code in the phone number, rather than the number itself, |
||||||
543 | // as we do not need to distinguish between different countries with the same country |
||||||
544 | // calling code and this is faster. |
||||||
545 | 26 | $region = $util->getRegionCodeForCountryCode($number->getCountryCode()); |
|||||
546 | |||||||
547 | 26 | if ($util->getNddPrefixForRegion($region, true) !== null |
|||||
548 | 26 | && \is_int(\mb_substr($normalizedCandidate, $fromIndex, 1)) |
|||||
549 | ) { |
||||||
550 | // This means there is no formatting symbol after the NDC. In this case, we only |
||||||
551 | // accept the number if there is no formatting symbol at all in the number, except |
||||||
552 | // for extensions. This is only important for countries with national prefixes. |
||||||
553 | $nationalSignificantNumber = $util->getNationalSignificantNumber($number); |
||||||
554 | return \mb_substr( |
||||||
555 | \mb_substr($normalizedCandidate, $fromIndex - \mb_strlen($formattedNumberGroups[$i])), |
||||||
556 | \mb_strlen($nationalSignificantNumber) |
||||||
557 | ) === $nationalSignificantNumber; |
||||||
558 | } |
||||||
559 | } |
||||||
560 | } |
||||||
561 | // The check here makes sure that we haven't mistakenly already used the extension to |
||||||
562 | // match the last group of the subscriber number. Note the extension cannot have |
||||||
563 | // formatting in-between digits |
||||||
564 | |||||||
565 | 25 | if ($number->hasExtension()) { |
|||||
566 | 4 | return \mb_strpos(\mb_substr($normalizedCandidate, $fromIndex), $number->getExtension()) !== false; |
|||||
567 | } |
||||||
568 | |||||||
569 | 21 | return true; |
|||||
570 | } |
||||||
571 | |||||||
572 | /** |
||||||
573 | * @param PhoneNumberUtil $util |
||||||
574 | * @param PhoneNumber $number |
||||||
575 | * @param string $normalizedCandidate |
||||||
576 | * @param string[] $formattedNumberGroups |
||||||
577 | * @return bool |
||||||
578 | */ |
||||||
579 | 27 | public static function allNumberGroupsAreExactlyPresent( |
|||||
580 | PhoneNumberUtil $util, |
||||||
581 | PhoneNumber $number, |
||||||
582 | $normalizedCandidate, |
||||||
583 | $formattedNumberGroups |
||||||
584 | ) { |
||||||
585 | 27 | $candidateGroups = \preg_split(PhoneNumberUtil::NON_DIGITS_PATTERN, $normalizedCandidate); |
|||||
586 | |||||||
587 | // Set this to the last group, skipping it if the number has an extension. |
||||||
588 | 27 | $candidateNumberGroupIndex = $number->hasExtension() ? \count($candidateGroups) - 2 : \count($candidateGroups) - 1; |
|||||
589 | |||||||
590 | // First we check if the national significant number is formatted as a block. |
||||||
591 | // We use contains and not equals, since the national significant number may be present with |
||||||
592 | // a prefix such as a national number prefix, or the country code itself. |
||||||
593 | 27 | if (\count($candidateGroups) == 1 |
|||||
594 | 24 | || \mb_strpos( |
|||||
595 | 24 | $candidateGroups[$candidateNumberGroupIndex], |
|||||
596 | 24 | $util->getNationalSignificantNumber($number) |
|||||
597 | 27 | ) !== false |
|||||
598 | ) { |
||||||
599 | 8 | return true; |
|||||
600 | } |
||||||
601 | |||||||
602 | // Starting from the end, go through in reverse, excluding the first group, and check the |
||||||
603 | // candidate and number groups are the same. |
||||||
604 | 19 | for ($formattedNumberGroupIndex = (\count($formattedNumberGroups) - 1); |
|||||
605 | 19 | $formattedNumberGroupIndex > 0 && $candidateNumberGroupIndex >= 0; |
|||||
606 | $formattedNumberGroupIndex--, $candidateNumberGroupIndex--) { |
||||||
607 | 19 | if ($candidateGroups[$candidateNumberGroupIndex] != $formattedNumberGroups[$formattedNumberGroupIndex]) { |
|||||
608 | 6 | return false; |
|||||
609 | } |
||||||
610 | } |
||||||
611 | |||||||
612 | // Now check the first group. There may be a national prefix at the start, so we only check |
||||||
613 | // that the candidate group ends with the formatted number group. |
||||||
614 | 18 | return ($candidateNumberGroupIndex >= 0 |
|||||
615 | 18 | && \mb_substr( |
|||||
616 | 18 | $candidateGroups[$candidateNumberGroupIndex], |
|||||
617 | 18 | -\mb_strlen($formattedNumberGroups[0]) |
|||||
618 | 18 | ) == $formattedNumberGroups[0]); |
|||||
619 | } |
||||||
620 | |||||||
621 | /** |
||||||
622 | * Helper method to get the national-number part of a number, formatted without any national |
||||||
623 | * prefix, and return it as a set of digit blocks that would be formatted together. |
||||||
624 | * |
||||||
625 | * @param PhoneNumberUtil $util |
||||||
626 | * @param PhoneNumber $number |
||||||
627 | * @param NumberFormat $formattingPattern |
||||||
628 | * @return string[] |
||||||
629 | */ |
||||||
630 | 54 | protected static function getNationalNumberGroups( |
|||||
631 | PhoneNumberUtil $util, |
||||||
632 | PhoneNumber $number, |
||||||
633 | NumberFormat $formattingPattern = null |
||||||
634 | ) { |
||||||
635 | 54 | if ($formattingPattern === null) { |
|||||
636 | // This will be in the format +CC-DG;ext=EXT where DG represents groups of digits. |
||||||
637 | 54 | $rfc3966Format = $util->format($number, PhoneNumberFormat::RFC3966); |
|||||
638 | // We remove the extension part from the formatted string before splitting it into different |
||||||
639 | // groups. |
||||||
640 | 54 | $endIndex = \mb_strpos($rfc3966Format, ';'); |
|||||
641 | 54 | if ($endIndex === false) { |
|||||
642 | 44 | $endIndex = \mb_strlen($rfc3966Format); |
|||||
643 | } |
||||||
644 | |||||||
645 | // The country-code will have a '-' following it. |
||||||
646 | 54 | $startIndex = \mb_strpos($rfc3966Format, '-') + 1; |
|||||
647 | 54 | return \explode('-', \mb_substr($rfc3966Format, $startIndex, $endIndex - $startIndex)); |
|||||
648 | } |
||||||
649 | |||||||
650 | // If a format is provided, we format the NSN only, and split that according to the separator. |
||||||
651 | 15 | $nationalSignificantNumber = $util->getNationalSignificantNumber($number); |
|||||
652 | 15 | return \explode('-', $util->formatNsnUsingPattern( |
|||||
653 | 15 | $nationalSignificantNumber, |
|||||
654 | $formattingPattern, |
||||||
655 | 15 | PhoneNumberFormat::RFC3966 |
|||||
656 | )); |
||||||
657 | } |
||||||
658 | |||||||
659 | /** |
||||||
660 | * @param PhoneNumber $number |
||||||
661 | * @param string $candidate |
||||||
662 | * @param PhoneNumberUtil $util |
||||||
663 | * @param \Closure $checker |
||||||
664 | * @return bool |
||||||
665 | */ |
||||||
666 | 54 | public static function checkNumberGroupingIsValid( |
|||||
667 | PhoneNumber $number, |
||||||
668 | $candidate, |
||||||
669 | PhoneNumberUtil $util, |
||||||
670 | \Closure $checker |
||||||
671 | ) { |
||||||
672 | 54 | $normalizedCandidate = PhoneNumberUtil::normalizeDigits($candidate, true /* keep non-digits */); |
|||||
673 | 54 | $formattedNumberGroups = static::getNationalNumberGroups($util, $number); |
|||||
674 | 54 | if ($checker($util, $number, $normalizedCandidate, $formattedNumberGroups)) { |
|||||
675 | 39 | return true; |
|||||
676 | } |
||||||
677 | |||||||
678 | // If this didn't pass, see if there are any alternative formats that match, and try them instead. |
||||||
679 | 15 | $alternateFormats = static::getAlternateFormatsForCountry($number->getCountryCode()); |
|||||
680 | |||||||
681 | 15 | $nationalSignificantNumber = $util->getNationalSignificantNumber($number); |
|||||
682 | 15 | if ($alternateFormats !== null) { |
|||||
683 | 15 | foreach ($alternateFormats->numberFormats() as $alternateFormat) { |
|||||
684 | 15 | if ($alternateFormat->leadingDigitsPatternSize() > 0) { |
|||||
685 | // There is only one leading digits pattern for alternate formats. |
||||||
686 | 13 | $pattern = $alternateFormat->getLeadingDigitsPattern(0); |
|||||
687 | |||||||
688 | 13 | $nationalSignificantNumberMatcher = new Matcher($pattern, $nationalSignificantNumber); |
|||||
689 | 13 | if (!$nationalSignificantNumberMatcher->lookingAt()) { |
|||||
690 | // Leading digits don't match; try another one. |
||||||
691 | 13 | continue; |
|||||
692 | } |
||||||
693 | } |
||||||
694 | |||||||
695 | 15 | $formattedNumberGroups = static::getNationalNumberGroups($util, $number, $alternateFormat); |
|||||
696 | 15 | if ($checker($util, $number, $normalizedCandidate, $formattedNumberGroups)) { |
|||||
697 | 11 | return true; |
|||||
698 | } |
||||||
699 | } |
||||||
700 | } |
||||||
701 | 4 | return false; |
|||||
702 | } |
||||||
703 | |||||||
704 | /** |
||||||
705 | * @param PhoneNumber $number |
||||||
706 | * @param string $candidate |
||||||
707 | * @return bool |
||||||
708 | */ |
||||||
709 | 55 | public static function containsMoreThanOneSlashInNationalNumber(PhoneNumber $number, $candidate) |
|||||
710 | { |
||||||
711 | 55 | $firstSlashInBodyIndex = \mb_strpos($candidate, '/'); |
|||||
712 | 55 | if ($firstSlashInBodyIndex === false) { |
|||||
713 | // No slashes, this is okay |
||||||
714 | 53 | return false; |
|||||
715 | } |
||||||
716 | |||||||
717 | // Now look for a second one. |
||||||
718 | 2 | $secondSlashInBodyIndex = \mb_strpos($candidate, '/', $firstSlashInBodyIndex + 1); |
|||||
719 | 2 | if ($secondSlashInBodyIndex === false) { |
|||||
720 | // Only one slash, this is okay |
||||||
721 | 1 | return false; |
|||||
722 | } |
||||||
723 | |||||||
724 | // If the first slash is after the country calling code, this is permitted |
||||||
725 | 1 | $candidateHasCountryCode = ($number->getCountryCodeSource() === CountryCodeSource::FROM_NUMBER_WITH_PLUS_SIGN |
|||||
726 | 1 | || $number->getCountryCodeSource() === CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN); |
|||||
727 | |||||||
728 | 1 | if ($candidateHasCountryCode |
|||||
729 | 1 | && PhoneNumberUtil::normalizeDigitsOnly( |
|||||
730 | 1 | \mb_substr($candidate, 0, $firstSlashInBodyIndex) |
|||||
731 | 1 | ) == $number->getCountryCode() |
|||||
732 | ) { |
||||||
733 | // Any more slashes and this is illegal |
||||||
734 | 1 | return (\mb_strpos(\mb_substr($candidate, $secondSlashInBodyIndex + 1), '/') !== false); |
|||||
735 | } |
||||||
736 | |||||||
737 | 1 | return true; |
|||||
738 | } |
||||||
739 | |||||||
740 | /** |
||||||
741 | * @param PhoneNumber $number |
||||||
742 | * @param string $candidate |
||||||
743 | * @param PhoneNumberUtil $util |
||||||
744 | * @return bool |
||||||
745 | */ |
||||||
746 | 99 | public static function containsOnlyValidXChars(PhoneNumber $number, $candidate, PhoneNumberUtil $util) |
|||||
747 | { |
||||||
748 | // The characters 'x' and 'X' can be (1) a carrier code, in which case they always precede the |
||||||
749 | // national significant number or (2) an extension sign, in which case they always precede the |
||||||
750 | // extension number. We assume a carrier code is more than 1 digit, so the first case has to |
||||||
751 | // have more than 1 consecutive 'x' or 'X', whereas the second case can only have exactly 1 'x' |
||||||
752 | // or 'X'. We ignore the character if it appears as the last character of the string. |
||||||
753 | 99 | $candidateLength = \mb_strlen($candidate); |
|||||
754 | |||||||
755 | 99 | for ($index = 0; $index < $candidateLength - 1; $index++) { |
|||||
756 | 99 | $charAtIndex = \mb_substr($candidate, $index, 1); |
|||||
757 | 99 | if ($charAtIndex == 'x' || $charAtIndex == 'X') { |
|||||
758 | 15 | $charAtNextIndex = \mb_substr($candidate, $index + 1, 1); |
|||||
759 | 15 | if ($charAtNextIndex == 'x' || $charAtNextIndex == 'X') { |
|||||
760 | // This is the carrier code case, in which the 'X's always precede the national |
||||||
761 | // significant number. |
||||||
762 | $index++; |
||||||
763 | |||||||
764 | if ($util->isNumberMatch($number, \mb_substr($candidate, $index)) != MatchType::NSN_MATCH) { |
||||||
765 | return false; |
||||||
766 | } |
||||||
767 | 15 | } elseif (!PhoneNumberUtil::normalizeDigitsOnly(\mb_substr( |
|||||
768 | 15 | $candidate, |
|||||
769 | 15 | $index |
|||||
770 | 15 | )) == $number->getExtension() |
|||||
771 | ) { |
||||||
772 | // This is the extension sign case, in which the 'x' or 'X' should always precede the |
||||||
773 | // extension number |
||||||
774 | return false; |
||||||
775 | } |
||||||
776 | } |
||||||
777 | } |
||||||
778 | 99 | return true; |
|||||
779 | } |
||||||
780 | |||||||
781 | /** |
||||||
782 | * @param PhoneNumber $number |
||||||
783 | * @param PhoneNumberUtil $util |
||||||
784 | * @return bool |
||||||
785 | */ |
||||||
786 | 99 | public static function isNationalPrefixPresentIfRequired(PhoneNumber $number, PhoneNumberUtil $util) |
|||||
787 | { |
||||||
788 | // First, check how we deduced the country code. If it was written in international format, then |
||||||
789 | // the national prefix is not required. |
||||||
790 | 99 | if ($number->getCountryCodeSource() !== CountryCodeSource::FROM_DEFAULT_COUNTRY) { |
|||||
791 | 41 | return true; |
|||||
792 | } |
||||||
793 | |||||||
794 | 65 | $phoneNumberRegion = $util->getRegionCodeForCountryCode($number->getCountryCode()); |
|||||
795 | 65 | $metadata = $util->getMetadataForRegion($phoneNumberRegion); |
|||||
796 | 65 | if ($metadata === null) { |
|||||
797 | return true; |
||||||
798 | } |
||||||
799 | |||||||
800 | // Check if a national prefix should be present when formatting this number. |
||||||
801 | 65 | $nationalNumber = $util->getNationalSignificantNumber($number); |
|||||
802 | 65 | $formatRule = $util->chooseFormattingPatternForNumber($metadata->numberFormats(), $nationalNumber); |
|||||
803 | // To do this, we check that a national prefix formatting rule was present and that it wasn't |
||||||
804 | // just the first-group symbol ($1) with punctuation. |
||||||
805 | 65 | if (($formatRule !== null) && $formatRule->getNationalPrefixFormattingRule() !== '') { |
|||||
806 | 44 | if ($formatRule->getNationalPrefixOptionalWhenFormatting()) { |
|||||
807 | // The national-prefix is optional in these cases, so we don't need to check if it was |
||||||
808 | // present. |
||||||
809 | 7 | return true; |
|||||
810 | } |
||||||
811 | |||||||
812 | 37 | if (PhoneNumberUtil::formattingRuleHasFirstGroupOnly($formatRule->getNationalPrefixFormattingRule())) { |
|||||
813 | // National Prefix not needed for this number. |
||||||
814 | 3 | return true; |
|||||
815 | } |
||||||
816 | |||||||
817 | // Normalize the remainder. |
||||||
818 | 34 | $rawInputCopy = PhoneNumberUtil::normalizeDigitsOnly($number->getRawInput()); |
|||||
819 | 34 | $rawInput = $rawInputCopy; |
|||||
820 | // Check if we found a national prefix and/or carrier code at the start of the raw input, and |
||||||
821 | // return the result. |
||||||
822 | 34 | $carrierCode = null; |
|||||
823 | 34 | return $util->maybeStripNationalPrefixAndCarrierCode($rawInput, $metadata, $carrierCode); |
|||||
824 | } |
||||||
825 | 25 | return true; |
|||||
826 | } |
||||||
827 | |||||||
828 | |||||||
829 | /** |
||||||
830 | * Storage for Alternate Formats |
||||||
831 | * @var PhoneMetadata[] |
||||||
832 | */ |
||||||
833 | protected static $callingCodeToAlternateFormatsMap = array(); |
||||||
834 | |||||||
835 | /** |
||||||
836 | * @param $countryCallingCode |
||||||
837 | * @return PhoneMetadata|null |
||||||
838 | */ |
||||||
839 | 15 | protected static function getAlternateFormatsForCountry($countryCallingCode) |
|||||
840 | { |
||||||
841 | 15 | $countryCodeSet = AlternateFormatsCountryCodeSet::$alternateFormatsCountryCodeSet; |
|||||
842 | |||||||
843 | 15 | if (!\in_array($countryCallingCode, $countryCodeSet)) { |
|||||
844 | return null; |
||||||
845 | } |
||||||
846 | |||||||
847 | 15 | if (!isset(static::$callingCodeToAlternateFormatsMap[$countryCallingCode])) { |
|||||
848 | 3 | static::loadAlternateFormatsMetadataFromFile($countryCallingCode); |
|||||
849 | } |
||||||
850 | |||||||
851 | 15 | return static::$callingCodeToAlternateFormatsMap[$countryCallingCode]; |
|||||
852 | } |
||||||
853 | |||||||
854 | /** |
||||||
855 | * @param string $countryCallingCode |
||||||
856 | * @throws \Exception |
||||||
857 | */ |
||||||
858 | 3 | protected static function loadAlternateFormatsMetadataFromFile($countryCallingCode) |
|||||
859 | { |
||||||
860 | 3 | $fileName = static::$alternateFormatsFilePrefix . '_' . $countryCallingCode . '.php'; |
|||||
861 | |||||||
862 | 3 | if (!\is_readable($fileName)) { |
|||||
863 | throw new \Exception('missing metadata: ' . $fileName); |
||||||
864 | } |
||||||
865 | |||||||
866 | 3 | $metadataLoader = new DefaultMetadataLoader(); |
|||||
867 | 3 | $data = $metadataLoader->loadMetadata($fileName); |
|||||
868 | 3 | $metadata = new PhoneMetadata(); |
|||||
869 | 3 | $metadata->fromArray($data); |
|||||
870 | 3 | static::$callingCodeToAlternateFormatsMap[$countryCallingCode] = $metadata; |
|||||
871 | 3 | } |
|||||
872 | |||||||
873 | |||||||
874 | /** |
||||||
875 | * Return the current element |
||||||
876 | * @link http://php.net/manual/en/iterator.current.php |
||||||
877 | * @return PhoneNumberMatch|null |
||||||
878 | */ |
||||||
879 | #[\ReturnTypeWillChange] |
||||||
880 | 199 | public function current() |
|||||
881 | { |
||||||
882 | 199 | return $this->lastMatch; |
|||||
883 | } |
||||||
884 | |||||||
885 | /** |
||||||
886 | * Move forward to next element |
||||||
887 | * @link http://php.net/manual/en/iterator.next.php |
||||||
888 | * @return void Any returned value is ignored. |
||||||
889 | */ |
||||||
890 | #[\ReturnTypeWillChange] |
||||||
891 | 201 | public function next() |
|||||
892 | { |
||||||
893 | 201 | $this->lastMatch = $this->find($this->searchIndex); |
|||||
0 ignored issues
–
show
Are you sure the assignment to
$this->lastMatch is correct as $this->find($this->searchIndex) targeting libphonenumber\PhoneNumberMatcher::find() seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||||
894 | |||||||
895 | 201 | if ($this->lastMatch === null) { |
|||||
896 | 95 | $this->state = 'DONE'; |
|||||
897 | } else { |
||||||
898 | 126 | $this->searchIndex = $this->lastMatch->end(); |
|||||
0 ignored issues
–
show
The method
end() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
899 | 126 | $this->state = 'READY'; |
|||||
900 | } |
||||||
901 | |||||||
902 | 201 | $this->searchIndex++; |
|||||
903 | 201 | } |
|||||
904 | |||||||
905 | /** |
||||||
906 | * Return the key of the current element |
||||||
907 | * @link http://php.net/manual/en/iterator.key.php |
||||||
908 | * @return mixed scalar on success, or null on failure. |
||||||
909 | * @since 5.0.0 |
||||||
910 | */ |
||||||
911 | #[\ReturnTypeWillChange] |
||||||
912 | public function key() |
||||||
913 | { |
||||||
914 | return $this->searchIndex; |
||||||
915 | } |
||||||
916 | |||||||
917 | /** |
||||||
918 | * Checks if current position is valid |
||||||
919 | * @link http://php.net/manual/en/iterator.valid.php |
||||||
920 | * @return boolean The return value will be casted to boolean and then evaluated. |
||||||
921 | * Returns true on success or false on failure. |
||||||
922 | * @since 5.0.0 |
||||||
923 | */ |
||||||
924 | #[\ReturnTypeWillChange] |
||||||
925 | 29 | public function valid() |
|||||
926 | { |
||||||
927 | 29 | return $this->state === 'READY'; |
|||||
928 | } |
||||||
929 | |||||||
930 | /** |
||||||
931 | * Rewind the Iterator to the first element |
||||||
932 | * @link http://php.net/manual/en/iterator.rewind.php |
||||||
933 | * @return void Any returned value is ignored. |
||||||
934 | * @since 5.0.0 |
||||||
935 | */ |
||||||
936 | #[\ReturnTypeWillChange] |
||||||
937 | 18 | public function rewind() |
|||||
938 | { |
||||||
939 | 18 | $this->searchIndex = 0; |
|||||
940 | 18 | $this->next(); |
|||||
941 | 18 | } |
|||||
942 | } |
||||||
943 |