cakeのエレメントをDB管理する(変数に入ったhtml/phpコードをincludeする)

いくつかのエレメントファイルがview/elements/foo/以下にあって、それをDBで更新することになりました。
ctpの内容をDBの1フィールドに入れて、表示は $this->element(foo/bar)の代わりに DBから取ってきたデータをecho($foo['Model']['bar']);すれば良いかなって思ってたら、なんとbar.ctpの中に

<?php $this->element(baz/menu);?>

みたいな記述があるじゃありませんか!!
このままだと、単純にechoすると、phpのコードがそのままhtml出力されてしまうので対策しました。

結論からいうと、viewテンプレートの中でincludeで読み込めばOK。ただしincludeでファイルを読み込むわけじゃなく、変数に入ってるデータを読み込む必要があります。

ストリームラッパー

Twitterで変数に入れたデータをincludeしたいとつぶやいたら反応があり、ストリームラッパーを使えば変数に入れたコンテンツをincludeできるんじゃないかと、@iakioさんから教えてもらいました。ありがとうございました!

stream wrapperを使うと、includeやfopenでファイル以外にも色々な形式のものが開けて、http://から始まるURLや、ftp://, zlib://なんてものが透過的にread/writeできます。
http://www.php.net/manual/en/wrappers.php

標準のストリームラッパーを試す

data://というストリームがあったので下記のようにしたところうまくいきました。

<?php
$data = 'hello <?php echo "world"; ?>';
include('data://text/plain,'.$data);

ただし、php.iniにallow_url_fopen, allow_url_includeがONじゃないと動きません。この設定はOFFにしておきたかったので、しょうがなくストリームラッパーを作ることにしました。

ストリームラッパーを自作

下記URLに変数に入れたものをストリームラッパーで取り出すサンプルがあり、基本的にはこれで問題ありません。
http://php.net/manual/ja/stream.streamwrapper.example-1.php

使い方は下記のように。

<?php
stream_wrapper_register("var", "VariableStream");
$myvar = 'hello <?php echo "world"; ?>';
include("var://myvar");

$myvarの変数の中身がstream_wrapper_register()によって$GLOBAL['mybar']の中に格納され、VariableStreamクラスではそのGlobal変数を参照してread()メソッドの中でそれを返しているだけです。


ただ、cakephpのviewテンプレートの中でこれを使うと、GLOBAL変数に$myvarの値が格納されず、期待した動作になりませんでした。そこで、色々と改良し、下記のようにしました。

このファイルを、vendors/variable_stream.phpとして保存し、下記のようにすると動作します。

<?php
App::import('Vendor', 'VariableStream');

$val = 'Hello <?php echo "World"; ?>';
stream_wrapper_register("var", "VariableStream");
include("var://".urlencode($val));

includeで渡す値はurlencode必須になってます($valの中にphpタグが入っているとうまく動かないため)。内部ではurldecodeしてデータを扱ってます。

これで、elementのデータをDBから取ってきてviewの中で変数をincludeするだけでOKとなりました。