肉とビールとパンケーキ by @sotarok

少し大人になった「肉とご飯と甘いもの」

EthnaでUTF-8を使うとき、Validator Maxの自動検証が機能しなくなる

EthnaUTF-8を使う方法は、ググればじゃんじゃん出てくるのでいまさら書くまでもないのですが、ポイントは、

  1. テンプレートをUTF-8で記述すること
  2. Ethnaソースコード全部UTF-8化すること
  3. ActionErrorはUTF-8に変換して出力してやること(参考:http://d.hatena.ne.jp/riaf/20070627/1182941891

まぁ普通にやったら、これで大丈夫なんだけどさあ・・・。

ActionFormで

        'name'  => array(
            'type'          => VAR_TYPE_STRING,
            'name'          => 'なまえ',
            'required'      => false,
            'max'           => 32,
        ),

こんなことやると、正常にできない。最大文字数のチェック(Validator Max)が機能しません。

で、問題のソース(Ethna_Plugin_Validator_Max.php)がどうなっているかというと、

<?php
...
            case VAR_TYPE_STRING:
                if (strlen($var) > $params['max']) {
                    if (isset($params['error'])) {
                        $msg = $params['error'];
                    } else {
                        $msg = "{form}は全角%d文字以下(半角%d文字以下)で入力して下さい";
                    }
                    return Ethna::raiseNotice($msg, E_FORM_MAX_STRING,
                            array(intval($params['max']/2), $params['max']));
                }
                break;
...

そう、strlenを使っているんです。やりたいことは、「バイト数数えて、全角だったら半分にしてやりゃいいよねー?という方法をとっているのですが、これがまあ問題。

EUC-JPの場合、マルチバイトは2バイトですが、UTF-8の場合3バイトです、たしかね、だいたい3バイト(笑)

だから、アクションフォームを上記のように表記した場合、エラーメッセージは

なまえは全角16文字以下(半角32文字以下)で入力して下さい

って表示されるんだけど、実際に許可されるのは、全角10文字まで。11文字は33バイトになるからアウト。なぜならUTF-8だから(笑)



というわけで、しょうがないので、Ethna標準のValidator_Maxプラグインは使うのやめて、app/plugin/Validator の中に「{APPID}_Plugin_Validator_Max.php」を作ってあげて、以下のように変更する。(変更する部分以外はそのまま使う)

<?php

class {APPID}_Plugin_Validator_Max extends Ethna_Plugin_Validator
{
...
            case VAR_TYPE_STRING:
                if (strlen($var)*2/3 > $params['max']) {
                    if (isset($params['error'])) {
                        $msg = $params['error'];
                    } else {
                        $msg = "{form}は全角%d文字以下(半角%d文字以下)で入力して下さい";
                    }
                    return Ethna::raiseNotice($msg, E_FORM_MAX_STRING,
                            array(intval($params['max']/2), $params['max']));
                }
                break;
...
}

たぶん、これでUTF-8で大丈夫。ていうかUTF-8でしかダメ(笑)超その場しのぎ。(だからボツ。)
Ethnaの今の仕様にあわせるならこういう風になる。たぶん今の仕様っていうのは、EUCで使うことを前提として、strlenしてるんだろうと思うので。。


というか、普通に考えたら、やっぱりマルチバイト環境ではstrlenを使うべきではないのではないか。。このあたりは、気になったから自分でも検証してみたけど、strlenでは文字コードによって長さ違っちゃうし。mb_strlenは文字数だしね。MySQLもそう。

アプリケーションのポリシーにもよるんだけど、「全角16文字以下(半角32文字以下)」のようにチェックをかけたい場合、

  • 最大文字数は半角の文字数
  • 全角はその半分

という仕様にしたい場合は、

<?php

class {APPID}_Plugin_Validator_Max extends Ethna_Plugin_Validator
{
...
            case VAR_TYPE_STRING:
                if (mb_strwidth($var) > $params['max']) {
                    if (isset($params['error'])) {
                        $msg = $params['error'];
                    } else {
                        $msg = "{form}は全角%d文字以下(半角%d文字以下)で入力して下さい";
                    }
                    return Ethna::raiseNotice($msg, E_FORM_MAX_STRING,
                            array(intval($params['max']/2), $params['max']));
                }
                break;
...
}

のように、mb_strwidthにしてあげれば、なんとかなります。EUC-JPで使っても正常に動きます。(たぶん)

うん、これ採用。

パッチ投げようかな?とか思ったのだけど、なんか自信ないうえに、公式にはEthnaEUCで使うことになっているので、まあなんかアレですな。