Yiiを少しまじめに学習してみる:ユーザ認証とRDBの利用

YiiをJSのTimelineウイジェット対応にしてみる」で、年表づくりが面白くなってきたので、データ格納部分をDB化して、入力インターフェースも作ってみることにした。

といってもヨクワカランので、公式ドキュメントのチュートリアル:Yiiを使ってブログシステムを作るを見ながら祖型を作ってみる。

要求分析

http://www.yiiframework.com/doc/blog/1.1/ja/start.requirementsの記述に基づいて書けば、

  1. 入力ユーザの認証(ログイン、ログアウト)
  2. 年表データの作成、更新、削除
  3. 年表データをグループ化してグループ名をつけ、グループ毎にtimeline.jsから呼び出せるようにする。

追加要求はなし。

全体の設計

http://www.yiiframework.com/doc/blog/1.1/ja/start.designに倣って書いてみる

ユーザ情報

  • tbl_user ユーザ情報の格納

年表データ情報

timeline.jsでevents変数に入れられるhttp://code.google.com/p/simile-widgets/wiki/Timeline_EventSourcesの項目をテーブル化する。正規化しない(必要を感じない)

なおevents用項目名は

start 開始年月日時刻
latestStart 最も遅いと考えられる開始年月日時刻
earliestEnd 最も速い考えられる終息年月日時刻
end 終息年月日時刻
durationEvent trueだと継続日時として表示(アイコンなし)falseだとその逆
title タイトル
icon アイコンのイメージURL
image 窓内のイメージURL
link 窓内のリンク
color テキストと年月日範囲帯(durationEvent trueの場合)の色
textColor テキストラベルの色(範囲帯と別にしたい場合)
tapeImage 範囲帯をイメージにしたいときのURLパス
tapeRepeat リピートするか(repeat-x|repeat-y)
caption オンマウスの時に表示されるキャプション
classname cssクラス名
description 窓内の記述文 (XMLの場合はevents要素の「内容」となるのでdescriptionを項目名は使わない)

このうち、start,end,latestStart,earliestEndは日付。durationEventは真偽値。durationEventは日付と組み合わせて使うが、その使い方についてはhttp://code.google.com/p/simile-widgets/wiki/Timeline_Event_Display

日付は、

  1. May 10 1961 00:00:00 GMT-0600
  2. RFC 2822 format of "Thu, 21 Dec 2000 16:01:07 +0200"
  3. A subset of the ISO 8601 format. This is also the xsd:dateTime format from XML.a

Examples: "1995-02-04 10:20:01Z"

のようなかたちをとれる。どのフォーマットを利用するかはtimeline.jsのdataTimeFormatで指定できる。

格納フォーマットは自然ソートが楽なISO8601に統一する。

利用DBは日付型をもっていないSQLite3を使うので、属性はtextとなる。

なお、tapeImageはURL(ファイルパス)を指定することになっているが、これについては別の考え方で格納することになりそうだが、これは後述。

というわけで、idとdurationEventを除いてデータ型はtextとなる。なおSQLite3はboolはないので、durationEventはInteger型で代替する(0 or 1)。

tbl_tl

  • id
  • start 必須
  • end 記入がない時はstartと同じ
  • title 必須
  • description 記入がない時はtitleと同じものを入れる
  • latestStart
  • earliestEnd
  • durationEvent 必須 0 or 1
  • icon
  • image
  • link
  • color
  • textColor
  • tapeImage
  • tapeRepeat
  • caption
  • classname
  • user_id 作成ユーザID 必須

年表グループ情報

tbl_tl_categoryは実際のtimelineで描画するtimelineの各項目のグループ化。tbl_tl_categoryというテーブルをつくり

  • cat_id
  • cat_title
  • cat_group
  • cat_description
  • user_id 作成ユーザID

cat_groupに年表データidを格納する予定だが、データフォーマット未定。


年表データ情報:tbl_tl_dataを作る

とりあえず作ってみる。

testdrive.dbにテーブルを一つ

protected/data/schema.tbl_tl.sql
CREATE TABLE tbl_tl (
    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    start VARCHAR NOT NULL,
    end VARCHAR NOT NULL,
    title VARCHAR NOT NULL,
    description  VARCHAR NOT NULL,
    lastestStart VARCHAR,
    earliestEnd VARCHAR,
    durationEvent INTEGER NOT NULL,
    icon VARCHAR,
    image VARCHAR,
    link VARCHAR,
    color VARCHAR,
    textColor VARCHAR,
    tapeImage VARCHAR,
    tapeRepeat VARCHAR,
    caption VARCHAR,
    classname VARCHAR,
    user_id INTEGER NOT NULL
);

sqlite3 testdrive.db < schema.tbl_tl.sql

足場(Scaffolding)を作る

http://www.yiiframework.com/doc/blog/1.1/ja/prototype.scaffold

http://ホスト名/index.php?r=gii(config/main.phpでurlManagerコメントを外してルーティングルールを機能させているとすれば、http://ホスト名/gii/)。

Model Generator

giiマネージャにはパスワード入れてModel Generator

no title


  • Table Prefix tbl_
  • Table Name tbl_tl

これでmodels/tl.phpが出来て、class tl extends CActiveRecord が生成される。

CRUD Generator

次にCRUD GeneratorでModel Classにtlと入れると

  • controllers/TlController.php
  • views/tl/admin.php,_view.php,index.php,_form.php,create.php,view.php,update.php,_search.php

ふむふむ。


http://ホスト名/index.php?r=tl(or http://ホスト名/tl/)にアクセスすると。

no title


画面右にあるCreats tl をクリックすると

no title


modelの修正

http://www.yiiframework.com/doc/blog/1.1/ja/post.modelを参照して、tlモデルクラスの修正をしてみる。

rulesの修正

durationEventは0か1のみを入れるので、次のようにした。

public function rules()
{
        return array(
                array('start, end, title, description, durationEvent, user_id', 'required'),
                array('durationEvent, user_id', 'numerical', 'integerOnly'=>true),
                array('durationEvent','in','range'=>array(0,1)),
                array('lastestStart, earliestEnd, icon, image, link, color, textColor, tapeImage, tap\
eRepeat, caption, classname', 'safe'),
               array('id, start, end, title, description, lastestStart, earliestEnd, durationEvent, \
icon, image, link, color, textColor, tapeImage, tapeRepeat, caption, classname, user_id', 'safe', 'on'=>'sear\
ch'),
        );
}

relationsメソッドについて

学習が進むとまだ作ってないユーザテーブル(tbl_user)とタイムライングループテーブル(tbl_tl_category)とのrelation定義をする必要があるが、今は手をつけない。

attributeLabelsメソッド

ラベル名を配列で整える。

return array(
   'id' => 'ID',
   'start' => 'Start',
   'end' => 'End',
   'title' => 'Title',
   'description' => 'Description',
   'lastestStart' => 'Lastest Start',
   'earliestEnd' => 'Earliest End',
   'durationEvent' => 'Duration Event',
   'icon' => 'Icon',
   'image' => 'Image',
   'link' => 'Link',
   'color' => 'Color',
   'textColor' => 'Text Color',
   'tapeImage' => 'Tape Image',
   'tapeRepeat' => 'Tape Repeat',
   'caption' => 'Caption',
   'classname' => 'Classname',
   'user_id' => 'User',
);

日本語で対応した場合、このmodelsのファイルに直接書くのではなくて、別に定義ファイルを作っておいてその配列変数を返すという風になる(かもしれない)。とりあえずこのままにしておく


ビューを考える。

まずは生成されたコントローラ(TlController.php)のactionIndexメソッドの記述を理解する。

public function actionIndex()
{
	$dataProvider=new CActiveDataProvider('tl');
	$this->render('index',array(
		'dataProvider'=>$dataProvider,
	));
}

というわけで、CActiveDataProvider APIの学習からはじめなければならない。


CActiveDataProvider

http://www.yiiframework.com/doc/api/1.1/CActiveDataProvider/

CDataProviderクラスの継承。

It uses the AR CActiveRecord::findAll method to retrieve the data from database.

という風になっている。

クラス引数

__construct($modelClass,$config=array())となっていて、config変数は次のようなものをとることが出来る。

  • criteria CDbCriteriaのインスタンス
  • sort モデル($modelClass)を指定したCSortのインスタンス
  • pagination 親クラスのCDataProviderで定義されている。CPaginationのインスタンス

他に、id,itemCount,totalitemCount,keys。これらはpagination同様親のCDataProviderで定義されているのでそちら側のAPI解説を読むこと。

このうち特に重要なのがCDbCriteriaクラスがうけもつcriteria。これでActiveRecodeのクエリー定義を行う。

criteria 記述
 $dataProvider=new CActiveDataProvider('Post', array(
     'criteria'=>array(
         'condition'=>'status=1',
         'order'=>'create_time DESC',
         'with'=>array('author'),
     ),
 ));

上のうち'with'=>array('author')はモデル(上の例で言えばPostモデルクラス)のrelationsメソッドで定義したrelational query criteria である。

condtionはWHERE句。文字列型なので生にWHERE句の中身を書く。例 age >31 AND team=1

views/tl/index.phpのzii.widgets.CListView

コントローラから$dataProvider変数がビューに渡される。

views/tl/index.phpの記述は下記のようなもの。

$this->widget('zii.widgets.CListView', array(

'dataProvider'=>$dataProvider,

'itemView'=>'_view',

));

というわけで、次はzii.widgets.CListViewの学習。

http://www.yiiframework.com/doc/api/1.1/CListView

zii.widgetsの名前空間に属しているクラスはhttp://www.yiiframework.com/doc/api/1.1/#zii.widgets

すべてCBaseControllerー>CWidgetのサブクラスになっている。

CListVIewは、itemViewでビュー用テンプレート(上の場合は_view)を指定する。

ビュー用テンプレートでは$this,$data,$index,$widget変数を使える。

$thisは当該コントローラオブジェクトである(zii.widget各クラスはCBaseControllerのサブクラスだからそうなる)。

$dataは、dataProviderで指定したデータオブジェクト。($dataProviderという変数のほうがわかりやすいと思うのだけど)。

$indexは、現在レンダリングされているデータの、零を起点とする(zero-based index)インデックスオブジェクト(ようするに何番目のデータか、ということ)。

$widgetは現在のwidget自身のオブジェクトを指す。

views/tl/_view.php

CRUD generatorで生成されたままの_view(.php)を見ると、

echo CHtml::encode($data->getAttributeLabel('id')); ?>:

echo CHtml::link(CHtml::encode($data->id), array('view', 'id'=>$data->id));

のような記述になっている。

どうもテーブルカラムのうち最初から7つまでを表示するように生成されるらしく(それ以上のものは/**/でコメントアウト)、したがって画面を再掲すると

no title


のように7項目のみが表示されるというわけである。

余談:zii.widgets.grid

http://www.yiiframework.com/doc/api/1.1/#zii.widgets.grid

zii.widget.gridという名前空間に所属するクラスがあって、一見zii.widgetのサブクラスのように見えるが違う。zii.widgetがCBaseControllerを親クラスとするのに対して、CGridColumnを親クラスとするのだ。カラム描画に特化した一種のヘルパーと考えておきたい。テーブル描画を扱う時はzii.widget.grid.CGridViewがあって、http://サイト名/index.php?r=tl/adminの場合のビューに使われている。

http://www.yiiframework.com/doc/api/1.1/CGridView



余談2:CArrayDataProvider

DBデータではなくて配列を表示操作したいときはCArrayDataProviderというのがある。

$array=array(array=>'name'=>'orange','id'=>'1','price=>'300'...);
$dataProvider=new CArrayDataProvider(
  $array,
  array('sort'=>array('attributes'=>'name','id'))),
  'pagination'=>array('pageSize'=>10),
);
$this->render('index',array('dataProvider'=>$dataProvider));

みたいに書ける。

データ入力

コントローラー:actionCreate

http://サイト名/tl/index.php?r=tl/create

public function actionCreate()
 {
  $model=new tl;
  // Uncomment the following line if AJAX validation is needed
  // $this->performAjaxValidation($model);
  if(isset($_POST['tl']))
  {
    $model->attributes=$_POST['tl'];
    if($model->save())
      $this->redirect(array('view','id'=>$model->id));
  }
  $this->render('create',array(
  	'model'=>$model,
 ));
}

ビュー:views/tl/creat.php views/tl/_form.php

上のコントローラーからviews/tl/creat.phpが呼び出され

<?php echo $this->renderPartial('_form', array('model'=>$model)); ?>

renderPartialで views/tl/_form.php が描画される。

_form.phpは次のようになっている。

<div class="form">
<?php $form=$this->beginWidget('CActiveForm', array(
 'id'=>'tl-form',
 'enableAjaxValidation'=>false,
)); ?>
 <p class="note">Fields with <span class="required">*</span> are required.</p>
 <?php echo $form->errorSummary($model); ?>
 <div class="row">
    <?php echo $form->labelEx($model,'start'); ?>
    <?php echo $form->textField($model,'start'); ?>
    <?php echo $form->error($model,'start'); ?>
 </div>
略
<div class="row buttons">
 <?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); ?>
</div>
<?php $this->endWidget(); ?>
</div>

というわけで、beginWidget/endWidgetとCActiveFormのお勉強。

beginWidget/endWidgetメソッド

CBaseControllerに実装されているメソッド。

public CWidget beginWidget(string $className, array $properties=array ( ))

CWebApplicationにあるgetWidgetFactory()(*1)を用いてWidgetクラスのインスタンスをつくる(CWidget:initでイニシャライズ)。

properties配列は、当該Widgetクラスの変数にループで代入されることになる(*2)。

endWidgetメソッドでは、beginWidgetで始まった処理が入っている$this->_widgetstackオブジェクトをrun()した上で、同オブジェクトを返す。

CActiveForm

というわけで、beginWidgetで動いているCActiveFormについて。

http://www.yiiframework.com/doc/api/1.1/CActiveForm/

CBaseControllerー>CWidgetを親クラスとする。ActiveRecord用Formのヘルパーである。

enableAjaxValidation

コントロール側のactionCreate()コードにある

// Uncomment the following line if AJAX validation is needed

//$this->performAjaxValidation($model);

のコメントを外し、_form.phpのbeginWidgetのオプション'enableAjaxValidation'=>falseをtrueにした場合、ajaxでのバリデーションが効き、入力時に画面遷移なしに下の画象のようにエラー表示が可能となる(この場合durationEvent値はmodelのルールで0あるいは1以外の入力のみを可能にしているので3が入力されるとエラーとなる)。

no title


$form->errorSummary

画面遷移した時に、入力値にバリデーションエラーが有った場合のメッセージ表示。

no title



*1: これはyiiルートframework/web/CWidgetFactory.phpを呼び出している

*2: CWidgetFactory.phpのcreateWidgetメソッド内

this file --> last modified:2012-05-17 17:19:32