振り返ると結局今年は花見しませんでした。
またFuelPHPの話。
よくある話?
View(Presenter)にクラスをセットしてレンダリングしたら、
RuntimeException [ Error ]:
Object class “XXXXX” could not be converted to string or sanitized as ArrayAccess. Whitelist it in security.whitelisted_classes in app/config/config.php to allow it to be passed unchecked.
こんなエラーになった事、誰もがあるかと思います。
で、このメッセージを見ると、はっはーんapp/config/config.phpのwhitelisted_classesにXXXXXを追加すればいいんやな、はっはーん、となります。
もうちょい調べてみる
が、もう少し調べてみると FuelPHPのwhitelisted_classesにクラスを追加する人はどういう影響があるかきちんと把握しましょう — A Day in Serenity (Reloaded) — PHP, FuelPHP, Linux or something この様な素晴らしい記事に巡りあえます。
簡単にまとめてみると、
whitelisted_classesに追加されたクラスはViewで使えるようになるけど、出力される値は自動でエスケープされない- その為、何かしら値を表示する時、自分でエスケープしないといけない
- Sanitizationインターフェイスを実装し対応すれば、自動でエスケープされる
- 出力するプロパティはゲッター、マジックメソッドなどを介する事
こんな感じかと思います。
簡単な例
<?php
/**
* sample
*/
class Model_User
{
protected $name = '';
public function get_name()
{
return $this->name;
}
public function set_name($in)
{
$this->name = $in;
}
}
こんなクラスがあったとしたら、
<?php
/**
* sample
*/
class Model_User implements Sanitization
{
protected $name = '';
public function get_name()
{
if ($this->_sanitization_enabled)
{
return Security::htmlentities($this->name);
}
return $this->name;
}
public function set_name($in)
{
$this->name = $in;
}
/*
|--------------------------------------------------------------------------
| view(presenter)に直接渡すためにはSanitizationを実装する必要がある
|--------------------------------------------------------------------------
| @link http://blog.a-way-out.net/blog/2014/02/19/fuelphp-whitelisted_classes-and-sanitization-interface/
|
*/
private $_sanitization_enabled = false;
public function sanitize()
{
$this->_sanitization_enabled = true;
return $this;
}
public function sanitized()
{
return $this->_sanitization_enabled;
}
public function unsanitize()
{
$this->_sanitization_enabled = false;
return $this;
}
}
こう書けばいい事になります。
で、view側はこう書く。
Presenter
<?php
/**
* Sample
*/
class Presenter_Sample_Index extends Presenter
{
public function view()
{
$user = new Model_User();
$user->set_name("<script>alert(1);</script>");
$this->set('user', $user); // $this->user = $user; でもいい
}
}
template
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Sample</title> </head> <body> <?php echo $user->get_name(); ?> </body> </html>
こんな感じで、e()使わなくても勝手にエスケープしてくれます。
一応確認として、プレゼンター側をこうすると、
<?php
/**
* Sample
*/
class Presenter_Sample_Index extends Presenter
{
public function view()
{
$user = new Model_User();
$user->set_name("<script>alert(1);</script>");
$this->set('user', $user, false);
}
}
$this->_sanitization_enabledはfalseとなり、エスケープはすっ飛ばされるので
public function get_name()
{
if ($this->_sanitization_enabled)
{
return Security::htmlentities($this->name);
}
// ここにくる
return $this->name;
}
view側ではエスケープしないとあかんという事が分かります。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Sample</title> </head> <body> <?php echo e($user->get_name()); ?> </body> </html>
こんな感じで上手い事使えば、エスケープのコントロールも出来ますね。
Viewで使いたいクラスはこんな感じにすればOK!!
※ なお、stdClassはそのままセット出来る模様
・・・でも、これ対象のクラスに対して全部同じ事(Sanitizationの実装部分)書くの??
それはかったるい。DRYじゃな〜い。
・・・適当な親クラス作ってそれを継承??
それもなんだかなぁ〜。
で、そこで trait ですよ。
traitを使う
traitは誤解を恐れずに言うと、クラスの中でクラスをinclude出来るイメージです。
※あくまでもイメージで、実際traitはクラスではないです
便利な機能を持ったクラス(っぽいもの)を継承でもなく、移譲でもなく使える様になる。みたいな。
縦に繋がるんじゃなくて、横に繋げられる?みたいな。
こんなゆるふわな感じで自分はとりあえず理解しています。
もちろん、ちゃんとリファレンス見たほうがいいです。
で、traitは PHP5.4 から使えます。
traitを使った例
上記例でSanitizationを実装していた箇所を抜き出してtraitとして定義します。
※場所はどこでもいいですがここではmodel直下
<?php
/**
* Sanitization実装 trait
*
* (PHP 5 >= 5.4.0)
*
*/
trait Model_TraitSanitization
{
/*
|--------------------------------------------------------------------------
| view(presenter)に直接渡すためにはSanitizationを実装する必要がある
|--------------------------------------------------------------------------
| @link http://blog.a-way-out.net/blog/2014/02/19/fuelphp-whitelisted_classes-and-sanitization-interface/
|
*/
private $_sanitization_enabled = false;
public function sanitize()
{
$this->_sanitization_enabled = true;
return $this;
}
public function sanitized()
{
return $this->_sanitization_enabled;
}
public function unsanitize()
{
$this->_sanitization_enabled = false;
return $this;
}
}
Viewで使いたいクラスはこう書きます。
<?php
/**
* sample
*/
class Model_User implements Sanitization
{
use Model_TraitSanitization;
protected $name = '';
public function get_name()
{
if ($this->_sanitization_enabled)
{
return Security::htmlentities($this->name);
}
return $this->name;
}
public function set_name($in)
{
$this->name = $in;
}
}
こうすると、最初に挙げた例の様にViewにセット出来るオブジェクトの出来上がりです。
これでもしViewで直接使いたいクラスが増えても、同じ様にtraitを使えばいいという事になります。
結構DRYになったし、記述量も減って劇的ビフォーアフターですね。※劇的ほどでもないか
traitは横に繋がる感じも何となく分かる気がしますよね。
継承はいや、移譲もいや、でもあんたが欲しい。
そんな時traitが便利な気がします。
でも使いすぎると、どこで実装されているのか分かり辛くなるので注意が必要かと思います。
まとめ
traitは上手く使えば捗る
※で、ここまで書いておいてなんだけど、何か理由が無い限り出力するものは必ずエスケープする、という事にしていればwhitelisted_classesにひたすら追加でもいい気がしないでもないですね