country calling code, including shared calling codes. */ public static function regions(): array { static $regions = null; if ( $regions === null ) { $regions = array(); $util = self::util(); foreach ( $util->getSupportedRegions() as $region ) { $regions[ $region ] = $util->getCountryCodeForRegion( $region ); } ksort( $regions ); } return $regions; } /** New numbers only: never infer a national region from language or location. */ public static function normalize( string $raw, string $region = '' ): string { $region = strtoupper( trim( $region ) ); if ( strlen( $raw ) > 256 || ( $region !== '' && ! isset( self::regions()[ $region ] ) ) ) { throw new \DomainException( 'phone_invalid' ); } // Reject controls, invisible format marks (including bidi), and invalid UTF-8. if ( ! preg_match( '//u', $raw ) || preg_match( '/[\p{Cc}\p{Cf}]/u', $raw ) ) { throw new \DomainException( 'phone_invalid' ); } if ( class_exists( '\\IntlChar' ) ) { $raw = preg_replace_callback( '/\p{Nd}/u', static function ( $match ) { $digit = \IntlChar::digit( $match[0] ); return is_int( $digit ) && $digit >= 0 && $digit <= 9 ? (string) $digit : $match[0]; }, $raw ); } $map = array( '+' => '+' ); foreach ( array( '0123456789', '٠١٢٣٤٥٦٧٨٩', '۰۱۲۳۴۵۶۷۸۹', '०१२३४५६७८९' ) as $alphabet ) { foreach ( preg_split( '//u', $alphabet, -1, PREG_SPLIT_NO_EMPTY ) as $digit => $character ) { $map[ $character ] = (string) $digit; } } $number = strtr( $raw, $map ); $number = preg_replace( '/\p{Zs}/u', ' ', $number ); $number = trim( $number ); // A full-string grammar prevents the parser extracting a number from text, // accepting vanity letters, silently dropping extensions or injection text. if ( ! preg_match( '/^\+?[0-9 ().-]+$/D', $number ) || ( $region === '' && $number[0] !== '+' ) ) { throw new \DomainException( 'phone_invalid' ); } try { $util = self::util(); $parsed = $util->parse( $number, $region === '' ? null : $region ); if ( $parsed->hasExtension() || ! $util->isValidNumber( $parsed ) ) { throw new \DomainException( 'phone_invalid' ); } return $util->format( $parsed, PhoneNumberFormat::E164 ); } catch ( \Exception $error ) { throw new \DomainException( 'phone_invalid' ); } } /** Friendly international display; preserve unparseable historical values. */ public static function format( string $raw ): string { try { $canonical = self::normalize( $raw ); $util = self::util(); return $util->format( $util->parse( $canonical, null ), PhoneNumberFormat::INTERNATIONAL ); } catch ( \DomainException $error ) { return $raw; } } }