【FuelPHP】Validationでmax_lengthとかmax_minとかexact_lengthを使うときの罠

投稿日:

あんまり自分にとってPPAPは響かないです。

今回はFuelPHPのValideationで少しムムッ!?となった所があったのでメモしておきます。
なお、FuelPHPのバージョンは1.7.2です。
※ 1.8でも一緒だと思います

例えば以下の様なviewがあったとして

  • 改行コードはLF
  • 簡易入力文字数カウンターがある
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
</head>
<body>
  <form method="post">
    <textarea name="message" rows="10" cols="50" id="t"><?= $message; ?></textarea><br>
    <input type="submit">  入力文字数カウンター:<span id="c"></span>
  </form>
  <script>
  var target = document.querySelector('#t');
  var counter = document.querySelector('#c');

  counter.innerHTML = target.value.length;

  target.addEventListener('keyup', function(e){
    counter.innerHTML = e.currentTarget.value.length;
  });
  </script>
</body>
</html>

コントローラとかでこんなバリデーションをするとします。

  • 入力文字は3文字(3byte)まで許可する
  • 文字列はマルチバイトも1byteとカウントする
  • 改行も1byte
$val = Validation::forge();
$val->add('message')->add_rule('max_length', 3);

if (!$val->run()) {
  echo 'error!!';
  exit;
}

echo 'おk';

そしてテキストエリアに

a
b
※改行のみ

と入力します。
(カウンターには 3 と表示されている)
ほいで、送信。

すると、あら不思議。

error!!

と表示される(はず)。

つまり、上記の文字数はPHP的に3より多いと思われるようです。。

Why??何故なんだぜ???

jsでは3ってちゃんとでてるじゃあーりませんか。
クライアント側(ブラウザ)はwindowsでもmacでもやったけど結果は一緒。

種明かし

そんなときはソースを見てみます。

まず、Validationでルールを定義して実行すると以下が呼ばれる様です。

fuel\core\classes\validation.php

上記例add_rule('max_length', 3)だと、_validation_max_lengthが呼ばれます。

class Validation
{

  // ~ 略 ~

  /**
   * Minimum string length
   *
   * @param   string
   * @param   int
   * @return  bool
   */
  public function _validation_min_length($val, $length)
  {
  	return $this->_empty($val) || \Str::length($val) >= $length;
  }

  /**
   * Maximum string length
   *
   * @param   string
   * @param   int
   * @return  bool
   */
  public function _validation_max_length($val, $length)
  {
  	return $this->_empty($val) || \Str::length($val) <= $length; 
  }

  /** 
   * Exact string length 
   * 
   * @param string 
   * @param int 
   * @return bool 
   */ 
  public function _validation_exact_length($val, $length) 
  { 
    return $this->_empty($val) || \Str::length($val) == $length;
  }

  // ~ 略 ~
}

ふむふむ、中では\Str::length($val)を使って文字列の長さを得ている様です。
では続けて見てみます。

\Str::length($val)

fuel\core\classes\str.php

class Str
{

  // ~ 略 ~

  /**
   * strlen
   *
   * @param   string  $str       required
   * @param   string  $encoding  default UTF-8
   * @return  int
   */
  public static function length($str, $encoding = null)
  {
    $encoding or $encoding = \Fuel::$encoding;

    return function_exists('mb_strlen')
      ? mb_strlen($str, $encoding)
      : strlen($str);
  }

  // ~ 略 ~
}

ほうほう。mb_strlenで文字列の長さを返しているようです。
mb_strlenを調べてみると改行コードがCRLFの場合、2(byte)でカウントされるとの事。

これや

原因はこれやで。
冒頭で、error!!となったのは改行を2byteでカウントしている(1 + 2 + 1 で 4byte)からなんですね。
textareaから送信される値の改行コードがCRLFになってるっぽい。
windowsでもmacでも結果は一緒だったからブラウザ依存でもなさそうです。
※ ファイルの改行コードも関係ありませんでした

そしてさらに職場の人に教えてもらったのですが、要素がinput text以外の値は、送信時?改行コードがCRLFになるという事でした。(その人も片手間で調べてくれたので確かでは無いですが、少なくともtextareaはそうでした)

なんということでしょう。つまり、改行の含まれる可能性がある値(submitされたもの)を受け取って文字数チェックをする時は上記を意識しないといけません。

バリデーションの修正

というわけで原因が判明しました。
改行コードを\r\n(CRLF)から\n(LF)に変換すればいいわけです。
変換した値をValidationに渡す様にして、再度実行してみます。

runメソッドは配列(key => value)を渡すことでバリデーションする値を上書きできます。

$val = Validation::forge();
$val->add('message')->add_rule('max_length', 3);

if (!$val->run(['message' => str_replace("\r\n", "\n", Input::post('message', ''))])) {
  echo 'error!!';
  exit;
}

echo 'おk';

無事 おk とでるはず。
\Str::lengthに依存している、min_length, exact_lengthも同様です

でも納得いかない

これだと、どの要素から送信されているか意識しないといけなくなるじゃない?
というか今までどおだったっけ?とか、他のフレームワークとかどうしてるんだろう?上手いこと吸収してくれてるんだろうか?他の人は?
など、悩みはつきないし、かなり腑に落ちない。

もしかしたら、ユースケースとか、そもそもの使い方が間違ってるのかも。
どなたか教えてください。

まとめ

改行が含まれる可能性のあるtextareaなどから送信されている文字列の長さを調べる時は気をつける。

では、ペンパイナッポーアッポーペン!!

投稿日:
カテゴリー: PHP タグ:

作成者: shimabox

Web系のプログラマをやっています。 なるべく楽しく生きていきたい。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください