Yiiでも中間テーブルにアクセスしたい
またこのネタです。
YiiFrameworkのCManyManyRelationは中間テーブルへのアクセスをサポートしていません。ならばオレオレリレーションによってそれを可能にしてみます。前エントリのまとめと反するのですが、まあ、あそびばってことで。
実現のためにCJoinElementを改造します。まだまだ中途半端な状態ですが載せておきます。気がむいたら更新するかもしれません。
2012.10.25 実験段階 とりあえずレイジーローディング
リレーション
まずオレオレリレーションモデルです。
protected/components/ManyManyPivotRelation.php
<?php class ManyManyPivotRelation extends CManyManyRelation { public $pivotModel = null; public $pivotWith = array(); }
モデル
続いてモデル
<?php class Viewer extends CActiveRecord { public static function model($className=__CLASS__) { return parent::model($className); } public function tableName() { return 'viewer'; } public function relations() { return array( 'movies_with_pivot' => array("ManyManyPivotRelation", 'Movie', 'viewer_watched_movie(viewer_id, movie_id)', 'pivotModel' => 'ViewerWatchedMovie', // <- 中間テーブルモデル 'pivotWith' => array('id','viewer_id', 'movie_id', 'liked',), // <-中間デーブルのカラム ), ); } }
viewer_watched_movie テーブルのPKはIDです。
<?php class ViewerWatchedMovie extends CActiveRecord { public static function model($className=__CLASS__) { return parent::model($className); } public function tableName() { return 'viewer_watched_movie'; } }
<?php class Movie extends CActiveRecord { public $pivot; // <- ここに中間テーブルのモデルが入る public static function model($className=__CLASS__) { return parent::model($className); } public function tableName() { return 'movie'; } }
CJoinElement
次はCJoinElementに手を入れます。hack begin - hack end を挿入してください。
リレーションがManyManyPivotRelationならば、selectするカラムを追加し、select結果からpivotインスタンスを生成し、子モデルの $record->pivot にセットしています。
YiiFramework/framework/db/ar/CActiveFinder.php
<?php class CJoinElement { private function applyLazyCondition($query,$record) { $schema=$this->_builder->getSchema(); $parent=$this->_parent; if($this->relation instanceof CManyManyRelation) { .... if($parentCondition!==array() && $childCondition!==array()) { $join='INNER JOIN '.$joinTable->rawName.' '.$joinAlias.' ON '; $join.='('.implode(') AND (',$parentCondition).') AND ('.implode(') AND (',$childCondition).')'; if(!empty($this->relation->on)) $join.=' AND ('.$this->relation->on.')'; $query->joins[]=$join; foreach($params as $name=>$value) $query->params[$name]=$value; } else throw new CDbException(Yii::t('yii','The relation "{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.', array('{class}'=>get_class($parent->model), '{relation}'=>$this->relation->name))); // hack begin ---------- if($this->relation instanceof ManyManyPivotRelation) { // add pivot columns // TODO table.column -> t0_c0 foreach ($this->relation->pivotWith as $col) { $query->selects[]=$joinAlias.'.'.$schema->quoteColumnName($col); } } // hack end ---------- } else { .... } } private function populateRecord($query,$row) { .... if(isset($this->records[$pk])) $record=$this->records[$pk]; else { $attributes=array(); $aliases=array_flip($this->_columnAliases); foreach($row as $alias=>$value) { if(isset($aliases[$alias])) $attributes[$aliases[$alias]]=$value; } $record=$this->model->populateRecord($attributes,false); foreach($this->children as $child) { if(!empty($child->relation->select)) { $record->addRelatedRecord($child->relation->name,null,$child->relation instanceof CHasManyRelation); } } $this->records[$pk]=$record; // hack begin ---------- if($this->relation instanceof ManyManyPivotRelation) { // make pivot instance // TODO t0_c0 -> column_name $p = new $this->relation->pivotModel; $record->pivot = $p->populateRecord($row,false); } // hack end ---------- } .... } }
ビュー
<?php $viewers = Viewer::model()->findAll(); foreach($viewers as $viewer) { foreach ($viewer->movies_with_pivot as $movie) { echo $viewer->name . ($movie->pivot->liked ? ' liked ' : ' didn’t like ') . $movie->title; } }
結果
hoge didn’t like foo hoge liked bar piyo liked foo piyo didn’t like bar
Yiiで中間テーブルに属性を持たせたいときはHasMany, BelongsToを使う
YiiFrameworkのCManyManyRelationは中間テーブルへのアクセス手段を用意してくれません。なので、中間テーブルに属性を持たせたいときはHasManyとBelongsToを使うのが、今のところエレガントな解決方法のようです。
http://www.yiiframework.com/wiki/285/accessing-data-in-a-join-table-with-the-related-models/#c6007
http://www.yiiframework.com/wiki/285/accessing-data-in-a-join-table-with-the-related-models/#c7483
この問題のサンプルコードにはtypoがたくさんあるので、検証コードを載せておきます。
テーブル
CREATE TABLE IF NOT EXISTS `viewer` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(45) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `movie` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(45) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `viewer_watched_movie` ( `id` int(11) NOT NULL AUTO_INCREMENT, `viewer_id` int(11) NOT NULL, `movie_id` int(11) NOT NULL, `liked` tinyint(1) DEFAULT NULL, PRIMARY KEY (`id`), KEY `fk_viewer_watched` (`movie_id`), KEY `fk_movie_watched_by` (`viewer_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
データ
INSERT INTO `viewer` (`id`, `name`) VALUES (1, 'v1'), (2, 'v2'); INSERT INTO `movie` (`id`, `title`) VALUES (1, 'm1'), (2, 'm2'); INSERT INTO `viewer_watched_movie` (`id`, `viewer_id`, `movie_id`, `liked`) VALUES (1, 1, 1, 1), (2, 1, 2, 0), (3, 2, 1, 1);
モデル
<?php class Viewer extends CActiveRecord { public static function model($className=__CLASS__) { return parent::model($className); } public function tableName() { return 'viewer'; } public function relations() { return array( 'watched_movies' => array(self::HAS_MANY, 'ViewerWatchedMovie', 'viewer_id'), ); } }
<?php class Movie extends CActiveRecord { public static function model($className=__CLASS__) { return parent::model($className); } public function tableName() { return 'movie'; } }
<?php class ViewerWatchedMovie extends CActiveRecord { public static function model($className=__CLASS__) { return parent::model($className); } public function tableName() { return 'viewer_watched_movie'; } public function relations() { return array( 'movie' => array(self::BELONGS_TO, 'Movie', 'movie_id'), ); } }
レイジーローディング
<?php $viewer = Viewer::model()->findByPk(1); foreach ($viewer->watched_movies as $watched) { echo $viewer->name . ($watched->liked ? ' liked ' : ' didn’t like ') . $watched->movie->title; }
イーガーローディング
<?php $viewers = Viewer::model()->with(array( 'watched_movies'=>array( 'condition'=>'watched_movies.liked=:liked', 'params'=>array(':liked'=>true), ), ))->findAll(); foreach ($viewers as $viewer) { foreach ($viewer->watched_movies as $watched) { echo $viewer->name . ($watched->liked ? ' liked ' : ' didn’t like ') . $watched->movie->title; } }
まとめのような
CActiveRecordの MANY_MANY, HAS_MANY...は単なるクラス名なので、次のようにオレオレリレーションのクラス名「OreOreRelation」でもいいんです。
<?php class Viewer extends CActiveRecord { .... public function relations() { return array( 'movies' => array('OreOreRelation', 'Movie', 'viewer_watched_movie(viewer_id, movie_id)', 'pivot_model'=>'ViewerWatchedMovie', ), ); } }
で、オレオレリレーションでSQL書き換えればいいかと思ったらさにあらず、SELECT..JOIN...生成はリレーションクラスでなく、CActiveFinderというかCJoinElementがやっています。
中間テーブルを取得するリレーションを作ってみたのですが、CJoinElementの 「if (relation instanseof CManyManyRelation) 」している辺りを探りながらパッチ当てることで、レイジーローディングだけどうにかできました。書くまでもなく茨の道です。
そんなわけで、いまのところエレガントな解決方法はHasMany, BelongsToだな、というところに落ち着きました。まえに弱点と書きましたが、今回のような設計で使ってくれと言うことなんだと思います。
YiiFrameworkでPHPTALしてみた
PHPTALやってみようかと思ったので、YiiFrameworkで動くPHPTALViewRenderer作ってみた。
インストール
PHPTAL
phptalとかgithubからzipをダウンロードし解凍し、classes ディレクトリ下の PHPTALディレクトリと PHPTAL.phpを prorected/vendor/PHPTAL へコピーする。
PHPTALViewRenderer
次をコピペする。ETwigViewRenderer を参考に作成しました。
prorected/extension/PHPTALViewRenderer.php
<?php class PHPTALViewRenderer extends CApplicationComponent implements IViewRenderer { /** * @var string Path alias to PHPTAL.php */ public $PHPTALPathAlias = 'application.vendors.PHPTAL'; /** * @var string Template files extension */ public $fileExtension = '.html'; private $_basePath; private $_basePathLength; function init() { require_once Yii::getPathOfAlias($this->PHPTALPathAlias).'/PHPTAL.php'; Yii::registerAutoloader(array('PHPTAL', 'autoloadRegister'), true); $app = Yii::app(); /** @var $theme CTheme */ $theme = $app->getTheme(); if ($theme === null) { $this->_basePath = $app->getBasePath(); } else { $this->_basePath = $theme->getBasePath(); } $this->_basePathLength = strlen($this->_basePath) ; return parent::init(); } public function renderFile($context, $sourceFile, $data, $return) { $sourceFile = "protected/".substr($sourceFile, $this->_basePathLength); $phptal = new PHPTAL($sourceFile); $phptal->this = $context; foreach($data as $key => $val) { $phptal->$key = $val; } $result = $phptal->execute(); if ($return) { return $result; } echo $result; } }
設定
PHPTALViewRenderer を設定
prorected/config/main.php
'components'=>array( 'viewRenderer'=>array( 'class'=>'ext.PHPTALViewRenderer', 'fileExtension' => '.html', ),
PHPTALのサンプルを試す
コントローラー
PHPTALのサンプルのパクリ。Personクラス作るのが面倒なので(object)で代用。
protected/controllers/TestController.php
<?php class TestController extends Controller { public $layout='column1'; public function actionIndex() { $title = 'The title value'; $people = array(); $people[] = (object)array("name"=>"foo", "phone"=>"01-344-121-021"); $people[] = (object)array("name"=>"bar", "phone"=>"05-999-165-541"); $people[] = (object)array("name"=>"baz", "phone"=>"01-389-321-024"); $people[] = (object)array("name"=>"quz", "phone"=>"05-321-378-654"); $this->render('index', compact('title', 'people')); } }
ビュー
PHPTALのサンプルそのまんま。
protected/views/test/index.html
<?xml version="1.0"?> <html> <head> <title tal:content="title"> Place for the page title </title> </head> <body> <h1 tal:content="title">sample title</h1> <table> <thead> <tr> <th>Name</th> <th>Phone</th> </tr> </thead> <tbody> <tr tal:repeat="person people"> <td tal:content="person/name">person's name</td> <td tal:content="person/phone">person's phone</td> </tr> <tr tal:replace=""> <td>sample name</td> <td>sample phone</td> </tr> <tr tal:replace=""> <td>sample name</td> <td>sample phone</td> </tr> </tbody> </table> </body> </html>
結果
得られたhtmlの一部
layouts/main.php, layouts/column1.php の内部に↓が出力される。
<?xml version="1.0"?> <html> <head> <title>The title value</title> </head> <body> <h1>The title value</h1> <table> <thead> <tr> <th>Name</th> <th>Phone</th> </tr> </thead> <tbody> <tr> <td>foo</td> <td>01-344-121-021</td> </tr><tr> <td>bar</td> <td>05-999-165-541</td> </tr><tr> <td>baz</td> <td>01-389-321-024</td> </tr><tr> <td>quz</td> <td>05-321-378-654</td> </tr> </tbody> </table> </body> </html>
課題
- もっとPHPTALを学ばなきゃ
- CWebUserへのアクセスは $template->user = Yii::app()->user; とすれば可能
- layouts/main.php, layouts/column1.php をどうにかする
- renderPartialは macroを使うのかな
- formatterはモディファイアに組み込むのかな
- yii::t()は PHPTAL_TranslationService に組み込むのかな
- フラグメントキャッシュは PHPTAL_Trigger を使って実現するのかな
次は TAL-way じゃないのでサポートしなくていいよね
- widget
- CHTML
yiiのアクセス制御を学ぶ yii-rightsを導入するの巻
前エントリのロール管理はこれでもロール管理かという代物でした。今回はyii-rightsを使いGUIで管理できるようにします。
blogデモにyii-rightsを組み込んだソースやデモも用意されている。手っ取り早く確認したいならはここから。
導入
- とりあえずドキュメント参照
- ロール関連のテーブルをドロップ (DROP TABLE `authassignment`, `authitem`, `authitemchild`;)
- ダウンロードして protected/modules/rights へ展開
- main.php 修正 ドキュメント参照
- adminロールをつけたいユーザーでログイン
- http://path/to/index.php/rights へアクセスしインストールする( rights/components/RInstaller.php参照)
- main.php 'install'=true を削除
- adminロールを持つユーザーでロール管理する
前エントリの権限を回復したい場合、http://path/to/index.php/site/loadroles へアクセスする。これで簡易ロール管理は不要になる。あるいは、yii-rightsインストール手順をせずにrightsテーブル(rights/data/schema.sql参照)をcreateする。デフォルトでは管理者権限はadminとなっているので異なる場合は注意。
yii-rights管理ページにはdescriptionが表示されるが、前エントリのロールに説明を設定していなかったので加えておく。
protected/coltrollers/SiteController.php
$role=$auth->createRole('reader', 'reader'); ... ↑↑↑↑ $role=$auth->createRole('author', 'author'); ... ↑↑↑↑ $role=$auth->createRole('editor', 'editor'); ... ↑↑↑↑ $role=$auth->createRole('admin', 'admin'); ... ↑↑↑↑
yii-rightsのクラス
書きかけ。後で書き加えたい。
RDbAuthManager
CDbAuthManager を継承している。rightsテーブル weight カラムを使っている
RWebUser
isSuperuser が追加されている。
yiiのアクセス制御を学ぶ アクションで権限チェックしないの巻
前エントリで触れなかったupdateOwnPost関連のアクセス制御について。アクションに書いてしまいがちな権限チェックをフィルタに書いてアクションをシンプルに保っていきたい。
パラメータ付き権限アイテム
ここの図でupdateOwnPostを再確認する。updateOwnPost自身はパラメータ付き権限アイテムで、updatePost がぶら下がっている。
updateOwnPostの権限チェックは次のように実装する。
if(!Yii::app()->user->checkAccess('updateOwnPost', array('post'=>$post))) Yii::app()->end(); // または if(!Yii::app()->user->checkAccess('updatePost', array('post'=>$post))) Yii::app()->end();
CAccessControlFilterの権限チェックを調べる
CAccessControlFilterがユーザーと権限をどう扱うか調べた。ユーザーと権限は前エントリ参照。
検証コードの一部
protected/controllers/PostController.php
<?php public function filters() { return array( 'accessControl', // perform access control for CRUD operations ); } public function accessRules() { return array( array('allow', 'actions'=>array('update'), 'roles'=>array('author'), //'roles'=>array('editor'), //'roles'=>array('updatePost'), //'roles'=>array('updateOwnPost'), // ユーザー毎にrolesを切り替えて検証する ), ); }
検証結果
filter のroles \ user | usera | userb | userc | userd |
---|---|---|---|---|
author | x | o | x | o |
editor | x | x | o | o |
updatePost | x | x | o | o |
updateOwnPost | x | x | x | x |
o : 200 OK, x : 403 forbidden
ロールに updateOwnPost を設定したとき、全ユーザーがアクセス不可。フィルタリング時点では updateOwnPostに必要なパラメータを渡せないから仕方ないか。
問題は userb の updatePost。userbは自身のポストはupdatePostしたいのだが、フィルタにupdatePostを指定すると403になってしまう。これではロールにupdatePost指定できない。
フィルタで権限チェックせず、アクションで権限チェックする手もあるが。。。
<?php public function accessRules() { return array( array('allow', 'actions'=>array('update'), 'roles'=>array(), //ロールなし ), array('deny', // deny all users 'users'=>array('*'), ), ); } public function update() { // 複雑な権限チェック if(!Yii::app()->user->checkAccess('updatePost', array('post'=>$post))) Yii::app()->end(); // updateの仕事 ... }
嫌だなー権限チェックはフィルタに任せたい。アクションはアクションの仕事に集中したい。
フィルタでもパラメータ付き権限チェックできる
expressionを使えば!
<?php public function accessRules() { return array( array('allow', 'actions'=>array('update'), 'expression'=>array($this, "checkUpdatePost"), ), array('deny', // deny all users 'users'=>array('*'), ), ); } protected function checkUpdatePost($user, $rule) { // 複雑な権限チェック $user は Yii::app()->user のこと return $user->checkAccess('updatePost', array('post'=>$this->loadModel()); } public function actionUpdate() { // updateの仕事 ... } protected function loadModel() { ... }
これでいける。checkUpdatePostでloadしたモデルはアクションで利用できるオマケつきつき。
beforeActionでも
「アクションはアクションの仕事に集中」するならば、beforeActionでチェックしてもいいか。
yiiのアクセス制御を学ぶ CDbAuthManager導入の巻
ロールベースアクセスコントロール(RBAC)を導入するのは面倒で後回しにしてきました。ロールのデザイン、ロール管理の実装、ユーザー管理の実装、権限付与の実装には時間がかかりそうで躊躇してしまうのです。そこでこれらをできる限り省略してRBACを導入することにフォーカスしてみます。
これからやることが想像できない場合はロールベースアクセスコントロールを読む。やりたいことはなんとなくわかった、でもなんだか面倒だなーと思うくらいでOK。とりあえずやってみる。
手順
- アプリケーション作成
- ユーザー管理
- ユーザー認証
- 権限付与マネージャの導入
- ロール管理
- 操作に権限を割り当てる
アプリケーション作成
blogデモを利用します。
ユーザー管理
blogデモにはユーザー管理機能がありません。前のポストを参考にしてもいいですが、とりあえずのユーザー生成スクリプトを用意したので使ってください。
4ユーザー usera, userb, userc, userd のパスワードはすべてdemoです。
INSERT INTO tbl_user (id, username, password, salt, email) VALUES (901, 'usera','2e5c7db760a33498023813489cfadc0b','28b206548469ce62182048fd9cf91760','webmaster@example.com'); INSERT INTO tbl_user (id, username, password, salt, email) VALUES (902, 'userb','2e5c7db760a33498023813489cfadc0b','28b206548469ce62182048fd9cf91760','webmaster@example.com'); INSERT INTO tbl_user (id, username, password, salt, email) VALUES (903, 'userc','2e5c7db760a33498023813489cfadc0b','28b206548469ce62182048fd9cf91760','webmaster@example.com'); INSERT INTO tbl_user (id, username, password, salt, email) VALUES (904, 'userd','2e5c7db760a33498023813489cfadc0b','28b206548469ce62182048fd9cf91760','webmaster@example.com');
ユーザー認証
blogデモに実装済みなのでパス。パス。
権限付与マネージャの導入
権限付与マネージャには CDbAuthManager と CPhpAuthManager がありますが今回使うのは前者です。
CDbAuthManager の設定
- config/main.phpに次を追加する
protected/config/main.php
'components'=>array( 'authManager'=>array( 'class'=>'CDbAuthManager', 'connectionID'=>'db', ), ),
CDbAuthManager が必要とするテーブルを生成
- framework/web/auth/schema-*.sql のスクリプトで作成する
ロール管理
yiiガイドにあるロール構造を登録します。権限付与アイテムとは、ロール構造図をあわせて読むと理解しやすい。
権限付与アイテム
権限付与アイテムとは何かをする許可のことです。(例:新しいブログ記事を作る、ユーザを管理する) 粒度と対象者によって、権限付与アイテムはオペレーション、タスク、ロールに分類されます。 ロールは複数のタスクからなり、タスクは複数のオペレーションからなります。 そして、オペレーションが一番小さな許可単位です。
http://www.yiiframework.com/doc/guide/1.1/ja/topics.auth
ロール構造図
ここのロール構造図を参照。
簡易ロール管理
ロール管理はUIで管理したいところですが、今回は SiteController.phpにロールを初期化/登録するアクションを追加します。追加したら http:/path/to/index.php/site/loadroles にアクセスしロール定義をロードします。
protected/controllers/SiteController.php
<?php public function actionLoadRoles() { $auth=Yii::app()->authManager; // ロール初期化 $auth=Yii::app()->clearAll(); // ロール定義 $auth->createOperation('createPost','create a post'); $auth->createOperation('readPost','read a post'); $auth->createOperation('updatePost','update a post'); $auth->createOperation('deletePost','delete a post'); $bizRule='return Yii::app()->user->id==$params["post"]->author_id;'; $task=$auth->createTask('updateOwnPost','update a post by author himself',$bizRule); $task->addChild('updatePost'); $role=$auth->createRole('reader','reader'); $role->addChild('readPost'); $role=$auth->createRole('author','author'); $role->addChild('reader'); $role->addChild('createPost'); $role->addChild('updateOwnPost'); $role=$auth->createRole('editor','editor'); $role->addChild('reader'); $role->addChild('updatePost'); $role=$auth->createRole('admin','admin'); $role->addChild('editor'); $role->addChild('author'); $role->addChild('deletePost'); // ユーザーに権限付与 $auth->assign('reader','1'); //demo $auth->assign('reader','901'); //usera $auth->assign('author','902'); //userb $auth->assign('editor','903'); //userc $auth->assign('admin', '904'); //userd }
ロール作成時、descriptionを設定しました。
各ユーザーの権限
- demo 記事の参照
- usera 記事の参照
- userb 記事の参照と作成 + 自身が作成した記事の編集
- userc 記事の参照と作成と編集
- userd 記事の参照と作成と編集と削除
権限チェック
ここまでできたら次はいよいよユーザーに付与した権限を元にBlogデモが動作するよう改修していきます。ログインユーザーが権限を持っている/いないは CWebUser#checkAccess で得られます。
Yii::app()->user->checkAccess('createPost')
view上で判定する
protected/components/views/userMenu.php
<?php <ul> <?php if (Yii::app()->user->checkAccess('createPost')) : ?> <li><?php echo CHtml::link('Create New Post',array('post/create')); ?></li> <?php endif; ?> <?php if (Yii::app()->user->checkAccess('admin')) : ?> <li><?php echo CHtml::link('Manage Posts',array('post/admin')); ?></li> <?php endif; ?> <?php if (Yii::app()->user->checkAccess('editor')) : ?> <li><?php echo CHtml::link('Approve Comments',array('comment/index')) . ' (' . Comment::model()->pendingCommentCount . ')'; ?></li> <?php endif; ?> <li><?php echo CHtml::link('Logout',array('site/logout')); ?></li> </ul>
Postコントローラーのアクセスコントロールフィルター
protected/controllers/PostController.php
<?php public function accessRules() { return array( array('allow', 'actions'=>array('index','view'), 'roles'=>array('readPost'), ), array('allow', 'actions'=>array('create'), 'roles'=>array('createPost'), ), array('allow', 'actions'=>array('update'), 'roles'=>array('updatePost'), ), array('allow', 'actions'=>array('delete'), 'roles'=>array('deletePost'), ), array('deny', // deny all users 'users'=>array('*'), ), ); }
まとめ
説明しきれていないことが多々あるけれど、CDbAuthManagerは導入できたと思う。
- 権限付与マネージャは CDbAuthManager と CPhpAuthManager がある
- 権限アイテムにはロール、タスク、オペレーションがある。オペレーションが最小単位。
- ロール管理は権限付与マネージャを使う。が、将来的にはyii-Rightsなどのエクステンションを使って管理すると思う
- 権限チェックは CwebUser#checkAccess からできる
- アクセスコントロールフィルターから権限チェックを利用できる
blogデモにユーザー管理機能を設ける
blogデモにユーザー管理機能を追加したときのメモ。gii-Crud GeneratorでUserモデルメンテナンス機能を生成し、生成したコードを調整します。
Userモデルメンテナンス機能を生成する
gii を有効にする
ついでにgiiにたどり着けるよう urlManager も調整する。なんか勘違いしていた。
protected/config/main.php
<?php 'modules'=>array( 'gii'=>array( 'class'=>'system.gii.GiiModule', 'password'=>'gii', // If removed, Gii defaults to localhost only. Edit carefully to taste. ), ),
user の crud を作成
http://path/to/index.php/gii へアクセスしuser の crud を作成する。
giiで生成したコードを調整する
password, saltは表示したくない。saltは入力したくないので次を修正する。
protected/views/user/_form.php
saltは入力不要。salt をカット
protected/views/user/_search.php
saltは表示不要。salt をカット
protected/views/user/_view.php
password, saltは表示不要。password, salt をカット
protected/views/user/admin.php
password, saltは表示不要。password, salt をカット
protected/views/user/view.php
password, saltは表示不要。password, salt をカット
protected/models/user.php
saltは入力しないので rules から salt をカット。beforeSave を追加する。
<?php public function rules() { return array( array('username, password, email', 'required'), array('username, password, email', 'length', 'max'=>128), array('profile', 'safe'), ); } protected function beforeSave() { $this->salt = $this->generateSalt(); $this->password = $this->hashPassword($this->password, $this->salt); return true; }
使い方
一覧表示 : http://path/to/index.php/user
詳細表示 : http://path/to/index.php/user/view?id=999
作成 : http://path/to/index.php/user/create
変更 : http://path/to/index.php/user/update?id=999
削除 : http://path/to/index.php/user/delete?id=999
コンテキストメニュー等の修正はパス。