| Total Complexity | 105 | 
| Total Lines | 910 | 
| Duplicated Lines | 0 % | 
| Changes | 0 | ||
Complex classes like PhoneNumberMatcher 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.
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 PhoneNumberMatcher, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 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 | protected static function init()  | 
            ||
| 105 |     { | 
            ||
| 106 | static::$alternateFormatsFilePrefix = dirname(__FILE__) . '/data/' . static::META_DATA_FILE_PREFIX;  | 
            ||
| 107 | |||
| 108 | 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 | $openingParens = "(\\[\xEF\xBC\x88\xEF\xBC\xBB";  | 
            ||
| 133 | $closingParens = ")\\]\xEF\xBC\x89\xEF\xBC\xBD";  | 
            ||
| 134 | $nonParens = '[^' . $openingParens . $closingParens . ']';  | 
            ||
| 135 | |||
| 136 | // Limit on the number of pairs of brackets in a phone number.  | 
            ||
| 137 | $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 | static::$matchingBrackets =  | 
            ||
| 145 | '(?:[' . $openingParens . '])?' . '(?:' . $nonParens . '+' . '[' . $closingParens . '])?'  | 
            ||
| 146 | . $nonParens . '+'  | 
            ||
| 147 | . '(?:[' . $openingParens . ']' . $nonParens . '+[' . $closingParens . '])' . $bracketPairLimit  | 
            ||
| 148 | . $nonParens . '*';  | 
            ||
| 149 | |||
| 150 | // Limit on the number of leading (plus) characters.  | 
            ||
| 151 | $leadLimit = static::limit(0, 2);  | 
            ||
| 152 | |||
| 153 | // Limit on the number of consecutive punctuation characters.  | 
            ||
| 154 | $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 | $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 | $blockLimit = static::limit(0, $digitBlockLimit);  | 
            ||
| 168 | |||
| 169 | // A punctuation sequence allowing white space  | 
            ||
| 170 | $punctuation = '[' . PhoneNumberUtil::VALID_PUNCTUATION . ']' . $punctuationLimit;  | 
            ||
| 171 | |||
| 172 | // A digits block without punctuation.  | 
            ||
| 173 |         $digitSequence = "\\p{Nd}" . static::limit(1, $digitBlockLimit); | 
            ||
| 174 | |||
| 175 | |||
| 176 | $leadClassChars = $openingParens . PhoneNumberUtil::PLUS_CHARS;  | 
            ||
| 177 | $leadClass = '[' . $leadClassChars . ']';  | 
            ||
| 178 | static::$leadClass = $leadClass;  | 
            ||
| 179 | |||
| 180 | // Init extension patterns from PhoneNumberUtil  | 
            ||
| 181 | PhoneNumberUtil::initCapturingExtnDigits();  | 
            ||
| 182 | PhoneNumberUtil::initExtnPatterns();  | 
            ||
| 183 | |||
| 184 | |||
| 185 | // Phone number pattern allowing optional punctuation.  | 
            ||
| 186 | static::$pattern = '(?:' . $leadClass . $punctuation . ')' . $leadLimit  | 
            ||
| 187 | . $digitSequence . '(?:' . $punctuation . $digitSequence . ')' . $blockLimit  | 
            ||
| 188 | . '(?:' . PhoneNumberUtil::$EXTN_PATTERNS_FOR_MATCHING . ')?';  | 
            ||
| 189 | |||
| 190 | static::$initialized = true;  | 
            ||
| 191 | }  | 
            ||
| 192 | |||
| 193 | /**  | 
            ||
| 194 | * Helper function to generate regular expression with an upper and lower limit.  | 
            ||
| 195 | *  | 
            ||
| 196 | * @param int $lower  | 
            ||
| 197 | * @param int $upper  | 
            ||
| 198 | * @return string  | 
            ||
| 199 | */  | 
            ||
| 200 | protected static function limit($lower, $upper)  | 
            ||
| 201 |     { | 
            ||
| 202 |         if (($lower < 0) || ($upper <= 0) || ($upper < $lower)) { | 
            ||
| 203 | throw new \InvalidArgumentException();  | 
            ||
| 204 | }  | 
            ||
| 205 | |||
| 206 |         return '{' . $lower . ',' . $upper . '}'; | 
            ||
| 207 | }  | 
            ||
| 208 | |||
| 209 | /**  | 
            ||
| 210 | * The phone number utility.  | 
            ||
| 211 | * @var PhoneNumberUtil  | 
            ||
| 212 | */  | 
            ||
| 213 | protected $phoneUtil;  | 
            ||
| 214 | |||
| 215 | /**  | 
            ||
| 216 | * The text searched for phone numbers.  | 
            ||
| 217 | * @var string  | 
            ||
| 218 | */  | 
            ||
| 219 | protected $text;  | 
            ||
| 220 | |||
| 221 | /**  | 
            ||
| 222 | * The region (country) to assume for phone numbers without an international prefix, possibly  | 
            ||
| 223 | * null.  | 
            ||
| 224 | * @var string  | 
            ||
| 225 | */  | 
            ||
| 226 | protected $preferredRegion;  | 
            ||
| 227 | |||
| 228 | /**  | 
            ||
| 229 | * The degrees of validation requested.  | 
            ||
| 230 | * @var AbstractLeniency  | 
            ||
| 231 | */  | 
            ||
| 232 | protected $leniency;  | 
            ||
| 233 | |||
| 234 | /**  | 
            ||
| 235 | * The maximum number of retires after matching an invalid number.  | 
            ||
| 236 | * @var int  | 
            ||
| 237 | */  | 
            ||
| 238 | protected $maxTries;  | 
            ||
| 239 | |||
| 240 | /**  | 
            ||
| 241 | * One of:  | 
            ||
| 242 | * - NOT_READY  | 
            ||
| 243 | * - READY  | 
            ||
| 244 | * - DONE  | 
            ||
| 245 | * @var string  | 
            ||
| 246 | */  | 
            ||
| 247 | protected $state = 'NOT_READY';  | 
            ||
| 248 | |||
| 249 | /**  | 
            ||
| 250 | * The last successful match, null unless $this->state = READY  | 
            ||
| 251 | * @var PhoneNumberMatch  | 
            ||
| 252 | */  | 
            ||
| 253 | protected $lastMatch;  | 
            ||
| 254 | |||
| 255 | /**  | 
            ||
| 256 | * The next index to start searching at. Undefined when $this->state = DONE  | 
            ||
| 257 | * @var int  | 
            ||
| 258 | */  | 
            ||
| 259 | protected $searchIndex = 0;  | 
            ||
| 260 | |||
| 261 | /**  | 
            ||
| 262 | * Creates a new instance. See the factory methods in PhoneNumberUtil on how to obtain a new instance.  | 
            ||
| 263 | *  | 
            ||
| 264 | *  | 
            ||
| 265 | * @param PhoneNumberUtil $util The Phone Number Util to use  | 
            ||
| 266 | * @param string|null $text The text that we will search, null for no text  | 
            ||
| 267 | * @param string|null $country The country to assume for phone numbers not written in international format.  | 
            ||
| 268 | * (with a leading plus, or with the international dialling prefix of the specified region).  | 
            ||
| 269 | * May be null, or "ZZ" if only numbers with a leading plus should be considered.  | 
            ||
| 270 | * @param AbstractLeniency $leniency The leniency to use when evaluating candidate phone numbers  | 
            ||
| 271 | * @param int $maxTries The maximum number of invalid numbers to try before giving up on the text.  | 
            ||
| 272 | * This is to cover degenerate cases where the text has a lot of false positives in it. Must be >= 0  | 
            ||
| 273 | * @throws \NullPointerException  | 
            ||
| 274 | * @throws \InvalidArgumentException  | 
            ||
| 275 | */  | 
            ||
| 276 | public function __construct(PhoneNumberUtil $util, $text, $country, AbstractLeniency $leniency, $maxTries)  | 
            ||
| 277 |     { | 
            ||
| 278 |         if ($maxTries < 0) { | 
            ||
| 279 | throw new \InvalidArgumentException();  | 
            ||
| 280 | }  | 
            ||
| 281 | |||
| 282 | $this->phoneUtil = $util;  | 
            ||
| 283 | $this->text = ($text !== null) ? $text : '';  | 
            ||
| 284 | $this->preferredRegion = $country;  | 
            ||
| 285 | $this->leniency = $leniency;  | 
            ||
| 286 | $this->maxTries = $maxTries;  | 
            ||
| 287 | |||
| 288 |         if (static::$initialized === false) { | 
            ||
| 289 | static::init();  | 
            ||
| 290 | }  | 
            ||
| 291 | }  | 
            ||
| 292 | |||
| 293 | /**  | 
            ||
| 294 |      * Attempts to find the next subsequence in the searched sequence on or after {@code searchIndex} | 
            ||
| 295 | * that represents a phone number. Returns the next match, null if none was found.  | 
            ||
| 296 | *  | 
            ||
| 297 | * @param int $index The search index to start searching at  | 
            ||
| 298 | * @return PhoneNumberMatch|null The Phone Number Match found, null if none can be found  | 
            ||
| 299 | */  | 
            ||
| 300 | protected function find($index)  | 
            ||
| 301 |     { | 
            ||
| 302 | $matcher = new Matcher(static::$pattern, $this->text);  | 
            ||
| 303 |         while (($this->maxTries > 0) && $matcher->find($index)) { | 
            ||
| 304 | $start = $matcher->start();  | 
            ||
| 305 | $cutLength = $matcher->end() - $start;  | 
            ||
| 306 | $candidate = mb_substr($this->text, $start, $cutLength);  | 
            ||
| 307 | |||
| 308 | // Check for extra numbers at the end.  | 
            ||
| 309 | // TODO: This is the place to start when trying to support extraction of multiple phone number  | 
            ||
| 310 | // from split notations (+41 49 123 45 67 / 68).  | 
            ||
| 311 | $candidate = static::trimAfterFirstMatch(PhoneNumberUtil::$SECOND_NUMBER_START_PATTERN, $candidate);  | 
            ||
| 312 | |||
| 313 | $match = $this->extractMatch($candidate, $start);  | 
            ||
| 
                                                                                                    
                        
                         | 
                |||
| 314 |             if ($match !== null) { | 
            ||
| 315 | return $match;  | 
            ||
| 316 | }  | 
            ||
| 317 | |||
| 318 | $index = $start + mb_strlen($candidate);  | 
            ||
| 319 | $this->maxTries--;  | 
            ||
| 320 | }  | 
            ||
| 321 | |||
| 322 | return null;  | 
            ||
| 323 | }  | 
            ||
| 324 | |||
| 325 | /**  | 
            ||
| 326 | * Trims away any characters after the first match of $pattern in $candidate,  | 
            ||
| 327 | * returning the trimmed version.  | 
            ||
| 328 | *  | 
            ||
| 329 | * @param string $pattern  | 
            ||
| 330 | * @param string $candidate  | 
            ||
| 331 | * @return string  | 
            ||
| 332 | */  | 
            ||
| 333 | protected static function trimAfterFirstMatch($pattern, $candidate)  | 
            ||
| 334 |     { | 
            ||
| 335 | $trailingCharsMatcher = new Matcher($pattern, $candidate);  | 
            ||
| 336 |         if ($trailingCharsMatcher->find()) { | 
            ||
| 337 | $startChar = $trailingCharsMatcher->start();  | 
            ||
| 338 | $candidate = mb_substr($candidate, 0, $startChar);  | 
            ||
| 339 | }  | 
            ||
| 340 | return $candidate;  | 
            ||
| 341 | }  | 
            ||
| 342 | |||
| 343 | /**  | 
            ||
| 344 | * Helper method to determine if a character is a Latin-script letter or not. For our purposes,  | 
            ||
| 345 | * combining marks should also return true since we assume they have been added to a preceding  | 
            ||
| 346 | * Latin character.  | 
            ||
| 347 | *  | 
            ||
| 348 | * @param string $letter  | 
            ||
| 349 | * @return bool  | 
            ||
| 350 | * @internal  | 
            ||
| 351 | */  | 
            ||
| 352 | public static function isLatinLetter($letter)  | 
            ||
| 353 |     { | 
            ||
| 354 | // Combining marks are a subset of non-spacing-mark.  | 
            ||
| 355 |         if (preg_match('/\p{L}/u', $letter) !== 1 && preg_match('/\p{Mn}/u', $letter) !== 1) { | 
            ||
| 356 | return false;  | 
            ||
| 357 | }  | 
            ||
| 358 | |||
| 359 |         return (preg_match('/\p{Latin}/u', $letter) === 1) | 
            ||
| 360 |         || (preg_match('/\pM+/u', $letter) === 1); | 
            ||
| 361 | }  | 
            ||
| 362 | |||
| 363 | /**  | 
            ||
| 364 | * @param string $character  | 
            ||
| 365 | * @return bool  | 
            ||
| 366 | */  | 
            ||
| 367 | protected static function isInvalidPunctuationSymbol($character)  | 
            ||
| 370 | }  | 
            ||
| 371 | |||
| 372 | /**  | 
            ||
| 373 | * Attempts to extract a match from a $candidate.  | 
            ||
| 374 | *  | 
            ||
| 375 | * @param string $candidate The candidate text that might contain a phone number  | 
            ||
| 376 | * @param int $offset The offset of $candidate within $this->text  | 
            ||
| 377 | * @return PhoneNumberMatch|null The match found, null if none can be found  | 
            ||
| 378 | */  | 
            ||
| 379 | protected function extractMatch($candidate, $offset)  | 
            ||
| 406 | }  | 
            ||
| 407 | |||
| 408 | /**  | 
            ||
| 409 | * Attempts to extract a match from $candidate if the whole candidate does not qualify as a  | 
            ||
| 410 | * match.  | 
            ||
| 411 | *  | 
            ||
| 412 | * @param string $candidate The candidate text that might contact a phone number  | 
            ||
| 413 | * @param int $offset The current offset of $candidate within $this->text  | 
            ||
| 414 | * @return PhoneNumberMatch|null The match found, null if none can be found  | 
            ||
| 415 | */  | 
            ||
| 416 | protected function extractInnerMatch($candidate, $offset)  | 
            ||
| 445 | }  | 
            ||
| 446 | |||
| 447 | /**  | 
            ||
| 448 | * Parses a phone number from the $candidate} using PhoneNumberUtil::parse() and  | 
            ||
| 449 | * verifies it matches the requested leniency. If parsing and verification succeed, a  | 
            ||
| 450 | * corresponding PhoneNumberMatch is returned, otherwise this method returns null.  | 
            ||
| 451 | *  | 
            ||
| 452 | * @param string $candidate The candidate match  | 
            ||
| 453 | * @param int $offset The offset of $candidate within $this->text  | 
            ||
| 454 | * @return PhoneNumberMatch|null The parsed and validated phone number match, or null  | 
            ||
| 455 | */  | 
            ||
| 456 | protected function parseAndVerify($candidate, $offset)  | 
            ||
| 457 |     { | 
            ||
| 458 |         try { | 
            ||
| 459 | // Check the candidate doesn't contain any formatting which would indicate that it really  | 
            ||
| 460 | // isn't a phone number  | 
            ||
| 461 | $matchingBracketsMatcher = new Matcher(static::$matchingBrackets, $candidate);  | 
            ||
| 462 | $pubPagesMatcher = new Matcher(static::$pubPages, $candidate);  | 
            ||
| 463 |             if (!$matchingBracketsMatcher->matches() || $pubPagesMatcher->find()) { | 
            ||
| 464 | return null;  | 
            ||
| 465 | }  | 
            ||
| 466 | |||
| 467 | // If leniency is set to VALID or stricter, we also want to skip numbers that are surrounded  | 
            ||
| 468 | // by Latin alphabetic characters, to skip cases like abc8005001234 or 8005001234def.  | 
            ||
| 469 |             if ($this->leniency->compareTo(Leniency::VALID()) >= 0) { | 
            ||
| 470 | // If the candidate is not at the start of the text, and does not start with phone-number  | 
            ||
| 471 | // punctuation, check the previous character.  | 
            ||
| 472 | $leadClassMatcher = new Matcher(static::$leadClass, $candidate);  | 
            ||
| 473 |                 if ($offset > 0 && !$leadClassMatcher->lookingAt()) { | 
            ||
| 474 | $previousChar = mb_substr($this->text, $offset - 1, 1);  | 
            ||
| 475 | // We return null if it is a latin letter or an invalid punctuation symbol.  | 
            ||
| 476 |                     if (static::isInvalidPunctuationSymbol($previousChar) || static::isLatinLetter($previousChar)) { | 
            ||
| 477 | return null;  | 
            ||
| 478 | }  | 
            ||
| 479 | }  | 
            ||
| 480 | $lastCharIndex = $offset + mb_strlen($candidate);  | 
            ||
| 481 |                 if ($lastCharIndex < mb_strlen($this->text)) { | 
            ||
| 482 | $nextChar = mb_substr($this->text, $lastCharIndex, 1);  | 
            ||
| 483 |                     if (static::isInvalidPunctuationSymbol($nextChar) || static::isLatinLetter($nextChar)) { | 
            ||
| 484 | return null;  | 
            ||
| 485 | }  | 
            ||
| 486 | }  | 
            ||
| 487 | }  | 
            ||
| 488 | |||
| 489 | $number = $this->phoneUtil->parseAndKeepRawInput($candidate, $this->preferredRegion);  | 
            ||
| 490 | |||
| 491 |             if ($this->leniency->verify($number, $candidate, $this->phoneUtil)) { | 
            ||
| 492 | // We used parseAndKeepRawInput to create this number, but for now we don't return the extra  | 
            ||
| 493 | // values parsed. TODO: stop clearing all values here and switch all users over  | 
            ||
| 494 | // to using rawInput() rather than the rawString() of PhoneNumberMatch  | 
            ||
| 495 | $number->clearCountryCodeSource();  | 
            ||
| 496 | $number->clearRawInput();  | 
            ||
| 497 | $number->clearPreferredDomesticCarrierCode();  | 
            ||
| 498 | return new PhoneNumberMatch($offset, $candidate, $number);  | 
            ||
| 499 | }  | 
            ||
| 500 |         } catch (NumberParseException $e) { | 
            ||
| 501 | // ignore and continue  | 
            ||
| 502 | }  | 
            ||
| 503 | return null;  | 
            ||
| 504 | }  | 
            ||
| 505 | |||
| 506 | /**  | 
            ||
| 507 | * @param PhoneNumberUtil $util  | 
            ||
| 508 | * @param PhoneNumber $number  | 
            ||
| 509 | * @param string $normalizedCandidate  | 
            ||
| 510 | * @param string[] $formattedNumberGroups  | 
            ||
| 511 | * @return bool  | 
            ||
| 512 | */  | 
            ||
| 513 | public static function allNumberGroupsRemainGrouped(  | 
            ||
| 514 | PhoneNumberUtil $util,  | 
            ||
| 515 | PhoneNumber $number,  | 
            ||
| 516 | $normalizedCandidate,  | 
            ||
| 517 | $formattedNumberGroups  | 
            ||
| 518 |     ) { | 
            ||
| 519 | $fromIndex = 0;  | 
            ||
| 520 |         if ($number->getCountryCodeSource() !== CountryCodeSource::FROM_DEFAULT_COUNTRY) { | 
            ||
| 521 | // First skip the country code if the normalized candidate contained it.  | 
            ||
| 522 | $countryCode = $number->getCountryCode();  | 
            ||
| 523 | $fromIndex = mb_strpos($normalizedCandidate, $countryCode) + mb_strlen($countryCode);  | 
            ||
| 524 | }  | 
            ||
| 525 | |||
| 526 | // Check each group of consecutive digits are not broken into separate groupings in the  | 
            ||
| 527 | // $normalizedCandidate string.  | 
            ||
| 528 | $formattedNumberGroupsLength = count($formattedNumberGroups);  | 
            ||
| 529 |         for ($i = 0; $i < $formattedNumberGroupsLength; $i++) { | 
            ||
| 530 | // Fails if the substring of $normalizedCandidate starting from $fromIndex  | 
            ||
| 531 | // doesn't contain the consecutive digits in $formattedNumberGroups[$i].  | 
            ||
| 532 | $fromIndex = mb_strpos($normalizedCandidate, $formattedNumberGroups[$i], $fromIndex);  | 
            ||
| 533 |             if ($fromIndex === false) { | 
            ||
| 534 | return false;  | 
            ||
| 535 | }  | 
            ||
| 536 | |||
| 537 | // Moves $fromIndex forward.  | 
            ||
| 538 | $fromIndex += mb_strlen($formattedNumberGroups[$i]);  | 
            ||
| 539 |             if ($i === 0 && $fromIndex < mb_strlen($normalizedCandidate)) { | 
            ||
| 540 | // We are at the position right after the NDC. We get the region used for formatting  | 
            ||
| 541 | // information based on the country code in the phone number, rather than the number itself,  | 
            ||
| 542 | // as we do not need to distinguish between different countries with the same country  | 
            ||
| 543 | // calling code and this is faster.  | 
            ||
| 544 | $region = $util->getRegionCodeForCountryCode($number->getCountryCode());  | 
            ||
| 545 | |||
| 546 | if ($util->getNddPrefixForRegion($region, true) !== null  | 
            ||
| 547 | && is_int(mb_substr($normalizedCandidate, $fromIndex, 1))  | 
            ||
| 548 |                 ) { | 
            ||
| 549 | // This means there is no formatting symbol after the NDC. In this case, we only  | 
            ||
| 550 | // accept the number if there is no formatting symbol at all in the number, except  | 
            ||
| 551 | // for extensions. This is only important for countries with national prefixes.  | 
            ||
| 552 | $nationalSignificantNumber = $util->getNationalSignificantNumber($number);  | 
            ||
| 553 | return mb_substr(  | 
            ||
| 554 | mb_substr($normalizedCandidate, $fromIndex - mb_strlen($formattedNumberGroups[$i])),  | 
            ||
| 555 | mb_strlen($nationalSignificantNumber)  | 
            ||
| 556 | ) === $nationalSignificantNumber;  | 
            ||
| 557 | }  | 
            ||
| 558 | }  | 
            ||
| 559 | }  | 
            ||
| 560 | // The check here makes sure that we haven't mistakenly already used the extension to  | 
            ||
| 561 | // match the last group of the subscriber number. Note the extension cannot have  | 
            ||
| 562 | // formatting in-between digits  | 
            ||
| 563 | |||
| 564 |         if ($number->hasExtension()) { | 
            ||
| 565 | return mb_strpos(mb_substr($normalizedCandidate, $fromIndex), $number->getExtension()) !== false;  | 
            ||
| 566 | }  | 
            ||
| 567 | |||
| 568 | return true;  | 
            ||
| 569 | }  | 
            ||
| 570 | |||
| 571 | /**  | 
            ||
| 572 | * @param PhoneNumberUtil $util  | 
            ||
| 573 | * @param PhoneNumber $number  | 
            ||
| 574 | * @param string $normalizedCandidate  | 
            ||
| 575 | * @param string[] $formattedNumberGroups  | 
            ||
| 576 | * @return bool  | 
            ||
| 577 | */  | 
            ||
| 578 | public static function allNumberGroupsAreExactlyPresent(  | 
            ||
| 614 | }  | 
            ||
| 615 | |||
| 616 | /**  | 
            ||
| 617 | * Helper method to get the national-number part of a number, formatted without any national  | 
            ||
| 618 | * prefix, and return it as a set of digit blocks that would be formatted together.  | 
            ||
| 619 | *  | 
            ||
| 620 | * @param PhoneNumberUtil $util  | 
            ||
| 621 | * @param PhoneNumber $number  | 
            ||
| 622 | * @param NumberFormat $formattingPattern  | 
            ||
| 623 | * @return string[]  | 
            ||
| 624 | */  | 
            ||
| 625 | protected static function getNationalNumberGroups(  | 
            ||
| 649 | }  | 
            ||
| 650 | |||
| 651 | /**  | 
            ||
| 652 | * @param PhoneNumber $number  | 
            ||
| 653 | * @param string $candidate  | 
            ||
| 654 | * @param PhoneNumberUtil $util  | 
            ||
| 655 | * @param \Closure $checker  | 
            ||
| 656 | * @return bool  | 
            ||
| 657 | */  | 
            ||
| 658 | public static function checkNumberGroupingIsValid(  | 
            ||
| 659 | PhoneNumber $number,  | 
            ||
| 660 | $candidate,  | 
            ||
| 661 | PhoneNumberUtil $util,  | 
            ||
| 662 | \Closure $checker  | 
            ||
| 663 |     ) { | 
            ||
| 664 | $normalizedCandidate = PhoneNumberUtil::normalizeDigits($candidate, true /* keep non-digits */);  | 
            ||
| 665 | $formattedNumberGroups = static::getNationalNumberGroups($util, $number);  | 
            ||
| 666 |         if ($checker($util, $number, $normalizedCandidate, $formattedNumberGroups)) { | 
            ||
| 667 | return true;  | 
            ||
| 668 | }  | 
            ||
| 669 | |||
| 670 | // If this didn't pass, see if there are any alternative formats that match, and try them instead.  | 
            ||
| 671 | $alternateFormats = static::getAlternateFormatsForCountry($number->getCountryCode());  | 
            ||
| 672 | |||
| 673 | $nationalSignificantNumber = $util->getNationalSignificantNumber($number);  | 
            ||
| 674 |         if ($alternateFormats !== null) { | 
            ||
| 675 |             foreach ($alternateFormats->numberFormats() as $alternateFormat) { | 
            ||
| 676 |                 if ($alternateFormat->leadingDigitsPatternSize() > 0) { | 
            ||
| 677 | // There is only one leading digits pattern for alternate formats.  | 
            ||
| 678 | $pattern = $alternateFormat->getLeadingDigitsPattern(0);  | 
            ||
| 679 | |||
| 680 | $nationalSignificantNumberMatcher = new Matcher($pattern, $nationalSignificantNumber);  | 
            ||
| 681 |                     if (!$nationalSignificantNumberMatcher->lookingAt()) { | 
            ||
| 682 | // Leading digits don't match; try another one.  | 
            ||
| 683 | continue;  | 
            ||
| 684 | }  | 
            ||
| 685 | }  | 
            ||
| 686 | |||
| 687 | $formattedNumberGroups = static::getNationalNumberGroups($util, $number, $alternateFormat);  | 
            ||
| 688 |                 if ($checker($util, $number, $normalizedCandidate, $formattedNumberGroups)) { | 
            ||
| 689 | return true;  | 
            ||
| 690 | }  | 
            ||
| 691 | }  | 
            ||
| 692 | }  | 
            ||
| 693 | return false;  | 
            ||
| 694 | }  | 
            ||
| 695 | |||
| 696 | /**  | 
            ||
| 697 | * @param PhoneNumber $number  | 
            ||
| 698 | * @param string $candidate  | 
            ||
| 699 | * @return bool  | 
            ||
| 700 | */  | 
            ||
| 701 | public static function containsMoreThanOneSlashInNationalNumber(PhoneNumber $number, $candidate)  | 
            ||
| 702 |     { | 
            ||
| 703 | $firstSlashInBodyIndex = mb_strpos($candidate, '/');  | 
            ||
| 704 |         if ($firstSlashInBodyIndex === false) { | 
            ||
| 705 | // No slashes, this is okay  | 
            ||
| 706 | return false;  | 
            ||
| 707 | }  | 
            ||
| 708 | |||
| 709 | // Now look for a second one.  | 
            ||
| 710 | $secondSlashInBodyIndex = mb_strpos($candidate, '/', $firstSlashInBodyIndex + 1);  | 
            ||
| 711 |         if ($secondSlashInBodyIndex === false) { | 
            ||
| 712 | // Only one slash, this is okay  | 
            ||
| 713 | return false;  | 
            ||
| 714 | }  | 
            ||
| 715 | |||
| 716 | // If the first slash is after the country calling code, this is permitted  | 
            ||
| 717 | $candidateHasCountryCode = ($number->getCountryCodeSource() === CountryCodeSource::FROM_NUMBER_WITH_PLUS_SIGN  | 
            ||
| 718 | || $number->getCountryCodeSource() === CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN);  | 
            ||
| 719 | |||
| 720 | if ($candidateHasCountryCode  | 
            ||
| 721 | && PhoneNumberUtil::normalizeDigitsOnly(  | 
            ||
| 722 | mb_substr($candidate, 0, $firstSlashInBodyIndex)  | 
            ||
| 723 | ) == $number->getCountryCode()  | 
            ||
| 724 |         ) { | 
            ||
| 725 | // Any more slashes and this is illegal  | 
            ||
| 726 | return (mb_strpos(mb_substr($candidate, $secondSlashInBodyIndex + 1), '/') !== false);  | 
            ||
| 727 | }  | 
            ||
| 728 | |||
| 729 | return true;  | 
            ||
| 730 | }  | 
            ||
| 731 | |||
| 732 | /**  | 
            ||
| 733 | * @param PhoneNumber $number  | 
            ||
| 734 | * @param string $candidate  | 
            ||
| 735 | * @param PhoneNumberUtil $util  | 
            ||
| 736 | * @return bool  | 
            ||
| 737 | */  | 
            ||
| 738 | public static function containsOnlyValidXChars(PhoneNumber $number, $candidate, PhoneNumberUtil $util)  | 
            ||
| 739 |     { | 
            ||
| 740 | // The characters 'x' and 'X' can be (1) a carrier code, in which case they always precede the  | 
            ||
| 741 | // national significant number or (2) an extension sign, in which case they always precede the  | 
            ||
| 742 | // extension number. We assume a carrier code is more than 1 digit, so the first case has to  | 
            ||
| 743 | // have more than 1 consecutive 'x' or 'X', whereas the second case can only have exactly 1 'x'  | 
            ||
| 744 | // or 'X'. We ignore the character if it appears as the last character of the string.  | 
            ||
| 745 | $candidateLength = mb_strlen($candidate);  | 
            ||
| 746 | |||
| 747 |         for ($index = 0; $index < $candidateLength - 1; $index++) { | 
            ||
| 748 | $charAtIndex = mb_substr($candidate, $index, 1);  | 
            ||
| 749 |             if ($charAtIndex == 'x' || $charAtIndex == 'X') { | 
            ||
| 750 | $charAtNextIndex = mb_substr($candidate, $index + 1, 1);  | 
            ||
| 751 |                 if ($charAtNextIndex == 'x' || $charAtNextIndex == 'X') { | 
            ||
| 752 | // This is the carrier code case, in which the 'X's always precede the national  | 
            ||
| 753 | // significant number.  | 
            ||
| 754 | $index++;  | 
            ||
| 755 | |||
| 756 |                     if ($util->isNumberMatch($number, mb_substr($candidate, $index)) != MatchType::NSN_MATCH) { | 
            ||
| 757 | return false;  | 
            ||
| 758 | }  | 
            ||
| 759 | } elseif (!PhoneNumberUtil::normalizeDigitsOnly(mb_substr($candidate,  | 
            ||
| 760 | $index)) == $number->getExtension()  | 
            ||
| 761 |                 ) { | 
            ||
| 762 | // This is the extension sign case, in which the 'x' or 'X' should always precede the  | 
            ||
| 763 | // extension number  | 
            ||
| 764 | return false;  | 
            ||
| 765 | }  | 
            ||
| 766 | }  | 
            ||
| 767 | }  | 
            ||
| 768 | return true;  | 
            ||
| 769 | }  | 
            ||
| 770 | |||
| 771 | /**  | 
            ||
| 772 | * @param PhoneNumber $number  | 
            ||
| 773 | * @param PhoneNumberUtil $util  | 
            ||
| 774 | * @return bool  | 
            ||
| 775 | */  | 
            ||
| 776 | public static function isNationalPrefixPresentIfRequired(PhoneNumber $number, PhoneNumberUtil $util)  | 
            ||
| 777 |     { | 
            ||
| 778 | // First, check how we deduced the country code. If it was written in international format, then  | 
            ||
| 779 | // the national prefix is not required.  | 
            ||
| 780 |         if ($number->getCountryCodeSource() !== CountryCodeSource::FROM_DEFAULT_COUNTRY) { | 
            ||
| 781 | return true;  | 
            ||
| 782 | }  | 
            ||
| 783 | |||
| 784 | $phoneNumberRegion = $util->getRegionCodeForCountryCode($number->getCountryCode());  | 
            ||
| 785 | $metadata = $util->getMetadataForRegion($phoneNumberRegion);  | 
            ||
| 786 |         if ($metadata === null) { | 
            ||
| 787 | return true;  | 
            ||
| 788 | }  | 
            ||
| 789 | |||
| 790 | // Check if a national prefix should be present when formatting this number.  | 
            ||
| 791 | $nationalNumber = $util->getNationalSignificantNumber($number);  | 
            ||
| 792 | $formatRule = $util->chooseFormattingPatternForNumber($metadata->numberFormats(), $nationalNumber);  | 
            ||
| 793 | // To do this, we check that a national prefix formatting rule was present and that it wasn't  | 
            ||
| 794 | // just the first-group symbol ($1) with punctuation.  | 
            ||
| 795 |         if (($formatRule !== null) && mb_strlen($formatRule->getNationalPrefixFormattingRule()) > 0) { | 
            ||
| 796 |             if ($formatRule->getNationalPrefixOptionalWhenFormatting()) { | 
            ||
| 797 | // The national-prefix is optional in these cases, so we don't need to check if it was  | 
            ||
| 798 | // present.  | 
            ||
| 799 | return true;  | 
            ||
| 800 | }  | 
            ||
| 801 | |||
| 802 |             if (PhoneNumberUtil::formattingRuleHasFirstGroupOnly($formatRule->getNationalPrefixFormattingRule())) { | 
            ||
| 803 | // National Prefix not needed for this number.  | 
            ||
| 804 | return true;  | 
            ||
| 805 | }  | 
            ||
| 806 | |||
| 807 | // Normalize the remainder.  | 
            ||
| 808 | $rawInputCopy = PhoneNumberUtil::normalizeDigitsOnly($number->getRawInput());  | 
            ||
| 809 | $rawInput = $rawInputCopy;  | 
            ||
| 810 | // Check if we found a national prefix and/or carrier code at the start of the raw input, and  | 
            ||
| 811 | // return the result.  | 
            ||
| 812 | $carrierCode = null;  | 
            ||
| 813 | return $util->maybeStripNationalPrefixAndCarrierCode($rawInput, $metadata, $carrierCode);  | 
            ||
| 814 | }  | 
            ||
| 815 | return true;  | 
            ||
| 816 | }  | 
            ||
| 817 | |||
| 818 | |||
| 819 | /**  | 
            ||
| 820 | * Storage for Alternate Formats  | 
            ||
| 821 | * @var PhoneMetadata[]  | 
            ||
| 822 | */  | 
            ||
| 823 | protected static $callingCodeToAlternateFormatsMap = array();  | 
            ||
| 824 | |||
| 825 | /**  | 
            ||
| 826 | * @param $countryCallingCode  | 
            ||
| 827 | * @return PhoneMetadata|null  | 
            ||
| 828 | */  | 
            ||
| 829 | protected static function getAlternateFormatsForCountry($countryCallingCode)  | 
            ||
| 830 |     { | 
            ||
| 831 | $countryCodeSet = AlternateFormatsCountryCodeSet::$alternateFormatsCountryCodeSet;  | 
            ||
| 832 | |||
| 833 |         if (!in_array($countryCallingCode, $countryCodeSet)) { | 
            ||
| 834 | return null;  | 
            ||
| 835 | }  | 
            ||
| 836 | |||
| 837 |         if (!isset(static::$callingCodeToAlternateFormatsMap[$countryCallingCode])) { | 
            ||
| 838 | static::loadAlternateFormatsMetadataFromFile($countryCallingCode);  | 
            ||
| 839 | }  | 
            ||
| 840 | |||
| 841 | return static::$callingCodeToAlternateFormatsMap[$countryCallingCode];  | 
            ||
| 842 | }  | 
            ||
| 843 | |||
| 844 | /**  | 
            ||
| 845 | * @param string $countryCallingCode  | 
            ||
| 846 | * @throws \Exception  | 
            ||
| 847 | */  | 
            ||
| 848 | protected static function loadAlternateFormatsMetadataFromFile($countryCallingCode)  | 
            ||
| 849 |     { | 
            ||
| 850 | $fileName = static::$alternateFormatsFilePrefix . '_' . $countryCallingCode . '.php';  | 
            ||
| 851 | |||
| 852 |         if (!is_readable($fileName)) { | 
            ||
| 853 |             throw new \Exception('missing metadata: ' . $fileName); | 
            ||
| 854 | }  | 
            ||
| 855 | |||
| 856 | $metadataLoader = new DefaultMetadataLoader();  | 
            ||
| 857 | $data = $metadataLoader->loadMetadata($fileName);  | 
            ||
| 858 | $metadata = new PhoneMetadata();  | 
            ||
| 859 | $metadata->fromArray($data);  | 
            ||
| 860 | static::$callingCodeToAlternateFormatsMap[$countryCallingCode] = $metadata;  | 
            ||
| 861 | }  | 
            ||
| 862 | |||
| 863 | |||
| 864 | /**  | 
            ||
| 865 | * Return the current element  | 
            ||
| 866 | * @link http://php.net/manual/en/iterator.current.php  | 
            ||
| 867 | * @return PhoneNumberMatch|null  | 
            ||
| 868 | */  | 
            ||
| 869 | public function current()  | 
            ||
| 870 |     { | 
            ||
| 871 | return $this->lastMatch;  | 
            ||
| 872 | }  | 
            ||
| 873 | |||
| 874 | /**  | 
            ||
| 875 | * Move forward to next element  | 
            ||
| 876 | * @link http://php.net/manual/en/iterator.next.php  | 
            ||
| 877 | * @return void Any returned value is ignored.  | 
            ||
| 878 | */  | 
            ||
| 879 | public function next()  | 
            ||
| 880 |     { | 
            ||
| 881 | $this->lastMatch = $this->find($this->searchIndex);  | 
            ||
| 882 | |||
| 883 |         if ($this->lastMatch === null) { | 
            ||
| 884 | $this->state = 'DONE';  | 
            ||
| 885 |         } else { | 
            ||
| 886 | $this->searchIndex = $this->lastMatch->end();  | 
            ||
| 887 | $this->state = 'READY';  | 
            ||
| 888 | }  | 
            ||
| 889 | |||
| 890 | $this->searchIndex++;  | 
            ||
| 891 | }  | 
            ||
| 892 | |||
| 893 | /**  | 
            ||
| 894 | * Return the key of the current element  | 
            ||
| 895 | * @link http://php.net/manual/en/iterator.key.php  | 
            ||
| 896 | * @return mixed scalar on success, or null on failure.  | 
            ||
| 897 | * @since 5.0.0  | 
            ||
| 898 | */  | 
            ||
| 899 | public function key()  | 
            ||
| 900 |     { | 
            ||
| 901 | return $this->searchIndex;  | 
            ||
| 902 | }  | 
            ||
| 903 | |||
| 904 | /**  | 
            ||
| 905 | * Checks if current position is valid  | 
            ||
| 906 | * @link http://php.net/manual/en/iterator.valid.php  | 
            ||
| 907 | * @return boolean The return value will be casted to boolean and then evaluated.  | 
            ||
| 908 | * Returns true on success or false on failure.  | 
            ||
| 909 | * @since 5.0.0  | 
            ||
| 910 | */  | 
            ||
| 911 | public function valid()  | 
            ||
| 914 | }  | 
            ||
| 915 | |||
| 916 | /**  | 
            ||
| 917 | * Rewind the Iterator to the first element  | 
            ||
| 918 | * @link http://php.net/manual/en/iterator.rewind.php  | 
            ||
| 919 | * @return void Any returned value is ignored.  | 
            ||
| 920 | * @since 5.0.0  | 
            ||
| 921 | */  | 
            ||
| 922 | public function rewind()  | 
            ||
| 926 | }  | 
            ||
| 927 | }  | 
            ||
| 928 | 
This check looks for function or method calls that always return null and whose return value is assigned to a variable.
The method
getObject()can return nothing but null, so it makes no sense to assign that value to a variable.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.