はじめに
Webアプリケーション開発において「入力フォームの文字数制限」は極めて一般的な要件です。しかし、この単純に見える要件が、モダンなWeb開発、特に絵文字やマルチプラットフォームを考慮した環境では、エンジニアを苦しめる罠へと豹変します。
今回は、PHP(Laravel)とバニラJavaScriptで構成されたシステムで、筆者が実際に直面した「文字数カウント不整合問題」とその解決策について共有します。
1. 改行コードがカウントを狂わせる
最初の罠は「改行コード」です。 JavaScript(ブラウザ上)のテキストエリアでは、改行は通常 \n(1文字)としてカウントされます。しかし、フォームを送信(POST)すると、ブラウザによっては \r\n(2文字)に変換して送信されます。
これにより、「画面上のカウントでは制限内なのに、サーバーサイドのバリデーションでは文字数オーバーで弾かれる」という不条理なユーザー体験が発生します。
2. 絵文字における「1文字」の解釈違い
さらに深刻なのが絵文字の扱いです。JavaScriptの .length と、PHPの標準的な mb_strlen では、絵文字の数え方が根本的に異なります。
| 絵文字の例 | JavaScript .length | PHP mb_strlen | 内容 |
| 😀 (にっこり) | 2 | 1 | サロゲートペア |
| 🇯🇵 (国旗) | 4 | 2 | 結合文字 |
| 👨👩👧👦 (家族) | 11 | 7 | ゼロ幅接合子(ZWJ) |
このように、家族の絵文字にいたっては、JSでは11文字と判定される一方で、PHPでは7文字。仕様書に「30文字以内」と書かれていても、どの物差しで測るかによって実装は崩壊します。
3. 解決策:Grapheme(書記素)の使用
これらを「見た目通りの1文字」としてカウントするためには、Grapheme(書記素クラスタ)という概念を導入する必要があります。
- フロントエンド(JS):
Intl.Segmenter(モダンブラウザ)や、書記素を扱うライブラリ(grapheme-splitter等)を使用する。 - サーバーサイド(PHP):
intl拡張モジュールのgrapheme_strlen()関数を使用する。
これにより、上記の絵文字、改行はいずれも正確に「1文字」としてカウントすることが可能になります。
4. インフラ層への波及という最大の壁
今回の対応で最も留意すべきは、「プログラムの修正だけでは解決しない場合がある」という点です。
PHPの intl 拡張モジュールは OS レベルのライブラリ(libicu)に依存します。本番環境がゴールデンイメージ等でガチガチに固められている場合、このモジュールを導入するために EC2 インスタンスの再構築やインフラ構成の変更が必要になるケースがあります。
「たかが文字数カウント」と侮るなかれ。この実装一つで、インフラエンジニアを巻き込んだ大掛かりな調整が必要になることもあるのです。
まとめ
- 画面上に文字数カウントを表示する場合は、必ずフロント・バック両方の「カウントロジック」を揃えること。
- 絵文字や改行を厳密に扱うなら
Graphemeを検討すること。 - 使用する言語やモジュールが OS レベルのライブラリに依存していないか、初期段階で確認すること。