/** * Takes two phone numbers and compares them for equality. * * <p>Returns EXACT_MATCH if the country_code, NSN, presence of a leading zero * for Italian numbers and any extension present are the same. Returns NSN_MATCH * if either or both has no region specified, and the NSNs and extensions are * the same. Returns SHORT_NSN_MATCH if either or both has no region specified, * or the region specified is the same, and one NSN could be a shorter version * of the other number. This includes the case where one has an extension * specified, and the other does not. Returns NO_MATCH otherwise. For example, * the numbers +1 345 657 1234 and 657 1234 are a SHORT_NSN_MATCH. The numbers * +1 345 657 1234 and 345 657 are a NO_MATCH. * * @param $firstNumberIn PhoneNumber|string First number to compare. If it is a * string it can contain formatting, and can have country calling code specified * with + at the start. * @param $secondNumberIn PhoneNumber|string Second number to compare. If it is a * string it can contain formatting, and can have country calling code specified * with + at the start. * @throws \InvalidArgumentException * @return int {MatchType} NOT_A_NUMBER, NO_MATCH, */ public function isNumberMatch($firstNumberIn, $secondNumberIn) { if (is_string($firstNumberIn) && is_string($secondNumberIn)) { try { $firstNumberAsProto = $this->parse($firstNumberIn, self::UNKNOWN_REGION); return $this->isNumberMatch($firstNumberAsProto, $secondNumberIn); } catch (NumberParseException $e) { if ($e->getErrorType() === NumberParseException::INVALID_COUNTRY_CODE) { try { $secondNumberAsProto = $this->parse($secondNumberIn, self::UNKNOWN_REGION); return $this->isNumberMatch($secondNumberAsProto, $firstNumberIn); } catch (NumberParseException $e2) { if ($e2->getErrorType() === NumberParseException::INVALID_COUNTRY_CODE) { try { $firstNumberProto = new PhoneNumber(); $secondNumberProto = new PhoneNumber(); $this->parseHelper($firstNumberIn, null, false, false, $firstNumberProto); $this->parseHelper($secondNumberIn, null, false, false, $secondNumberProto); return $this->isNumberMatch($firstNumberProto, $secondNumberProto); } catch (NumberParseException $e3) { // Fall through and return MatchType::NOT_A_NUMBER } } } } } return MatchType::NOT_A_NUMBER; } if ($firstNumberIn instanceof PhoneNumber && is_string($secondNumberIn)) { // First see if the second number has an implicit country calling code, by attempting to parse // it. try { $secondNumberAsProto = $this->parse($secondNumberIn, self::UNKNOWN_REGION); return $this->isNumberMatch($firstNumberIn, $secondNumberAsProto); } catch (NumberParseException $e) { if ($e->getErrorType() === NumberParseException::INVALID_COUNTRY_CODE) { // The second number has no country calling code. EXACT_MATCH is no longer possible. // We parse it as if the region was the same as that for the first number, and if // EXACT_MATCH is returned, we replace this with NSN_MATCH. $firstNumberRegion = $this->getRegionCodeForCountryCode($firstNumberIn->getCountryCode()); try { if ($firstNumberRegion != self::UNKNOWN_REGION) { $secondNumberWithFirstNumberRegion = $this->parse($secondNumberIn, $firstNumberRegion); $match = $this->isNumberMatch($firstNumberIn, $secondNumberWithFirstNumberRegion); if ($match === MatchType::EXACT_MATCH) { return MatchType::NSN_MATCH; } return $match; } else { // If the first number didn't have a valid country calling code, then we parse the // second number without one as well. $secondNumberProto = new PhoneNumber(); $this->parseHelper($secondNumberIn, null, false, false, $secondNumberProto); return $this->isNumberMatch($firstNumberIn, $secondNumberProto); } } catch (NumberParseException $e2) { // Fall-through to return NOT_A_NUMBER. } } } } if ($firstNumberIn instanceof PhoneNumber && $secondNumberIn instanceof PhoneNumber) { // Make copies of the phone number so that the numbers passed in are not edited. $firstNumber = new PhoneNumber(); $firstNumber->mergeFrom($firstNumberIn); $secondNumber = new PhoneNumber(); $secondNumber->mergeFrom($secondNumberIn); // First clear raw_input, country_code_source and preferred_domestic_carrier_code fields and any // empty-string extensions so that we can use the proto-buffer equality method. $firstNumber->clearRawInput(); $firstNumber->clearCountryCodeSource(); $firstNumber->clearPreferredDomesticCarrierCode(); $secondNumber->clearRawInput(); $secondNumber->clearCountryCodeSource(); $secondNumber->clearPreferredDomesticCarrierCode(); if ($firstNumber->hasExtension() && mb_strlen($firstNumber->getExtension()) === 0) { $firstNumber->clearExtension(); } if ($secondNumber->hasExtension() && mb_strlen($secondNumber->getExtension()) === 0) { $secondNumber->clearExtension(); } // Early exit if both had extensions and these are different. if ($firstNumber->hasExtension() && $secondNumber->hasExtension() && $firstNumber->getExtension() != $secondNumber->getExtension()) { return MatchType::NO_MATCH; } $firstNumberCountryCode = $firstNumber->getCountryCode(); $secondNumberCountryCode = $secondNumber->getCountryCode(); // Both had country_code specified. if ($firstNumberCountryCode != 0 && $secondNumberCountryCode != 0) { if ($firstNumber->equals($secondNumber)) { return MatchType::EXACT_MATCH; } elseif ($firstNumberCountryCode == $secondNumberCountryCode && $this->isNationalNumberSuffixOfTheOther($firstNumber, $secondNumber)) { // A SHORT_NSN_MATCH occurs if there is a difference because of the presence or absence of // an 'Italian leading zero', the presence or absence of an extension, or one NSN being a // shorter variant of the other. return MatchType::SHORT_NSN_MATCH; } // This is not a match. return MatchType::NO_MATCH; } // Checks cases where one or both country_code fields were not specified. To make equality // checks easier, we first set the country_code fields to be equal. $firstNumber->setCountryCode($secondNumberCountryCode); // If all else was the same, then this is an NSN_MATCH. if ($firstNumber->equals($secondNumber)) { return MatchType::NSN_MATCH; } if ($this->isNationalNumberSuffixOfTheOther($firstNumber, $secondNumber)) { return MatchType::SHORT_NSN_MATCH; } return MatchType::NO_MATCH; } return MatchType::NOT_A_NUMBER; }
/** * Gets the length of the national destination code (NDC) from the PhoneNumber object passed in, * so that clients could use it to split a national significant number into NDC and subscriber * number. The NDC of a phone number is normally the first group of digit(s) right after the * country calling code when the number is formatted in the international format, if there is a * subscriber number part that follows. An example of how this could be used: * * <pre> * PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); * PhoneNumber number = phoneUtil.parse("18002530000", "US"); * String nationalSignificantNumber = phoneUtil.getNationalSignificantNumber(number); * String nationalDestinationCode; * String subscriberNumber; * * int nationalDestinationCodeLength = phoneUtil.getLengthOfNationalDestinationCode(number); * if (nationalDestinationCodeLength > 0) { * nationalDestinationCode = nationalSignificantNumber.substring(0, * nationalDestinationCodeLength); * subscriberNumber = nationalSignificantNumber.substring(nationalDestinationCodeLength); * } else { * nationalDestinationCode = ""; * subscriberNumber = nationalSignificantNumber; * } * </pre> * * Refer to the unittests to see the difference between this function and * {@link #getLengthOfGeographicalAreaCode}. * * @param \libphonenumber\PhoneNumber $number the PhoneNumber object for which clients want to know the length of the NDC. * @return int the length of NDC of the PhoneNumber object passed in. */ public function getLengthOfNationalDestinationCode(PhoneNumber $number) { if ($number->hasExtension()) { // We don't want to alter the proto given to us, but we don't want to include the extension // when we format it, so we copy it and clear the extension here. $copiedProto = new PhoneNumber(); $copiedProto->mergeFrom($number); $copiedProto->clearExtension(); } else { $copiedProto = clone $number; } $nationalSignificantNumber = $this->format($copiedProto, PhoneNumberFormat::INTERNATIONAL); $numberGroups = preg_split('/' . self::NON_DIGITS_PATTERN . '/', $nationalSignificantNumber); // The pattern will start with "+COUNTRY_CODE " so the first group will always be the empty // string (before the + symbol) and the second group will be the country calling code. The third // group will be area code if it is not the last group. if (count($numberGroups) <= 3) { return 0; } if ($this->getRegionCodeForCountryCode($number->getCountryCode()) === "AR" && $this->getNumberType($number) == PhoneNumberType::MOBILE) { // Argentinian mobile numbers, when formatted in the international format, are in the form of // +54 9 NDC XXXX.... As a result, we take the length of the third group (NDC) and add 1 for // the digit 9, which also forms part of the national significant number. // TODO: Investigate the possibility of better modeling the metadata to make it // easier to obtain the NDC. return strlen($numberGroups[3]) + 1; } return strlen($numberGroups[2]); }