カテゴリー : Yii

Yii フォーム入力データのバリデーション

フォーム入力されたデータのバリデーションは、ActiveRecord を継承したモデルクラスの rules() メソッドの定義で、プロパティごとに評価される。評価は、framework/validators ディレクトリにあるプログラムが実行されることで行なわれるが、自分で新しく作る必要がないほど量が豊富だ。

そのため、rules() メソッドを使うためのチートシートが用意されている。

Yii Framework 1.1 Validators Cheatsheet

バリデーションの書き方がよく分からない事もあるので、チュートリアル以外の使い方の例があれば、それを書き留めておきます。

日付入力が yyyy-mm-dd の形式で入力されているかチェックする

public function rules() {
	return array(
		:
		array('day', 'type', 'type'=>'date', 'dateFormat'=>'yyyy-MM-dd'),
	);
}

‘type’ は ‘date’ の他、’time’、’datetime’、’integer’ などがある。形式指定のキーは、’time’ なら ‘timeFormat’、’datetime’ なら ‘datetimeFormat’ で、’integer’ の場合は必要ない。形式に使う記号は、クラスリファレンスの CDateTimeParser に説明がある。

(100610 yii-1.1.2.r2086)

Yii CGridView パラメータの指定2

CGridView は、かなり複雑な指定ができるようで奥が深い。こんなことできないかなぁ、と思って試してみると思った通りの結果が得られて驚かされる。で、いつものように備忘録となります。標題は「指定2」で、「指定1」は「Yii actionAdminのsearch-formとCGridViewで悪戦苦闘」がそれに当たる。

項目のデータを、3桁区切りのコンマを付けて右寄せ表示したい

<php $this->widget('zii.widgets.grid.CGridView', array(
	'id'=>'price-grid', // データ件数、テーブル、ページネーションを囲むdivタグのid名
	'dataProvider'=>$dataProvider,
	'columns'=>array(
		'id',
		'title',
		array(
			'name'=>'price',
			'value'=>'number_format($data->price)',
			'htmlOptions'=>array('style'=>'text-align:right'),
		),
	),
));
?>

DBのカラム名だけ指定すると、表のヘッダー名(thタグ)はモデルの「attributeLabels()」メソッドの値が使われる。

上の例のように、’name’ にDBのカラム名を指定すると、同様にモデルの「attributeLabels()」メソッドの値が利用される。あるいは、別の文字列を与えればそれが表示される。

3桁区切りは、PHPの number_format() 関数を利用すればよい。PHP 5.3 からは無名関数が利用できるので、複雑な処理ができるようになる。

表示の右寄せは CSS で指定するが、’htmlOptions’ を使って td タグ出力に属性を追加する事ができる。

1カラムにリンク項目を2つ以上セットしたい場合

1行に表示するデータで、そのデータの編集処理へのリンクを最後のカラムに置きたい場合、「その1」で述べた方法や次のような CHtml::link を使った方法がある。

<php $this->widget('zii.widgets.grid.CGridView', array(
	'id'=>'price-grid',
	'dataProvider'=>$dataProvider,
	'columns'=>array(
		'id',
		'title',
		array(
			'name'=>'price',
			'value'=>'number_format($data->price)',
			'htmlOptions'=>array('style'=>'text-align:right'),
		),
		array(
			'name'=>'処理',
			'type'=>'raw',
			'value'=>'CHtml::link("編集", array("edit", "id"=>$data->id))',
		),
	),
));
?>

では、編集処理のリンクの後ろに「編集 | 削除」のように、削除処理のリンクを追加したい場合はどうしたらよいのだろうか?

‘value’ の値に配列を指定すると、ビューからコントローラのメソッドを呼び出す仕掛けが用意されている。それを利用すればよい。これは、’value’ の値に array(データ, メソッド名) と指定する。

<php $this->widget('zii.widgets.grid.CGridView', array(
	'id'=>'price-grid',
	'dataProvider'=>$dataProvider,
	'columns'=>array(
		'id',
		'title',
		array(
			'name'=>'price',
			'value'=>'number_format($data->price)',
			'htmlOptions'=>array('style'=>'text-align:right'),
		),
		array(
			'name'=>'処理',
			'type'=>'raw',
			'value'=>array($this, 'linkRender')
		),
	),
));
?>

//コントローラのメソッド
protected function linkRender($data, $row, $grid)
{
	$edit = CHtml::link('編集', array('edit', 'id'=>$data->id));
	$delete = CHtml::link('削除', array('delete', 'id'=>$data->id));
	return "{$edit} | {$delete}";
}

(100608 yii-1.1.2.r2086)

Yii Gii 利用開始メモ

新しい作業を始める際、作業手順を忘れて毎度もたつくのでメモをしておこうかと。

1.インストール

  • Yii 本体(frameworkディレクトリ)を公開領域外にコピー
  • php yiic.php webapp /web directory でアプリ環境生成
  • 生成された protected を非公開領域へ移動
  • index.php 内の system と config/main.php のパス定義変更

参考:Blog Tutorial スキャホールディングとモジュールの配置

2.Gii 利用の段取り

これで、「http://xxx.example.com/index.php?r=gii」でパスワードを入力して Web 上から作業開始できる。DB のテーブル定義をしっかりと行なっておく事。

以下、Gii の実行ページ例

Yii actionAdminのsearch-formとCGridViewで悪戦苦闘

Ver.1.1.1 になってAjax検索やフィルタが追加されたのだが、スキャフォールドで吐き出されたアクションやビューを見たり、あるいはドキュメントを見ても使い方がよく分からない。そこでドキュメントの例など見ながらいろいろ操作して、結果何となく動くようになった今の時点でのメモです。

データが GridView に表示されない

吐き出されたビューには、表のタイトル直下にフィルタ入力用の項目が差し込まれる。また、「Advanced Search」リンクボタンにより全ての項目を含む検索ブロックが利用できる。それぞれの検索条件は、どうやらモデルに出力されたメソッド「search()」を利用するようだ。

ところで最初の表示でそれらの項目には、テーブル定義で設定した default 値がセットされる。そのため、数値項目で「0」をデフォルトに指定しているとそれを一致条件として検索するため、データが何も表示されないハメになるようだ。

回避策として、$model インスタンスのテーブル項目属性に null など初期値を与えておくとよい。

Grid View のフィルタを使わないようにしたい

CGridView ウィジェットに与えるパラメータに「’filter’=>$model」と言うのがあり、この「filter」パラメータを取り去ってしまえばよい。

Grid View の表示項目にリレーションデータを表示したい

例えば、モデルで以下のように定義されていたとする。

public function relations() {
	return array(
		'category' => array(self::BELONGS_TO, 'Category', 'category_id'),
	);
}

ビューでカテゴリ名を表示したいとき

<?php $this->widget('zii.widgets.grid.CGridView', array(
	'dataProvider'=>$dataProvider,
	'columns'=>array(
		'title',
		array(
			'name'=>'カテゴリ名',
			'value'=>'$data->category->name',
		),
		array(
			'class'=>'CButtonColumn',
			'template'=>'{view} {update}',
			'viewButtonUrl'=>'Yii::app()->createUrl("/post/archive", array("id"=>$data->id))',
		),
	),
)); ?>

のようにして関係データを表示できる。

また、編集などのボタンリンクは、「template」パラメータで「{delete}」を指定しない事で2つだけにしたり、「viewButtonUrl」でリンク先を変更したりできる。

sub query を利用したデータを使いたい

例えば、post データの中からカテゴリ別に最新の記事の一覧を表示したいとする。post データには category_id 項目があるとする。SQL の副問い合わせを利用して

$sql = "SELECT * FROM post WHERE id IN "
	. "(SELECT MAX(id) FROM post GROUP BY category_id);"; 
$posts = Post::model()->findAllBySql($sql);

と検索できる。これは残念ながら CGridView の data provider には使えないようだ。同じような質問がフォーラムに上がっていた。
Using addInCondition() with sub-query

そこで、それを参考に強引にやったやり方が次の通り。

$posts = Post::model()->findAll(array(
	'select'=>array(new CDbExpression('MAX(id) as id')),
	'group'=>'category_id',
));

foreach ($posts as $post) {
	$ids[] = $post->id;
}

$cr = new CDbCriteria;
$cr->addInCondition('id', $ids);
$dataProvider = new CActiveDataProvider('Post', array(
	'criteira'=>$cr,
));

$this->render('admin', array(
	'dataProvider'=>$dataProvider,
));

この中で、関数 MAX() を使った場合、オブジェクトに項目のデータをどうやって引き込むのか悩んだ。取りあえずプログラム例のようにやったらできた、と言うだけ。本当の使い方はよく分かりません。なお使われている SQL を見ると、MAX(id) は、MAX(t.id) の方がいいかも知れません。

ページネーションできるかこれから検討です。— (できました)

CGridView の項目(カラム)のデータにリンクを貼りたい

カテゴリ名をクリックすると、そのカテゴリのアーカイブを表示する処理へ移るリンクを貼りたいときは、どうすればいいのだろうか?以下のようにするとできた。

<?php $this->widget('zii.widgets.grid.CGridView', array(
	'dataProvider'=>$dataProvider,
	'columns'=>array(
		'title',
		array(
			'header'=>'カテゴリ名',
			'class'=>'CLinkColumn',
			'labelExpression'=>'$data->category->name',
			'urlExpression'=>'array("archive/category", "id"=>$data->category->id)',
		),
		array(
			'class'=>'CButtonColumn',
			'template'=>'{view} {update}',
			'viewButtonUrl'=>'Yii::app()->createUrl("/post/archive", array("id"=>$data->id))',
		),
	),
)); ?>

どうやら html の部品は、framework/zii/widgets/grid にあるクラスを使えばいいようだ。時間が掛った … orz

ところで、value の指定に 「CHtml::link(“label”, url)」を利用すると、タグがhtmlエンティティに置き換えられる(‘type’=>’raw’の指定をすればいいことが後で分かった)。

それと、ここで指定された処理は eval() で実行されるため、eval() を利用しない方法として、call_user_func_array() で実行する方法がフォーラムにありました。

avoid eval() on CGridView columns’ values

(yii-1.1.1.r1907)

Yii JavaScriptソース、ファイルの組み込みについて

JavaScript をビューに、フレキシブルに組み込むやり方についてまとめてみた。ドキュメントや検索でそれらしい記事が見つからなかったので、個人的な推測を交えての備忘録となります。

JavaScript のコードは 、HTML の head 部、body 部どちらに挿入してもいいのだが、body 部はコンテンツ中心としたいので、専ら head 部に組み込むようにしたい。ただし、レイアウトのフレームに固定で組み込むのは、JavaScript プログラムを利用しないページ表示でロード時間やトラフィックを考えると好ましくない。

コントローラの各アクションで用意するビュー内で、必要に応じて組み込んだり、組み込まなかったりを、

  • Yii が予め用意するプログラムファイル
  • 自分が用意したプログラムファイル
  • ファイルにするほどではないスニペット
  • GoogleのAjaxライブラリ

について、head 部に組み込んで利用する方法を見る。

Yii が予め用意するプログラムファイル

jQuery やその周りのプログラムが、ライブラリをインストールすると一緒に保存される。スキャホールドでプログラムしたり、widget を利用すると自動的に組み込んで使えるようにしてくれる。

そのような使い方でなく、自分で JavaScript のプログラムを書く際、jQuery を組み込んで利用したい訳で、そのやり方である。「CClientScript」クラスを利用する。以下にビューにおいて jQuery の組み込みを示す。

<?php
$cs = Yii::app()->clientScript;
$cs->registerCoreScript('jquery');

予めライブラリに用意された JavaScript プログラムは、core scripts として「framework/web/js/packages.php」に登録されている。

自分が用意したプログラムファイル

例えば公開ディレクトリに「js」ディレクトリを用意し、そこにまとめてプログラムを保管しリンクするのが一般的だと思う。Yii では、非公開ディレクトリにあるファイルを公開ディレクトリにコピーし、それをリンクして使う方法がある。

あるアクションに限定して使うようなファイルを、ビューディレクトリに保存して利用できれば、Widget などを外部パッケージで提供する際便利だと思う。

以下の例は、Site コントローラの login アクションで、md5 ハッシュ値を求めるための md5.js プログラムファイルをビューディレクトリ配下で「js/md5.js」と用意して、非公開領域から公開領域にコピーしてリンクする方法である。

<?php
$cs = Yii::app()->clientScript;
$cs-$gt;registerScriptFile(CHtml::asset('js/md5.js'), CClientScript::POS_HEAD);

ファイルにするほどでないスニペット

jQuery を利用すると、短いプログラムで記述することができる。それをいちいちファイルにせず、ソースを HTML に組み込んでしまう方法である。例は、入力されたパスワードの値を md5 ハッシュ値に変換するものである。

<?php
$cs = Yii::app()->clientScript;
$script = <<< JS
jQuery(document).ready(function($) {
	$("form").submit(function() {
		$("#login_password").val(md5($("#password").val()));
		return true;
	});
});
JS;
$cs->registerScript('1', $script, CClientScript::POS_HEAD);

Google の Ajax ライブラリ

CGoogleApi というヘルパークラスがあるのでこれを利用する。このクラスの register() メソッドを利用すると、head 部に組み込んでくれる。ただし、追加したいコードは POS_HEAD 以外、例えば POS_BEGIN などにしないと jQuery のイベント処理が動作しないので注意する。

<?php
CGoogleApi::register('jquery', '1.4.2');
$cs = Yii::app()-$gt;clientScript;
$cs->registerScript('1', $script, CClientScript::POS_BEGIN);