はじめに
実務にて、フォームデータを電話番号入力チェックするバリデーション(入力値の検証)を実装する作業があり、それを解決した情報になります。Web 開発の初心者、初学者の方にもわかりやすいように、ソースコードを編集してサンプルを公開しています、ご参考になれば幸いです。
検証環境
サンプル
解説
サンプルのソースコードを基に、基本的な PCRE 正規表現構文 を用いて説明します。
固定電話番号のチェック
初めに、固定電話番号の仕様を理解する必要があります。
国内電話番号の仕様は、総務省の 電話番号制度 を参考にしています。
Q1 電話番号とはどのようなものですか? によると、普通の固定電話「0-市外局番-市内局番-加入者番号」以外にも下記のような番号が存在するとあります。
一般的に利用される電話番号は 0 から始まることがわかります。
Q2 市外局番・市内局番とはどのようなものですか? によると、電話番号の構成は下記のようになります。
固定電話番号は 10 桁、市外局番 + 市内局番は 5 桁、地域によって市内局番の桁数が変動することがわかります。
9 桁の電話番号は 日本における市外局番の変更 と総務省の 平成18年度に実施する電話番号の変更 よると、電話番号逼迫対策 によって消滅していることがわかるので除外します。
Q12 どうして市外局番や市内局番の番号が変更になったりするのですか? よると、一般的に単位料金で電話できる地域内では市外局番が同じであることから、これを省略して市内局番からダイヤルできるとあります。この仕様を含めて正規表現で対応すると複雑になってしまうので、placeholder 属性でユーザーに非省略の入力例を与えてあげるなどの対応をします。
さらに、下記の 2 点を考慮します。
- 市内局番の前後にハイフンが入る : 03-3463-xxxx
- 市内局番を丸括弧で囲む : 03(3463)xxxx
前述の仕様を満たすには、下記のように preg_match でチェックします。
function checkLandline(string $str): int|false {
return preg_match("/\A0(\d{1}[-(]?\d{4}|\d{2}[-(]?\d{3}|\d{3}[-(]?\d{2}|\d{4}[-(]?\d{1})[-)]?\d{4}\z/", $str);
}
下記の PCRE 正規表現構文を記述しています。
/
:デリミタ\A
:エスケープシーケンス 検索対象文字列の始端()
: サブパターン\d
: 10 進数字[0-9]
と同じ{}
: 繰り返し[]
: 文字クラス?
: 量指定子{0,1}
と同じ|
: 選択肢\z
:エスケープシーケンス 検索対象文字列の終端
正規表現を可視化すると下記のようになります。
下記の文字列とマッチします。
0123456789
01-2345-6789
012-345-6789
0123-45-6789
01234-5-6789
01(2345)6789
012(345)6789
0123(45)6789
01234(5)6789
下記の文字列とマッチしません。
012345678
01234567890
01-2345-678
012-34-56789
0-1234-5678
012345-6-7890
01)2345)6789
012(345(6789
0123((456789
012345))6789
しかし、この正規表現は下記の文字列とマッチしてしまいます。
01(2345-6789
012-345)6789
01(23456789
012345-6789
この問題を正規表現で解決しようとすると複雑化してデバッグが困難になります。
妥協ができない場合は、下記のような処理でハイフンと丸括弧を取り除いてから /\A0\d{9}\z/
に通すなどの対応を検討すると良いと思います。
function convertDelimiter(string $str): string {
return strtr($str, array_fill_keys(['-', '(', ')'], ''));
}
携帯電話番号のチェック
初めに、携帯電話番号の仕様を理解する必要があります。
Q5 携帯電話やPHSに電話するには? によると、携帯電話と PHS の番号は「070」、「080」又は「090」から始まる 11 桁とわかります。
特定 IP 電話番号の「050」から始まる 11 桁の番号に関しては、対象に含めます。
FMC(Fixed-Mobile Convergence)サービスの「060」から始まる 11 桁の番号に関しては、電気通信番号規則等の一部を改正する省令案に対する意見募集 より、携帯電話に本番号を開放するという発表がありましたが、現在は検討段階なので除外します。
M2M 通信等専用の電気通信番号 の「0201 ~ 3, 0205 ~ 9」から始まる 11 桁の番号に関しては除外します。
発信者課金ポケベルの「0204」から始まる 11 桁の番号に関しては、2019 年 9 月 30 日に東京テレメッセージ株式会社より個人向けの無線呼出サービスを終了すると 発表 があったので除外します。
さらに、下記の 3 点を考慮します。
- IP 電話 アプリの「050」から始まる 11 桁の番号
- ハイフンの有無 : 080-1234-5678
- 丸括弧の有無 : 080(1234)5678
前述の仕様を満たすには、下記のように preg_match でチェックします。
function checkPhoneNumber(string $str): int|false {
return preg_match("/\A0[5789]0[-(]?\d{4}[-)]?\d{4}\z/", $str);
}
下記の PCRE 正規表現構文を記述しています。
/
:デリミタ\A
:エスケープシーケンス 検索対象文字列の始端[]
: 文字クラス()
: サブパターン\d
: エスケープシーケンス[0-9]
と同じ{}
: 繰り返し?
: 量指定子{0,1}
と同じ|
: 選択肢\z
:エスケープシーケンス 検索対象文字列の終端
正規表現を可視化すると下記のようになります。
下記の文字列とマッチします。
05012345678
07012345678
08012345678
09012345678
050-1234-5678
070-1234-5678
080-1234-5678
090-1234-5678
050(1234)5678
070(1234)5678
080(1234)5678
090(1234)5678
下記の文字列とマッチしません。
06012345678
0501234567
050123456789
050-123-5678
050-1234-567
070-12345-6789
070-1234-56789
080-1-2345678
080-1234567-8
090)1234)5678
090(1234(5678
090-1234--5678
090--1234-5678
しかし、この正規表現は下記の文字列とマッチしてしまいます。
090(1234-5678
090-1234)5678
090(12345678
0901234-5678
この問題を正規表現で解決しようとすると複雑化してデバッグが困難になります。
妥協ができない場合は、ハイフンと丸括弧を取り除いてから /\A0\d{10}\z/
に通すなどの対応を検討すると良いと思います。
フリーダイヤルのチェック
初めに、フリーダイヤルの仕様を理解する必要があります。
フリーダイヤル という言葉は NTT コミュニケーションズの登録商標であり和製英語です。英語では toll-free number、Freephone、Freecall などと呼ばれます。
Q6「0△△0」型の電話番号はどのように使われているのですか? によると、料金着信払い通話(フリーフォンサービス)用の「0120」から始まる 10 桁、「0800」から始まる 11 桁、情報料回収代行の「0990」から始まる 10 桁、テレドーム の「0180」から始まる 10 桁、ナビダイヤル の「0570」から始まる 10 桁の番号があるとわかります。
テレゴング の「0180」から始まる 10 桁の番号は、2012 年 5 月 31 日にサービス終了しています。
静岡県御殿場市と静岡県駿東郡小山町の市外局番は「0550」から始まる番号なので、「0AB0 から始まる番号(A, B は 0 以外)」の条件を満たしていることに注意します。
また、フリーダイヤルは下記のように登録者が好きな位置で番号を区切ることができます。
0120 – A – BCDEF
0120 – AB – CDEF
0120 – ABC – DEF
0120 – ABCD – EF
0120 – ABCDE – F
0120 – ABCDEF
この仕様に関しては、全てに対応すると正規表現が複雑化するので、10 桁なら 4 桁 – 3 桁 – 3 桁、11 桁なら 4 桁 – 3 桁 – 4桁 のケースに対応します。
前述の仕様を満たすには、下記のように preg_match でチェックします。
function checkFreephoneNumber(string $str): int|false {
return preg_match("/\A0((12|99|18|57)0[-(]?\d{3}[-)]?\d{3}|800[-(]?\d{3}[-)]?\d{4})\z/", $str);
}
下記の PCRE 正規表現構文を記述しています。
/
:デリミタ\A
:エスケープシーケンス 検索対象文字列の始端()
: サブパターン|
: 選択肢[]
: 文字クラス\d
: エスケープシーケンス[0-9]
と同じ{}
: 繰り返し?
: 量指定子{0,1}
と同じ\z
:エスケープシーケンス 検索対象文字列の終端
正規表現を可視化すると下記のようになります。
下記の文字列とマッチします。
0120123456
08001234567
0990123456
0180123456
0570123456
0120-123-456
0800-123-4567
0990-123-456
0180-123-456
0570-123-456
0120(123)456
0800(123)4567
0990(123)456
0180(123)456
0570(123)456
下記の文字列とマッチしません。
0550123456
012012345
01201234567
0800123456
080012345678
0000-123-456
0000-123-4567
0120-123-4567
0800-123-456
0570-123--456
0570((123)456
0(120)123456
080(0123)4567
0990)123)456
0990(123(456
018(012)3456
しかし、この正規表現は下記の文字列とマッチしてしまいます。
0120(123-456
0120-123)456
0120(123456
0120123-456
この問題を正規表現で解決しようとすると複雑化してデバッグが困難になります。
妥協ができない場合は、ハイフンと丸括弧を取り除いてから /\A0((12|99|18|57)0\d{6}|800\d{7})\z/
に通すなどの対応を検討すると良いと思います。
固定電話番号 + 携帯電話番号
下記のように、固定電話番号の正規表現に |
(選択肢)で携帯電話番号の正規表現を追加します。
function checkPhoneNumber2(string $str): int|false {
return preg_match("/\A0(\d{1}[-(]?\d{4}|\d{2}[-(]?\d{3}|\d{3}[-(]?\d{2}|\d{4}[-(]?\d{1}|[5789]0[-(]?\d{4})[-)]?\d{4}\z/", $str);
}
正規表現を可視化すると下記のようになります。
下記の文字列とマッチします。
0123456789
01-2345-6789
012-345-6789
0123-45-6789
01234-5-6789
01(2345)6789
012(345)6789
0123(45)6789
01234(5)6789
05012345678
07012345678
08012345678
09012345678
050-1234-5678
070-1234-5678
080-1234-5678
090-1234-5678
050(1234)5678
070(1234)5678
080(1234)5678
090(1234)5678
下記の文字列とマッチしません。
012345678
01234567890
01-2345-678
012-34-56789
0-1234-5678
012345-6-7890
01)2345)6789
012(345(6789
0123((456789
012345))6789
06012345678
050123456789
050-1234-567
070-12345-6789
070-1234-56789
080-1-2345678
080-1234567-8
090)1234)5678
090(1234(5678
090-1234--5678
090--1234-5678
しかし、この正規表現は下記の文字列とマッチしてしまいます。
01(2345-6789
012-345)6789
01(23456789
012345-6789
090(1234-5678
090-1234)5678
090(12345678
0901234-5678
妥協ができない場合は、ハイフンと丸括弧を取り除いてから /\A0\d{9,10}\z/
に通すなどの対応を検討すると良いと思います。
固定電話番号 + 携帯電話番号 + フリーダイヤル
下記のように、固定電話番号の正規表現に |
(選択肢)で携帯電話番号とフリーダイヤルの正規表現を追加します。
function checkPhoneNumber3(string $str): int|false {
return preg_match("/\A0((\d{1}[-(]?\d{4}|\d{2}[-(]?\d{3}|\d{3}[-(]?\d{2}|\d{4}[-(]?\d{1}|[5789]0[-(]?\d{4}|800[-(]?\d{3})[-)]?\d{4}|(12|99|18|57)0[-(]?\d{3}[-)]?\d{3})\z/", $str);
}
正規表現を可視化すると下記のようになります。
下記の文字列とマッチします。
0123456789
01-2345-6789
012-345-6789
0123-45-6789
01234-5-6789
01(2345)6789
012(345)6789
0123(45)6789
01234(5)6789
05012345678
07012345678
08012345678
09012345678
050-1234-5678
070-1234-5678
080-1234-5678
090-1234-5678
050(1234)5678
070(1234)5678
080(1234)5678
090(1234)5678
0120123456
08001234567
0990123456
0180123456
0570123456
0120-123-456
0800-123-4567
0990-123-456
0180-123-456
0570-123-456
0120(123)456
0800(123)4567
0990(123)456
0180(123)456
0570(123)456
下記の文字列とマッチしません。
012345678
01234567890
01-2345-678
012-34-56789
0-1234-5678
012345-6-7890
01)2345)6789
012(345(6789
0123((456789
012345))6789
06012345678
050123456789
050-1234-567
070-12345-6789
070-1234-56789
080-1-2345678
080-1234567-8
090)1234)5678
090(1234(5678
090-1234--5678
090--1234-5678
012012345
01201234567
080012345678
0000-123-456
0000-123-4567
0120-123-4567
0800-123-456
0570-123--456
0570((123)456
0(120)123456
0990)123)456
0990(123(456
しかし、この正規表現は下記の文字列とマッチしてしまいます。
0120(123-456
0120-123)456
0120(123456
0120123-456
01(2345-6789
012-345)6789
01(23456789
012345-6789
090(1234-5678
090-1234)5678
090(12345678
0901234-5678
妥協ができない場合は、ハイフンと丸括弧を取り除いてから /\A0\d{9,10}\z/
に通すなどの対応を検討すると良いと思います。
まとめ
電話番号の仕様は時代とともに変化していて、サービスを利用しているユーザーによってチェックの最適な方法は変わります。完璧な正規表現を目指して、工数と不具合を増やしてしまうのは過ぎたるは猶及ばざるが如しです。
大雑把で良いなら /\A0\d{1,4}[-(]?\d{1,4}[-)]?\d{3,4}\z/
でも問題はないと思います。
以上です。
おわりに
サンプルのソースコードを再利用する際は、要件定義やコーディング規約にお気を付けください。