FuelPHP

FuelPHPで会員管理(アプリ編その1)

更新日:

AdminコントローラとUserコントローラの作成

以前、『FuelPHPで簡単認証システム』でSimpleAuthの基本的な使い方を見てみましたが、実際にあれだけでは、ユーザー管理は出来ません。そこで、今日から数回に分けて、実際にユーザー管理用のアプリケーションを作成してみたいと思います。第1回目の今日は、AdminコントローラとUserコントローラの基本的な部分を作成します。

基本設定

1. Authパッケージを有効にする方法は、『FuelPHPで簡単認証システム』で説明していますので、参照して下さい。

2. usersテーブルは下記のように作成して下さい。

CREATE TABLE `users` (
 `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
 `username` VARCHAR( 50 ) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL ,
 `password` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL ,
 `group` INT NOT NULL DEFAULT 1 ,
 `email` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL ,
 `last_login` VARCHAR( 25 ) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL ,
 `login_hash` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL ,
 `profile_fields` TEXT CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL ,
 `created_at` INT( 11 ) UNSIGNED NOT NULL , 
 `updated_at` INT( 11 ) UNSIGNED NOT NULL ,
UNIQUE ( `username` , `email`)
)

3. まず、SimpleAuthドライバのユーザーグループを有効にします。48行目から59行目を下記のように修正(グループのコメント記号//を削除してグループを作成)します。

app/config/simpleauth.php

'groups' => array(
 /**
 * ユーザーグループの設定
 * rolesに所属グループを記述します
 * 所属グループは追加できます。
 */
 -1 => array('name' => 'Banned', 'roles' => array('banned')),
 0 => array('name' => 'Guests', 'roles' => array()),
 1 => array('name' => 'Users', 'roles' => array('user')),
 50 => array('name' => 'Moderators', 'roles' => array('user', 'moderator')),
 100 => array('name' => 'Administrators', 'roles' => array('user', 'moderator', 'admin')),
 ),

4. 今回はOrmを使用してデータベース接続を行いますので、Ormパーケージを有効にしていない方は、有効にして下さい(config.phpの204行目あたり)。Ormパッケージが分からない方は、『FuelphpのORM(オブジェクトリレーショナルマッパー)』のページを参照して下さい。

app/config/config.php

'packages' => array(
 //パッケージのオートロード
 'email',
 'orm',
 'auth',
 ),

5. 今回は全体的にテーマクラスを利用してサイトを作成したいと思います。後日作成しますが、テーマクラスを利用してサイトを作成しておけば、コントローラを全くいじらずにスマートフォン用のテンプレートと併用することが出来ます。テーマクラスの設定方法は、『FuelPHPのテーマクラスの概要』に記述していますので、参照して下さい。

Adminモデルの作成

6. Adminモデルを作成します。バリデーション用のvalidateメソッドとページネーション用のpagedataメソッド、Configファイルの配列を取得して再構成するためのconfig_groupsメソッドも追加作成します。

7. 『FuelPHPでページネーション(実践編その6)』では、ページネーションのデータを、controller上で取得して、ビューに受け渡していましたが、データの受け渡しはModelから行うようにしました(この方がコントローラがすっきりするのでいいのかな)。このpagedataメソッドはModel_User::pagedata('表示行数')で取得することが出来ます。引数を省略すると1ページに10行のデータが表示されます。尚、themeクラスでコントローラからビューへのデータの渡し方は、『Adminによるユーザープロフィールの操作』をご覧下さい。

app/classes/model/admin.php

<?php
class Model_Admin extends \Orm\Model{
 //テーブル名の指定(usersテーブルを指定)
 protected static $_table_name='users';
 //プロパティのセット
 protected static $_properties = array(
 'id',
 'username',
 'password',
 'group',
 'email',
 'last_login',
 'login_hash',
 'profile_fields',
 'created_at',
 'updated_at'
 );

 protected static $_observers = array(
 'Orm\Observer_CreatedAt' => array(
 'events' => array('before_insert'),
 'mysql_timestamp' => false,
 ),
 'Orm\Observer_UpdatedAt' => array(
 'events' => array('before_save'),
 'mysql_timestamp' => false,
 ),
 );

 //バリデーションの設定
 public static function validate($factory){
 //バリデーションのインスタンス化
 $val = Validation::forge($factory);
 //バリデーションフィールドの追加
 $val->add_field('username', 'ユーザー名', 'required|max_length[255]');
 $val->add_field('email', 'メールアドレス', 'required|valid_email');
 $val->add_field('password', 'パスワード', 'required|max_length[100]');
return $val;
 }
 //ページデータの取得
 public static function pagedata($lines=10){
 //配列の初期化
 $data=array();
 //データ件数の取得
 $count=Model_User::count();
 //Paginationの環境設定
 $config=array(
 'pagination_url'=>'admin/index',
 'uri_segment'=>3,
 'num_links'=>2,
 'per_page'=>$lines,
 'total_items'=>$count,
 'template'=>array(
 'wrapper_start'=>'<div class="pagination"><ul>',
 'wrapper_end'=>'</ul></div>',
 'previous_start'=>'<li class="previous">',
 'previous_end'=>'</li>',
 'previous_inactive_start'=>'<li class="active"><a href="#">',
 'previous_inactive_end'=>'</a></li>',
 'next_inactive_start'=>'<li class="active"><a href="#">',
 'next_inactive_end'=>'</a></li>',
 'next_start'=>'<li class="next">',
 'next_end'=>'</li></ul>',
 'active_start'=>'<li class="active"><a href="#">',
 'active_end'=>'</a></li>',
 ));
 //Paginationのセット
 Pagination::set_config($config);
 //ページデータの取得
 $data['users']=Model_User::find()
 ->order_by('created_at','desc')
 ->limit(Pagination::$per_page)
 ->offset(Pagination::$offset)
 ->get();
 return $data;
 }
 //Configデータの取得
 public static function config_groups(){
 //config/simpleauthのgroups配列を取得
 $config=Config::get('simpleauth.groups');
 //取得配列の再構成
 foreach($config as $key=>$row):
 $groups[$key]=$row['name'];
 endforeach;
 //配列$groupsを返す
 return $groups;
 }
}

Configファイルから中身の配列を取得する為(77行目から87行目)にConfigクラスについて詳しく調べました。よろしければ、『FulePHPのConfigクラス』を参照して下さい。

基本テンプレートファイルの作成

8. それでは、基本となるテンプレートファイルを作成したいと思います。レイアウトはTwitter Bootsrap2.0を使用して作成します。

app/views/template.php

<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
 <title><?php echo isset($title) ? $title : 'Winroad徒然草'; ?></title>
 <meta name="viewport" content="width=device-width,minimum-scale=1">
 <?php echo Asset::css('bootstrap.min.css');?>
 <?php echo Asset::css('bootstrap-responsive.min.css');?>
<!--自分専用スタイルシート-->
 <?php echo Asset::css('my-style.css');?>
</head>
<body>
<div class="navbar">
 <div class="navbar-inner">
 <div class="container">
 <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
 <span class="icon-bar"></span>
 <span class="icon-bar"></span>
 <span class="icon-bar"></span>
 </a>
 <?php echo Html::anchor('user/index/',isset($title) ? $title : 'Winroad徒然草',array('class'=>'brand'))?>
 <div class="nav-collapse">
 <ul class="nav">
 <?php if(Auth::check()):?>
 <li><?php echo Html::anchor('user/logout/','ログアウト')?></li>
 <?php if(Auth::member(100)):?>
 <li><?php echo Html::anchor('admin/create/','新規作成')?></li>
 <li><?php echo Html::anchor('admin/index/','管理室へ')?></li>
 <li>
 <?php echo Form::open(array('action'=>'admin/search','method'=>'post','class'=>'navbar-search pull-left')); ?>
 <?php echo Form::input(array('name'=>'search','type'=>'text','class'=>'search-query','placeholder'=>'検索'));?>
 <?php echo Form::close();?>
 </li>
 <?php endif;?>
 <?php endif;?>
 </ul>
 </div>
 </div><!--/nav--> 
 </div><!--/navbar-innner-->
</div><!--/navbar-->
<div class="container">
 <div id="header">
<!--ヘッダーがあれば表示する-->
<?php echo isset($header) ? $header : '' ;?>
 </div><!--/header-->
 <div class="row" id="content">
<!--サイドバーがあれば表示する-->
<?php echo isset($sidebar) ? $sidebar : '' ;?>
<!--ここにコンテンツを表示します。-->
 <?php echo isset($content) ? $content : ''; ?>
 </div><!--/content-->
 <div id="footer" style="background-color:yellow; text-align: center;">
 <p>copyright(c)2012 WinRoad徒然草</p>
 <p>表示速度{exec_time}s 使用メモリ{mem_usage}mb</p>
 <p>app/views/template.php</p>
 </div><!--/footer-->
</div><!--/container-->
 <?php echo Asset::js('jquery-1.7.2.min.js');?>
 <?php echo Asset::js('bootstrap.min.js');?>
</body>
</html>

ビューファイルの作成

9. ユーザー登録用のページを作成します。フィールドセットで作成した方が楽なのですが、フィールドセットは自動的にテーブルが作成されます。フィールドセットで作成されるテーブルフォームはちょっとださいので、フィールドセットは今回使用しません。

app/views/admin/create.php

<div class="row">
<div class="span4 offset2">
<h2 style="text-align:center">新規ユーザ登録</h2>
<?php echo Form::open(array('name'=>'create','method'=>'post','class'=>'form-horizontal')); ?>
<?php echo '<div class="alert-error">'.Session::get_flash('error').'</div>'?>

<div class="control-group">
 <label class="control-label" for="username">ユーザー名</label>
 <div class="controls">
 <?php echo Form::input('username',Input::post('username'));?>
 </div>
</div>
<div class="control-group">
<label class="control-label" for="email">Eメール</label>
<div class="controls">
<?php echo Form::input('email',Input::post('email'));?>
</div>
</div>
<div class="control-group">
<label class="control-label" for="password">パスワード</label>
<div class="controls">
<?php echo Form::password('password');?>
</div>
</div>
<div class="control-group">
<label class="control-label" for="group">所属グループ</label>
<div class="controls">
<?php echo Form::select('group',null,array('group'=>Model_Admin::config_groups()));?>
</div>
</div>
<div class="form-actions">
<?php echo Form::submit('submit','新規登録',array('class' => 'btn btn-primary span3'));?>
</div>
<?php echo Form::close();?>
</div><!--/span4 offset2-->
</div><!--/row-->

ポイントは28行目です。Adminモデル(77行目から87行目)で、Configファイルsimpleauth.phpで設定している(最初の方で作成したgroup)の配列を取得してgroup名の一覧を表示し、group名からgroupのIDを取得できるようにしています。尚、このページは、アクセス権まで作成できますので、必要なユーザー(管理者)を作成したら、管理者のみしかアクセスできないようにコントローラでアクセス制限をかけます。

10. 登録ユーザー一覧表示用のindexビューファイルを作成します。

app/views/admin/index.php

<div class="row">
<h3><?php echo Session::get_flash('success','ユーザー一覧');?></h3>
</div>
<div class="row">
<div class="span8">
</div>
</div>
<div class="row">
<table class="table table-bordered table-striped">
<thead>
 <tr>
 <th>ID</th>
 <th>ユーザ名</th>
 <th class="sp_none">グループ</th>
 <th>Eメール</th>
 <th class="sp_none">最終ログイン</th>
 <th class="sp_none">作成日</th>
 <th class="sp_none">更新日</th>
 </tr>
</thead>
<tbody>
<?php foreach($users as $row): ?>
 <tr>
 <td><?=$row->id?></td>
 <td><?php echo Html::anchor('adimin/edit/'.$row->id,$row->username);?></td>
 <th class="sp_none"><?=$row->group?></th>
 <th><?=$row->email?></th>
 <th class="sp_none"><?=$row->last_login>0 ? date('Y/m/d',$row->last_login) : "";?></th>
 <td class="sp_none"><?=date('Y/m/d',$row->created_at);?></td>
 <td class="sp_none"><?=$row->updated_at>0 ? date('Y/m/d',$row->updated_at) : "";?></td>
 </td>
 </tr>
<?php endforeach;?>
</tbody>
</table>
<?php echo Pagination::create_links();?>
</div>

Adminコントローラの作成

11. adminコントローラに一覧表示用のaction_indexと新規ユーザー作成用のaction_createを作成します。コードの中のコメントにも書いてあるように、3~4行目と18行目はAdminユーザーを登録後に削除します。

app/classes/controller/admin.php

<?php
class Controller_Admin extends Controller_Template{
 /*初期Adiminユーザーを登録後、3~4行目と
 *18行目は削除して下さい。
 //アクセス制限
 public function before(){
 parent::before();
 //ログインしていなければ
 if(!Auth::check()){
 //ログインページへ移動
 Response::redirect('user/login');
 //ログインしていてもAdminでなければ
 }elseif(!Auth::member(100)){
 //user/indexページへ移動
 Response::redirect('user/index');
 }
 }
 初期ユーザー登録後、この行は削除します*/
 //トップページの表示
 public function action_index()
 {
 //テーマのインスタンス化
 $this->theme=\Theme::forge();
 //テーマにテンプレートのセット
 $this->theme->set_template('template');
 //テーマのテンプレートにタイトルをセット
 $this->theme->get_template()->set('title','Winroad徒然草');
 //テーマのテンプレートにビューとページデータをセット
 $this->theme->get_template()->set('content',$this->theme->view('admin/index',Model_Admin::pagedata()));
 return $this->theme;
 }
 //新規ユーザー登録
 public function action_create()
 {
 //POST送信なら
 if(Input::method() == 'POST')
 {
 //バリデーションの初期化
 $val=Model_Admin::validate('create');
 $val->add_field('password', 'パスワード', 'required|max_length[100]');
 //バリデーションOKなら
 if($val->run())
 {
 //POSTデータを受け取る
 $username=Input::post('username');
 $email=Input::post('email');
 $password=Input::post('password');
 $group=Input::post('group');
 //重複確認
 $username_count=Model_Admin::find()->where('username',$username)->count();
 $email_count=Model_Admin::find()->where('email',$email)->count();
 //ユーザー名が重複していたら
 if($username_count>0){
 Session::set_flash('error', 'ユーザー名が重複しています');
 Response::redirect('admin/create');
 }else{
 //Eメールアドレスが重複していたら
 if($email_count>0){
 Session::set_flash('error', 'Eメールアドレスが重複しています');
 Response::redirect('admin/create');
 }
 //Authのインスタンス化
 $auth=Auth::instance();
 //もしユーザー登録されたら
 if($auth->create_user($username,$password,$email,$group))
 {
 //登録成功のメッセージ
 Session::set_flash('success', '<span class="btn btn-primary span8">新規ユーザーの『'.$username.'』を追加しました</span><br>');
 //indexページへ移動
 Response::redirect('admin/index');
 }else{
 //データが保存されなかったら
 Session::set_flash('error', '登録されませんでした');
 }
 }
 }
 //バリデーションNGなら
 Session::set_flash('error', $val->show_errors());
 }
 //POST送信でなければ
 //テーマのインスタンス化
 $this->theme=\Theme::forge();
 //テーマにテンプレートのセット
 $this->theme->set_template('template');
 //テーマのテンプレートにタイトルをセット
 $this->theme->get_template()->set('title','Winroad徒然草');
 //テーマのテンプレートにビューとページデータをセット
 $this->theme->get_template()->set('content',$this->theme->view('admin/create'));
 return $this->theme;
 }
}

12. 実際にバリデーションが機能しているかどうかメールアドレスに不正文字入力をしてみるときちんと弾いてくれました。

13. 49行目から61行目までの重複確認がないと、既存のユーザー名かEメールアドレスを入力すると例外エラーが発生します。

ユーザー登録

14. それでは、Administratorを1名登録したいと思います。

15. ついでにModeratorsとUsersも1名ずつ登録しました。

loginページの作成

16. さて、ここで、adminコントローラの3~4行目と18行目を削除しますが、ログイン用のページが無いとアクセスできません。そこで、先にログイン用のページを作成します。ログイン用のページはAdminコントローラでは無く、Userコントローラからアクセスするようにします。

app/views/user/login.php

<div class="row">
<div class="span4 offset2">
<h2 style="text-align:center">ログイン</h2>
<?php echo Form::open(array('name'=>'login','method'=>'post','class'=>'form-horizontal')); ?>
<?php echo '<div class="alert-error">'.Session::get_flash('error').'</div>'?>

<div class="control-group">
 <label class="control-label" for="username">ユーザー名</label>
 <div class="controls">
 <?php echo Form::input('username',Input::post('username'));?>
 </div>
</div>
<div class="control-group">
<label class="control-label" for="password">パスワード</label>
<div class="controls">
<?php echo Form::password('password');?>
</div>
<div class="form-actions">
<?php echo Form::submit('submit','ログイン',array('class' => 'btn btn-primary span3'));?>
</div>
<?php echo Form::close();?>
</div><!--/span4 offset2-->
</div><!--/row-->

Userモデルの作成

17. Userモデルを作成します。Eメールアドレスがユニークキー(重複不可)データですので、アクセスしているユーザーのEメールアドレスからusersテーブルの個人データを、userdataメソッドで取得しています(39行目から48行目)。

app/classes/model/user.php

<?php
class Model_User extends \Orm\Model{
 //テーブル名の指定(モデル名の複数形なら省略可)
 protected static $_table_name='users';
 //プロパティのセット
 protected static $_properties = array(
 'id',
 'username',
 'password',
 'group',
 'email',
 'last_login',
 'login_hash',
 'profile_fields',
 'created_at',
 'updated_at'
 );

 protected static $_observers = array(
 'Orm\Observer_CreatedAt' => array(
 'events' => array('before_insert'),
 'mysql_timestamp' => false,
 ),
 'Orm\Observer_UpdatedAt' => array(
 'events' => array('before_save'),
 'mysql_timestamp' => false,
 ),
 );

 //バリデーションの設定
 public static function validate($factory){
 //バリデーションのインスタンス化
 $val = Validation::forge($factory);
 //バリデーションフィールドの追加
 $val->add_field('username', 'ユーザー名', 'required|max_length[255]');
 $val->add_field('password', 'パスワード', 'required|max_length[100]');
 return $val;
 }
 //個人データの取得
 public static function userdata(){
 //data配列の初期化
 $data=array();
 //Authのインスタンス化
 $auth=Auth::instance();
 $email=$auth->get_email();
 $data=Model_User::find('first',array('where'=>array('email'=>$email)));
 return $data;
 }
 public static function theme($template,$content){
 //テーマのインスタンス化
 $theme=\Theme::forge();
 //テーマにテンプレートのセット
 $theme->set_template($template);
 //テーマのテンプレートにタイトルをセット
 $theme->get_template()->set('title','Winroad徒然草');
 //テーマのテンプレートにビューとページデータをセット
 $theme->get_template()->set('content',$theme->view($content,Model_User::userdata()));
 return $theme;
 }
}

49行目から59行目のメソッドは、実際に使用していませんが、頻繁に発生するテーマクラスの呼び出しを簡略化するために作成しました。このメソッドは、Model_User::theme(テンプレート名,contentに使用するビューファイル名)で使用することが出来ます。
例えば、下記のUserコントローラの13行目から21行目を下記のように修正することが出来ます。
return Model_User::theme('template','user/index'); 
尚、このメソッドは静的メソッドですので、Adminコントローラからも同様に呼び出すことが出来ます。 

Userコントローラの作成

18. Userコントローラを作成します。

app/classes/controller/user.php

<?php
class Controller_User extends Controller_Template{
 //beforeアクション
 public function before(){
 parent::before();
 if(!Auth::check() and Request::active()->action != 'login'){
 //ログインページへ移動
 Response::redirect('user/login');
 }
 }
 //トップページの表示
 public function action_index(){
 //テーマのインスタンス化
 $theme=\Theme::forge();
 //テーマにテンプレートのセット
 $theme->set_template('template');
 //テーマのテンプレートにタイトルをセット
 $theme->get_template()->set('title','Winroad徒然草');
 //テーマのテンプレートにビューとページデータをセット
 $theme->get_template()->set('content',$theme->view('user/index',Model_User::userdata()));
 return $theme;
 }
 //ログイン
 public function action_login(){
 //POST送信なら
 if(Input::method() == 'POST'){
 //Authのインスタンス化
 $auth=Auth::instance();
 //資格情報の取得
 if($auth->login(Input::post('username'),Input::post('password'))){
 //認証OKならトップページへ
 Response::redirect('user/index');
 }else{
 //認証が失敗したときの処理
 Session::set_flash('error', 'ユーザー名かパスワードが違います。');
 }
 }
 //テーマのインスタンス化
 $theme=\Theme::forge();
 //テーマにテンプレートのセット
 $theme->set_template('template');
 //テーマのテンプレートにタイトルをセット
 $theme->get_template()->set('title','Winroad徒然草');
 //テーマのテンプレートにビューとページデータをセット
 $theme->get_template()->set('content',$theme->view('user/login'));
 return $theme;
 }
 //ログアウト
 public function action_logout(){
 Auth::logout();
 //ログインページへ移動
 Session::set_flash('error', 'ログアウトしました。');
 Response::redirect('user/login');
 }
}

User/indexページの作成

19. ログイン後に移動するindexページを作成します。

app/views/user/index.php

<?php $user=Model_User::userdata();?>
<?php $group=Model_Admin::config_groups();?>
<div class="row">
<h3><?php echo Session::get_flash('success','ようこそ'.Auth::get_screen_name().'さん');?></h3>
</div>
<div class="row">
<div class="span8">
</div>
</div>
<div class="row">
<table class="table table-bordered table-striped">
 <thead>
 <tr>
 <th>項目</th>
 <th>内容</th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <th>ID</th>
 <td><?php echo $user['id'];?></td>
 </tr>
 <tr>
 <th>ユーザ名</th>
 <td><?php echo $user['username'];?></td>
 </tr>
 <tr>
 <th>Eメール</th>
 <td><?php echo $user['email'];?></td>
 </tr>
 <tr>
 <th>所属グループ</th>
 <td><?php echo $group[$user['group']];?></td>
 </tr>
 <tr>
 <th>最終ログイン</th>
 <td><?php echo date('Y/m/d',$user['last_login']);?></td>
 </tr>
 <tr>
 <th>作成日</th>
 <td><?php echo date('Y/m/d',$user['created_at']);?></td>
 </tr>
 <tr>
 <th>更新日</th>
 <td><?php echo $user['updatee_at']>0 ? date('Y/m/d',$user['updatede_at']) : '';?></td>
 </tr>
 <tr>
 <th>プロフィール</th>
 <td><?php echo $user['profile_fields'];?></td>
 </tr>
</tbody>
</table>
</div>

20. 最後にAdminコントローラの3~4行目と18行目を削除すると、これでログインしていない人がUserコントローラにアクセスしてきたら、全てloginページへ移動するようになりましたし、ログインしていても、Administrator以外の人がAdminコントローラにアクセスしてきたら、すべて、user/indexページへ移動するようになりました。

21. 実際にブラウザからアクセスしてみましょう。

22. Administratorでアクセスすると『新規作成』『管理室へ』『検索窓』のリンクが表示されます。

23. Administrator以外でアクセスすると『ログアウト』以外のリンクは表示されません。

次回から、少しずつ修正を加えて本格的に使えるアプリケーションにしていきたいと思います。本日は以上です。

-FuelPHP
-

Copyright© WinRoad徒然草 , 2018 All Rights Reserved Powered by AFFINGER5.