yiiのアクセス制御を学ぶ ログインの巻
YiiFrameworkのアクセスコントロールを学習したときのメモ。
ログインから始めロールベースアクセスコントロール、YiiRightsに辿り着きたい。素直にcookbook買うべきなのかもしれない。
ログイン
ユーザー認証。これがなくちゃ話にならない。最初に注意すべきなのは
情報: identity クラスと user アプリケーションコンポーネントはしばしば混同されます。前者は認証を行う方法のことであり、後者は現在のユーザに関する情報をあらわします。
http://www.yiiframework.com/doc/blog/1.1/ja/prototype.auth
というところか。
identity クラスとはCUserIdentityのこと。user アプリケーションコンポーネントとは CWebUserのこと。そして config/main.php の user ってのは、CWebUser のこと。
protected/config/main.php
<?php 'components'=>array( 'user'=>array( // enable cookie-based authentication 'allowAutoLogin'=>true, ), ),
userはアプリケーションでひとつだけだが、identity:認証方法は複数持てる。
認証の流れはガイド/チュートリアル/Blogデモ実装されているから迷わない。。。と思ったら迷った。SiteController#actionLogin -> LoginForm#login -> UserIdentity#authenticate, CWebuser#login と辿るうちに login の多さにめまいがする。Yii::app()->user->login() している protected/models/LoginForm.php を軸に辿れば迷わない。
protected/models/LoginForm.php
<?php public function login() { if($this->_identity===null) { $this->_identity=new UserIdentity($this->username,$this->password); // 認証情報を「認証方法」に渡す $this->_identity->authenticate(); // 認証 } if($this->_identity->errorCode===UserIdentity::ERROR_NONE) // 結果を判断 { $duration=$this->rememberMe ? 3600*24*30 : 0; // 30 days Yii::app()->user->login($this->_identity,$duration); // 「認証方法」からユーザー情報を引き渡す return true; } else return false; }
CUserIdentity を介してuser.id, user.name を CWebUser.login に引き渡していることが理解できればOKだと思う。
ログアウト
CWebUserのLogoutメソッドをコールする。これはコントローラに実装されている。
protected/controllers/SiteController.php
<?php public function actionLogout() { Yii::app()->user->logout(); $this->redirect(Yii::app()->homeUrl); }
ログイン済/未ログインの判断
次のコードで判断できる
<?php if (Yii::app()->user->isGuest) { echo '未ログイン:'.Yii::app()->user->name; } else { echo 'ログイン済:'.Yii::app()->user->name; }
この判断は layouts/main.php などで見られる。
protected/views/layouts/main.php
<div id="mainmenu"> <?php $this->widget('zii.widgets.CMenu',array( 'items'=>array( array('label'=>'Home', 'url'=>array('post/index')), array('label'=>'About', 'url'=>array('site/page', 'view'=>'about')), array('label'=>'Contact', 'url'=>array('site/contact')), array('label'=>'Login', 'url'=>array('site/login'), 'visible'=>Yii::app()->user->isGuest), array('label'=>'Logout ('.Yii::app()->user->name.')', 'url'=>array('site/logout'), 'visible'=>!Yii::app()->user->isGuest) ), )); ?> </div><!-- mainmenu -->
yiiext/twig-renderer でビューをフラグメントキャッシュしたい
{{void(...)}}でなく{%do...%}で
前のエントリで{{void(this.endWedgit)}}と書きましたが、{%do this.endWidget %}でいいことがわかり修正しました。ごめんなさい。
フラグメントキャッシュとは
フラグメントキャッシュはページの断片をキャッシュする事を指します。 たとえば、ページ中に年間売り上げサマリの表がある場合、 リクエスト毎にこれを生成する時間をなくすために、この表をキャッシュに保持できます。
http://www.yiiframework.com/doc/guide/1.1/ja/caching.fragment
yiiのフラグメントキャッシュのいいところは1テンプレート内にキャッシュされるところ/されないところが混在できるのでキャッシュの都合でテンプレートを分離せず済むことです。CakePHPのエレメントキャッシュではテンプレートを分離しなければならなかった、ような記憶があるような。
twig-rendererでもフラグメントキャッシュが働くかどうか、テンプレートとして読みやすいかどうかを検証します。
書き換え
php のフラグメントキャッシュ
<?php if($this->beginCache($id)) { ?> ...キャッシュされるコンテンツ... <?php $this->endCache(); } ?>
twig-renderer のフラグメントキャッシュ
{% if (this.beginCache(id)) %} ...キャッシュされるコンテンツ... {% do this.endCache %} {% endif %}
いびつなコードですね。できれば beginCache/endCache だけで書きたいところです。テンプレートとしては読みにくいですか。
検証コード
protected/config/main.php - ファイルキャッシュを有効にする
'components'=>array( 'cache'=>array( 'class'=>'system.caching.CFileCache', ), ),
protected/views/site/index.twig - フラグメントキャッシュのテスト
<h3>予想</h3> #1 : uncached == cached<br> #2 : uncached != cached<br> #3 : uncached != cached<br> #4 : ..<br> uncache : {{ date|date('Y-m-d\TH:i:sP') }} {% if (this.beginCache(1)) %} cached : {{ date|date('Y-m-d\TH:i:sP') }} {% do this.endCache %} {% endif %}
検証結果
予想通り、1回目の表示では2つの日時が一致し、2回目以降は2つの日時がズレる、のように動作すると思います。
というわけで、twig-renderer でもフラグメントキャッシュが機能することがわかりました。テンプレートとしてはちょっと読みにくいか。
yiiext/twig-rendererのビューで独自の書式を使いたい
twig-rendererのfiltersに関数を指定するとtwigのfilter構文でその関数が使えるようになります。twigのfilterとバッティングしたらどうなるんだろ。
こんな感じ。
'viewRenderer'=>array( 'class'=>'ext.etwigviewrenderer.ETwigViewRenderer', 'filters' => array( 'bold' => 'bold', ), ),
function bold ($value) { return "<b>".$value."</b>"; }
{% set value = "hogehoge" %} {{ value |bold}} ↓ <b>hogehoge</b>
独自の書式を使えると便利
Yiiのビューで独自の書式が欲しいときは CFormatter を拡張するといい | tipshare.infoのMyFormatterのstaticメソッドがフィルター構文で使えると便利ですよね。warekiをfiltersに登録するとこうなります。
'viewRenderer'=>array( 'class'=>'ext.etwigviewrenderer.ETwigViewRenderer', 'filters' => array( 'wareki' => 'MyFormatter::wareki', ), ),
<?php echo Yii::app()->format->wareki($date)); ?> ↓↓↓↓ {{ date|wareki }}
これは便利!!
yiiext/twig-rendererのビューでメッセージを扱いたい
これをどうするか
<?php echo Yii::t('yii','{attribute} is invalid.', array('{attribute}'=>'hoge')); ?>
Yii クラスをアクセス可能にする
Yiiクラス を globals に登録する
protected/config/main.php
'viewRenderer'=>array( 'class'=>'ext.etwigviewrenderer.ETwigViewRenderer', 'globals' => array( 'Yii' => 'Yii', ), ),
ビュー
{{ Yii.t('yii','{attribute} is invalid.', {'{attribute}':'hoge'}) }}
あるいは Yii::t メソッドをアクセス可能にする
Yii::tメソッド を functions に登録する
config/main.php
'viewRenderer'=>array( 'class'=>'ext.etwigviewrenderer.ETwigViewRenderer', 'functions' => array( 't' => 'Yii::t', ), ),
ビュー
{{ t('app','{attribute} is invalid.', {'{attribute}':'hoge'}) }}
どちらも Yii::t と短かったのでお得感は少ないかな。
yiiext/twig-rendererでページの一部分をテンプレート化したい
yii は $this->rendarPartial ですが、Twig はincludeタグで書きます。
<?php $this->renderPartial('_view', array('data'=>$model)); ?> ↓↓↓↓↓ {% include '/views/post/_view.twig' with {'data':model} %}
見たまんま書き換えできると思います。そしてrenderPartialでなくていいのかとつぶやくはず。私はつぶやきました。
yiiext/twig-rendererでビューテンプレートを継承したい
追記 2012.03.15
やだなーと思っていた{{void(...)}}ですが、{%do...%}で書くべきと気がつき、修正しました。
yiiのスケルトンappでは ページレイアウトの大枠を layouts/main.php、内部のカラム構造を layouts/column1.php, column2.php に実装しています。Twig ではこれをテンプレート継承で実装します。yiiext/twig-rendererというかTwigを使うならやはりテンプレート継承を使って実装したいところ。
yiiのスケルトンappからの書き換えはおもったより簡単です。こちらが参考になりました。
サンプルコード
親テンプレート
子に委譲するところをblockにする
protected/views/layouts/main.twig のごく一部
<?php echo $content; ?> ↓↓↓↓↓↓↓ {% block container %} {% endblock container %}
子テンプレート
親の上書きしたいブロックを実装する。{{content|raw}}にはコントローラで$this->render()した結果をはめ込みます。ここはyii風。
protected/views/layouts/column1.twig
{% extends 'views/layouts/main.twig' %} {% block container %} <div class="container"> <div id="content"> {% block content %} {{content|raw}} {% endblock content %} </div><!-- content --> </div> {% endblock container %}
protected/views/layouts/column2.twig
{% extends 'views/layouts/main.twig' %} {% block container %} <div class="span-19"> <div id="content"> {% block content %} {{content|raw}} {% endblock content %} </div><!-- content --> </div> <div class="span-5 last"> <div id="sidebar"> {% do this.beginWidget('zii.widgets.CPortlet', { 'title':'Operations', }) %} {% do this.widget('zii.widgets.CMenu', { 'items':this.menu, 'htmlOptions':{'class':'operations'} }) %} {% do this.endWidget %} </div><!-- sidebar --> </div> {% endblock container %}
yiiext/twig-rendererでWidgetを使いたい
追記 2012.03.15
やだなーと思っていた{{void(...)}}ですが、{%do...%}で書くべきと気がつき、修正しました。
ほぼ前のと同じですが、素直に this.beginWidgetでもうまくいきました。
前の{% set form = this.createWidget() %}...{{ form.run }}は何かの役に立つかもしれないので残しておきます。
サンプルコード
ポイント
void を利用して{% do %}でコントローラー pageTile, breadcrumbs に代入- Controller#setBreadcrumbs を追加する
- this.beginWidget の 戻り値を form に代入する
- this.endWidget は{% do %}
void中に書く
protected/views/site/login.twig
{% do this.setPageTitle(App.name~' - Login') %} {% do this.setBreadcrumbs({0:'Login'}) %} <h1>Login</h1> <p>Please fill out the following form with your login credentials:</p> <div class="form"> {% set form = this.beginWidget('system.web.widgets.CActiveForm', { 'id':'login-form', 'enableClientValidation':true, 'clientOptions':{ 'validateOnSubmit':true, }, }) %} <p class="note">Fields with <span class="required">*</span> are required.</p> <div class="row"> {{ form.labelEx(model,'username')|raw }} {{ form.textField(model,'username')|raw }} {{ form.error(model,'username')|raw }} </div> <div class="row"> {{ form.labelEx(model,'password') |raw}} {{ form.passwordField(model,'password')|raw }} {{ form.error(model,'password')|raw }} <p class="hint"> Hint: You may login with <tt>demo/demo</tt> or <tt>admin/admin</tt>. </p> </div> <div class="row rememberMe"> {{ form.checkBox(model,'rememberMe')|raw }} {{ form.label(model,'rememberMe')|raw }} {{ form.error(model,'rememberMe')|raw }} </div> <div class="row buttons"> {{ C.html.submitButton('Login') |raw }} </div> {% do this.endWidget %} </div><!-- form -->
void はphpコードが必要なとき多用するかもしれない。contactページではこんな感じで使います。すみません {% do ... %} でお願いします。
<?php $this->widget('CCaptcha'); ?> ↓ {{ void(this.widget('CCaptcha')) |raw }} ↓ {% do this.widget('CCaptcha') %}