TwigとDoctrineの相性
昨年度からずーっとやっていた「DoctrineとTwig」をどうにか連携(単に使うだけ)したい!
と思ってて先程なにやら面倒なことになったのでメモ的に書いてみます。
先に言っておくと、「DoctrineとTwigの相性が完全に悪い」というわけでもなく、「DoctrineとTwigの相性は最高だ!」というわけでもありません。
DoctrineとTwigで発生したのは以下のような問題。
Doctrineでyml(※1)から生成したモデルからデータを抽出し、抽出したデータをView(※2)の変数に突っ込み、index.phtmlで変数を利用出来るようにして、いざアクセスしてみたらnullだよ!
※ここではController、Actionともにindexであるとします。
※1今回利用しているDoctrineで生成したモデルは、「1つのデータを持ってる(hasOne)」の関係性で行っています。
※2Viewは以前紹介した「Zend FrameworkのViewをTwigで拡張する」で紹介したコードをそのまま使っています。
モデルのschema.ymlは以下のとおりです
AdminUsers:columns:
id:
type: integer(20)
autoincrement: true
unsigned: true
primary: true
auth_role:
type: integer(1)
notnull: true
unsigned: true
relations:
Roles:
class: Roles
foreign: id
local: auth_role
Roles:
columns:
id:
type: integer(1)
unsigned: true
primary: true
autoincrement: true
role_name:
type: string(100)
notnull: true
relations:
class: AdminUsers
foreign: auth_role
local: id
上記の用にymlを記述しました。
端折れる物ははしょって書いてますので、カラムはめちゃくちゃ少ないです。
AdminUsersはロール(権限)を一つ持っている
RolesはAdminUsersによって一つ持たれている
という関係(?)としています。
IndexControllerのindexActionは
IndexControllerのindexActionは以下のとおりです。
AdminUsers::fetchAll()はDoctrine_Query::create()->from("AdminUsers")->execute();です。
indexAction() {$this->view->users = AdminUsers::fetchAll();
// 又は Doctrine_Query::create()->from("AdminUsers")->execute();でも可
}
index.phtmlの中身
index.phtmlも簡単で、以下のとおりになります。
<table style="width: 100px;" border="0" cellspacing="0" cellpadding="0"><tbody>
<tr>
<th>ID</th>
<th>Role</th>
</tr>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.Roles["role_name"] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
これで実際に表示してみると、本来出て欲しい「Roles」の部分が全部表示されていないと思います。
理由
表示されない理由としては、Twig_Resource::getAttribute()で、$objectがis_object()で且つ「isset($object->$item)」ならばという箇所が有ります。
この「isset()」が呼ばれると、 Doctrine側では、Doctrine_Record::constain()というメソッドが呼ばれ、Doctrine_Access::__get()より先に読み込まれる為、そうなるのかなぁと個人的な見解ではりますが...
回避(?)
これを回避するには、Twig_Resource::getAttribute()に記述されている以下の文を次のように修正すると回避できますけど、バージョン上がったときにかなり苦しみます。
if (is_object($object) && isset($object->$item)){
if ($this->env->hasExtension('sandbox'))
{
$this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
}
return $object->$item;
}
を
if (is_object($object) &&(is_array($object->$item) || is_numeric($object->$item) || is_string($object->$item) || is_bool($object->$item) || is_array($object->$item)[...]))//if (is_object($object) && isset($object->$item))
{
if ($this->env->hasExtension('sandbox'))
{
$this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
}
return $object->$item;
}
みたいに力技で!
!empty()でもいいんじゃ?と思われるかもしれませんが、現在のPHPの仕様としては、__isset()はempty()及びisset()が呼ばれたらコールされるので、emptyもダメでした。
終りに
こんな理由があるので、併用は現在のところ厳しいかもしれません。
でも今後は誰か絶対回避してくれる人がいるはずなのでそれを待ち望みますw
※もしかすると私のDoctrineの書き方が甘くてこのような現象が発生しているかもしれません。
まだ検証しないとイケないとはおもいますが、データベースもまだまだあまちゃんなので、なんとも...といった感じなので、「ここおかしいだろぼけ!」や、「m9(^Д^)」 などが有りましたらコメントなどでご教授していただければと思います....