/** * Strips any national prefix (such as 0, 1) present in the number provided. * * @param string $number the normalized telephone number that we wish to strip any national * dialing prefix from * @param PhoneMetadata $metadata the metadata for the region that we think this number is from * @param string $carrierCode a place to insert the carrier code if one is extracted * @return bool true if a national prefix or carrier code (or both) could be extracted. */ public function maybeStripNationalPrefixAndCarrierCode(&$number, PhoneMetadata $metadata, &$carrierCode) { $numberLength = mb_strlen($number); $possibleNationalPrefix = $metadata->getNationalPrefixForParsing(); if ($numberLength == 0 || $possibleNationalPrefix === null || mb_strlen($possibleNationalPrefix) == 0) { // Early return for numbers of zero length. return false; } // Attempt to parse the first digits as a national prefix. $prefixMatcher = new Matcher($possibleNationalPrefix, $number); if ($prefixMatcher->lookingAt()) { $nationalNumberRule = $metadata->getGeneralDesc()->getNationalNumberPattern(); // Check if the original number is viable. $nationalNumberRuleMatcher = new Matcher($nationalNumberRule, $number); $isViableOriginalNumber = $nationalNumberRuleMatcher->matches(); // $prefixMatcher->group($numOfGroups) === null implies nothing was captured by the capturing // groups in $possibleNationalPrefix; therefore, no transformation is necessary, and we just // remove the national prefix $numOfGroups = $prefixMatcher->groupCount(); $transformRule = $metadata->getNationalPrefixTransformRule(); if ($transformRule === null || mb_strlen($transformRule) == 0 || $prefixMatcher->group($numOfGroups - 1) === null) { // If the original number was viable, and the resultant number is not, we return. $matcher = new Matcher($nationalNumberRule, substr($number, $prefixMatcher->end())); if ($isViableOriginalNumber && !$matcher->matches()) { return false; } if ($carrierCode !== null && $numOfGroups > 0 && $prefixMatcher->group($numOfGroups) !== null) { $carrierCode .= $prefixMatcher->group(1); } $number = substr($number, $prefixMatcher->end()); return true; } else { // Check that the resultant number is still viable. If not, return. Check this by copying // the string and making the transformation on the copy first. $transformedNumber = $number; $transformedNumber = substr_replace($transformedNumber, $prefixMatcher->replaceFirst($transformRule), 0, $numberLength); $matcher = new Matcher($nationalNumberRule, $transformedNumber); if ($isViableOriginalNumber && !$matcher->matches()) { return false; } if ($carrierCode !== null && $numOfGroups > 1) { $carrierCode .= $prefixMatcher->group(1); } $number = substr_replace($number, $transformedNumber, 0, mb_strlen($number)); return true; } } return false; }
/** * Parses a string and fills up the phoneNumber. This method is the same as the public * parse() method, with the exception that it allows the default region to be null, for use by * isNumberMatch(). checkRegion should be set to false if it is permitted for the default region * to be null or unknown ("ZZ"). * @param $numberToParse * @param string $defaultRegion * @param $keepRawInput * @param $checkRegion * @param PhoneNumber $phoneNumber * @throws NumberParseException */ private function parseHelper($numberToParse, $defaultRegion, $keepRawInput, $checkRegion, PhoneNumber $phoneNumber) { if ($numberToParse === NULL) { throw new NumberParseException(NumberParseException::NOT_A_NUMBER, "The phone number supplied was null."); } elseif (strlen($numberToParse) > self::MAX_INPUT_STRING_LENGTH) { throw new NumberParseException(NumberParseException::TOO_LONG, "The string supplied was too long to parse."); } $nationalNumber = ''; $this->buildNationalNumberForParsing($numberToParse, $nationalNumber); if (!$this->isViablePhoneNumber($nationalNumber)) { throw new NumberParseException(NumberParseException::NOT_A_NUMBER, "The string supplied did not seem to be a phone number."); } // Check the region supplied is valid, or that the extracted number starts with some sort of + // sign so the number's region can be determined. if ($checkRegion && !$this->checkRegionForParsing($nationalNumber, $defaultRegion)) { throw new NumberParseException(NumberParseException::INVALID_COUNTRY_CODE, "Missing or invalid default region."); } if ($keepRawInput) { $phoneNumber->setRawInput($numberToParse); } // Attempt to parse extension first, since it doesn't require region-specific data and we want // to have the non-normalised number here. $extension = $this->maybeStripExtension($nationalNumber); if (strlen($extension) > 0) { $phoneNumber->setExtension($extension); } $regionMetadata = $this->getMetadataForRegion($defaultRegion); // Check to see if the number is given in international format so we know whether this number is // from the default region or not. $normalizedNationalNumber = ""; $countryCode = 0; try { // TODO: This method should really just take in the string buffer that has already // been created, and just remove the prefix, rather than taking in a string and then // outputting a string buffer. $countryCode = $this->maybeExtractCountryCode($nationalNumber, $regionMetadata, $normalizedNationalNumber, $keepRawInput, $phoneNumber); } catch (NumberParseException $e) { $matcher = new Matcher(self::$PLUS_CHARS_PATTERN, $nationalNumber); if ($e->getErrorType() == NumberParseException::INVALID_COUNTRY_CODE && $matcher->lookingAt()) { // Strip the plus-char, and try again. $countryCode = $this->maybeExtractCountryCode(substr($nationalNumber, $matcher->end()), $regionMetadata, $normalizedNationalNumber, $keepRawInput, $phoneNumber); if ($countryCode == 0) { throw new NumberParseException(NumberParseException::INVALID_COUNTRY_CODE, "Could not interpret numbers after plus-sign."); } } else { throw new NumberParseException($e->getErrorType(), $e->getMessage()); } } if ($countryCode !== 0) { $phoneNumberRegion = $this->getRegionCodeForCountryCode($countryCode); if ($phoneNumberRegion != $defaultRegion) { // Metadata cannot be null because the country calling code is valid. $regionMetadata = $this->getMetadataForRegionOrCallingCode($countryCode, $phoneNumberRegion); } } else { // If no extracted country calling code, use the region supplied instead. The national number // is just the normalized version of the number we were given to parse. $normalizedNationalNumber .= $this->normalize($nationalNumber); if ($defaultRegion !== NULL) { $countryCode = $regionMetadata->getCountryCode(); $phoneNumber->setCountryCode($countryCode); } else { if ($keepRawInput) { $phoneNumber->clearCountryCodeSource(); } } } if (strlen($normalizedNationalNumber) < self::MIN_LENGTH_FOR_NSN) { throw new NumberParseException(NumberParseException::TOO_SHORT_NSN, "The string supplied is too short to be a phone number."); } if ($regionMetadata !== NULL) { $carrierCode = ""; $this->maybeStripNationalPrefixAndCarrierCode($normalizedNationalNumber, $regionMetadata, $carrierCode); if ($keepRawInput) { $phoneNumber->setPreferredDomesticCarrierCode($carrierCode); } } $lengthOfNationalNumber = strlen($normalizedNationalNumber); if ($lengthOfNationalNumber < self::MIN_LENGTH_FOR_NSN) { throw new NumberParseException(NumberParseException::TOO_SHORT_NSN, "The string supplied is too short to be a phone number."); } if ($lengthOfNationalNumber > self::MAX_LENGTH_FOR_NSN) { throw new NumberParseException(NumberParseException::TOO_LONG, "The string supplied is too long to be a phone number."); } if ($normalizedNationalNumber[0] == '0') { $phoneNumber->setItalianLeadingZero(true); } $phoneNumber->setNationalNumber((double) $normalizedNationalNumber); }