WinRoadのLaravel4初心者講座

次世代PHPフレームワークのLaravel4を初心者向けに解説して参ります。

Winroad徒然草の管理人がお届けします
開発メモ

ページネーションで嵌まってしまいました

ページネーションで結構嵌まったので、メモしておきたいと思います。

<?php
 class UserController extends BaseController{
 public function getIndex(){
 //ユーザー名をセッションデータから取得
 $username=Session::get('username');
 //検索データを取得
 $data['users']=User::where('username','like',"%".$username."%")->paginate(10);
 return View::make('user/index',$data);
 }
 public function postIndex(){
 //フォームからのデータを取得
 $username=Input::get('username');
 //取得したデータをセッションに保存
 Session::put('username',Input::get('username'));
 //検索データを取得
 $data['users']=User::where('username','like',"%".$username."%")->paginate(10);
 //検索データと一緒にビューを開く
 return View::make('user/index',$data);
 }
 }
  • まず検索したデータはpostIndexで検索値を取得し、ページネーションにデータを格納する。
  • $users->links()で、ページネーションの1ページ目は普通に開くのだが、2ページ目以降がまともに開かない。
  • そこで、getIndex()で、取得するデータは、セッションから検索値を取得することで2ページ目以降も開くようになった。
  • あまりかっこいい方法では無いが、とりあえず動くので良しとする。
  • もう少しかっこいいコードが見つかれば変更する予定。

クエリースコープ

7行目のコードがあまりかっこよくないのでクエリースコープを作成することにします。models/User.phpに下記のコードを追加します。これで、7行目は、User::like(‘username’,$username)->paginate(10)と書き換えることが出来ます。

//複合語の曖昧検索
 public function scopeLike($query,$name,$search){
 //全角スペースを半角スペースに変換
 $keyword = mb_convert_kana($search,'s');
 //検索文字を半角スペースで区切って配列に代入
 $keywords=preg_split('/[\s]+/', $keyword,-1,PREG_SPLIT_NO_EMPTY);
 //配列の数だけ繰り返し
 foreach($keywords as $value){
 $query->where($name,'like','%'.$value.'%');
 }
 return $query;
 }
  • 検索文字を空白(全角又は半角)で区切ることによりand検索が出来るように作成
  • User::like($name,$search)という風に使用。
  • 第1引数に検索フィールド名、第2引数に検索文字列を指定します。
  • 4行目:全角スペースを半角に変換します。
  • 6行目:指定した検索文字列を半角スペースで区切って配列に代入します。
  • 8-10行目:作成した配列の数だけ曖昧検索をかけます。

Bootstrap用Formマクロ集

作成したフォームマクロの覚え書き用のページです。少しずつ増やしていきます。

チェックボックス用

Form::macro('cb',function($name,$value){
 $markup='';
 $markup.='<div class="checkbox">'."\n";
 $markup.='<input type="checkbox"';
 $markup.=' name="'.$name.'"';
 $markup.=' value="'.$value.'"';
 if(Input::get($name)==$value){
 $markup.=' checked';
 }
 $markup.='> ';
 $markup.=$value;
 $markup.='</div>';
 return $markup;
 });
  • Form::cb(チェックボックス名,値)で使用
  • 7-9行目:バリデーション時に前ページでチェックを入れた箇所にチェックが入っているように作成

ラジオボタングループ用

Form::macro('rb',function($name,$values){
 $markup="";
 foreach($values as $key=>$value){
 $markup.='<div class="radio">'."\n";
 $markup.='<input type="radio"';
 $markup.=' name='.$name;
 $markup.=' value='.$key;
 if(Input::get($name)==$key){
 $markup.=' checked';
 }
 $markup.='> '.$value."\n";
 $markup.='</div>'."\n";
 }
 return $markup;
  • Form::rb(ラジオボタン名,値とラベル名の配列)で使用
  • 3-13行目:第2引数$valuesの数だけ繰り返し
  • 8-10行目:ラジオボタンの値と取得したキーが一致したラジオボタン(直前値)にチェックが入るように作成

非表示イベント付きradioボタン

//表示非表示Javascriptイベント付きradioボタン
 Form::macro('jsHiddenRadio',function($name,$values){
 $event=$name.'Event()';
 $onload=$name.'Event';
 $input=Session::get($name,'');
 $markup='';
 $markup.='<script type="text/javascript">'."\n";
 $markup.='function '.$event.'{'."\n";
 $markup.='radio = document.getElementsByName("'.$name.'") '."\n";
 $markup.='if(radio[0].checked){'."\n";
 $markup.='document.getElementById("'.$name.'").style.display = "none";'."\n";
 $markup.='}else if(radio[1].checked){';
 $markup.='document.getElementById("'.$name.'").style.display = "";'."\n";
 $markup.='}'."\n".'}'."\n";
 $markup.='window.onload ='.$onload.';'."\n";
 $markup.='</script>'."\n";
 //$valueの数だけ繰り返し
 foreach($values as $key=>$value){
 $markup.='<div class="radio">';
 $markup.='<label>';
 $markup.='<input type="radio"';
 $markup.=' name='.$name;
 $markup.=' value="'.$key.'"';
 $markup.=' onclick='.$event;
 if(Input::get($name)==$key){
 $markup.=' checked';
 }
 $markup.='> '.$value;
 $markup.='</label>';
 $markup.='</div>';
 }
 $markup.='<div id='.$name.' style="display:none;">';
 return $markup;
 });
 //表示非表示イベントの終了タグ
 Form::macro('hiddenEnd',function(){
 $markup='</div>';
 return $markup;
 });
  • Form::jsHiddenRadio(ラジオボタン名,値の配列)で使用。Form::hiddenEnd()と一緒に使用するように作成
  • ラジオボタンのキー1にチェックが入っている場合、Form::jsHiddenRadio()とForm::hiddenEnd()の間のタグが表示されるように作成
  • 基本的にYES/NOで表示非表示を切り替える場合に使用する目的で作成
  • 3つ以上のラジオボタンを扱う場合には修正が必要。

アコーディオンパネル

//アコーディオンパネルの開始タグ
 Form::macro('accordionOpen',function($id,$value){
 $markup='';
 $markup.='<div class="panel panel-default">'."\n";
 $markup.='<div class="panel-heading">'."\n";
 $markup.='<h4 class="panel-title">'."\n";
 $markup.='<a data-toggle="collapse" data-parent="#accordion" href="#collapse'.$id.'">'."\n";
 $markup.=$value."\n";
 $markup.='</a>'."\n";
 $markup.='</h4>'."\n";
 $markup.='</div>'."\n";
 $markup.='<div id="collapse'.$id.'" class="panel-collapse collapse">'."\n";
 $markup.='<div class="panel-body">'."\n";
 return $markup;
 });
 //アコーディオンパネルの終了タグ
 Form::macro('accordionClose',function(){
 $markup='';
 $markup.='</div>';
 $markup.='</div>';
 return $markup;
 });
  • Form::accordinonOpen()~Form::accordionClose()の間にデータを表記
  • Form::accordionOpen(id名,アコーディオンパネルのタイトル)で使用します。

使用例

<div class="panel-group" id="accordion">
 {{Form::accordionOpen('fubi',Session::get('fubi','資材の不備はありませんか'))}}
 {{Form::jsHiddenRadio('fubi',['ありません','あります'])}}
 <label>不備箇所にチェックを入れてください</label> 
 {{Form::cb('fubi1','破損箇所あり')}}
 {{Form::cb('fubi2','劣化箇所あり')}}
 {{Form::cb('fubi3','亀裂箇所あり')}}
 {{Form::cb('fubi4','数量不足')}}
 {{Form::cb('fubi5','サイズ違い')}}<br>
 <label>内容と状況を記述して下さい</label>
 {{Form::txt('fubi_naiyou','内容')}}
 {{Form::txt('fubi_joukyou','状況')}}
 {{Form::hiddenEnd()}}
 {{Form::accordionClose()}}
</div>
  •  2行目:バリデーションに応じて表題のタイトルを変化させるように作成
  • 3行目:ラジオボタンの『あります』にチェックが入っていたら、4-12行目を表示。デフォルトでは、『ありません』のボタンが選択されている状態。ラジオボタンのどちらも選択されていない状態にするためには、[‘1’=>’ありません’,’2’=>’あります’]とする。

Horizontalフォーム

//Horizontalフォームのオープンタグ
Form::macro('horizontalOpen',function($options=[]){
$markup = "";
$url=$_SERVER['REQUEST_URI'];
$class='form-horizontal';
if(!empty($options['url'])){
$url=$options['url'];
}
if(!empty($options['class'])){
$class=' '.$options['class'];
}
$options="'class'=>$class";
$markup.=Form::open([
'class'=>$class,
'url'=>$url,
]);
});
//Horizontalのテキストフォームタグ
 Form::macro('horizontalText',function($name,$value,$options=[]){
 //markupをリセット
 $markup = '';
 //$labelをリセット
 $label = '';
 //$typeに初期値'text'を代入
 $type = 'text';
 //$placeholderをリセット
 $placeholder = '';
 //$typeが指定されていたら
 if (!empty($options['type'])){
 //$typeにオプションの指定値を代入
 $type = $options['type'];
 }
 //$placeholderが指定されていたら
 if (!empty($options['placeholder'])){
 //$placeholderにオプションの指定値を代入
 $placeholder = $options['placeholder'];
 }
 $label=$name;
 //ラベル名が指定されていたら
 if (!empty($options['label'])){
 //$labelにオプションのラベル値を代入
 $label = $options['label'];
 }
 //フィールドサイズとラベルサイズの初期値を設定
 $field_size = 8;
 $label_size = 4;
 //$sizeが指定されていたら
 if (!empty($options['size'])){
 //$field_sizeとlabel_sizeに指定値を代入
 $field_size = $options['size'];
 $label_size = 12 - ($field_size);
 }
 $class = 'form-control';
 //$classが指定されていたら
 if (!empty($options['class'])){
 //$classに空白+クラスの指定値を代入
 $class = " " . $options['class'];
 }
 //必須項目オプション
 $required = '';
 //$requiredが指定されていたら
 if (!empty($options['required'])){
 //$classに空白+requiredの指定値を代入
 $required = " " . $options['required'];
 }
 //マークアップの生成
 $markup.='<div class="form-group">'."\n";
 $markup.='<label';
 $markup.=' class="col-sm-'.$label_size;
 $markup.=' control-label">'."\n";
 $markup.=' '.$label;
 $markup.='</label>'."\n";
 $markup.='<div class="col-sm-'.$field_size;
 $markup.='">'."\n";
 $markup.='<input type="'.$type.'"';
 $markup.=' name="'.$name.'"';
 $markup.=' value="'.$value.'"';
 $markup.=' class='.$class;
 $markup.=' placeholder="'.$placeholder.'"';
 $markup.=' '.$required.'>'."\n";
 $markup.='</div></div>'."\n";
 return $markup;
 });
  •  2-7行目:Horizontalフォーム用のオープンタグです。閉じタグは、Form::close()でOKです。
  • 9-64行目:Horizontalフォームのテキスト用タグです。
  • Form::horizontalText(‘フィールド名’,’値’,オプションの配列)で使用します。
  • input type=”” の初期値はtextに指定してあります。オプションで何も指定しなければ、基本的にtextが代入されます。
  • ラベル名の初期値はフィールド名にしてあります。オプションで何も指定しなければ、フィールド名が代入されます。
  • フィールドサイズの初期値を8に指定してあります。オプションで何も指定しなければ、ラベルサイズが4、フィールドサイズが8になります。サイズを指定するときは、フィールドのサイズのみを指定します。つまり、オプションで’size’=>’9’と指定すると、ラベルサイズが3で、フィールドサイズが9になります。
  • 44-52行目:クラスの初期値をclass=”form-control”にしていてあります。クラスを追加したいときは、オプションで指定できます。 
  • 59-65行目:必須項目オプションです。’required’=>’required’を指定すると、必須項目になります。何も入力しないで、送信ボタンをクリックすると、『このフィールドは必須です』とエラーメッセージが送信されます。必須項目だけのバリデーションなら、これだけで事足ります。

Form::horizontal()の使用例

<h2>{{$user->name}}データの更新</h2>
 {{ Form::horizontalOpen() }}
 {{ Form::horizontalText('name',$user->name,['label'=>'氏名'])}}
 @if($errors->has('name'))
 <div class="alert alert-danger">
 {{$errors->first('name')}}
 </div>
 @endif
 {{ Form::horizontalText('email',$user->email,['label'=>'Eメール','type'=>'email'])}}
 @if($errors->has('email'))
 <div class="alert alert-danger">
 {{$errors->first('email')}}
 </div>
 @endif
 {{ Form::horizontalText('password','',['label'=>'パスワード','type'=>'password'])}}
 @if($errors->has('password'))
 <div class="alert alert-danger">
 {{$errors->first('password')}}
 </div>
 @endif
 <br>
 {{Form::submit('送信',[
 'class'=>'btn btn-primary col-sm-6 col-sm-offset-4'
 ])}}
 {{ Form::close() }}

 tableフォーム

//テーブルFormの開始タグ
 Form::macro('tableOpen',function($class=null){
 $markup='<table class="table';
 if(!empty($class)){
 $markup.=' '.$class;
 }
 $markup.='">'."\n";
 return $markup;
 });
 //テーブルFormの終了タグ
 Form::macro('tableClose',function(){
 $markup='</table>';
 return $markup;
 });
  • 2-9行目:テーブルの開始タグです。オプションでクラスの追加が出来ます。
  • (例)Form::open(‘table-borderd’);
  • 複数追加するときは、スペースの後に続けます。
  • 11-14行目:テーブルの終了タグです。</table>だけですので、なくても大丈夫ですが、開始と終了をセットにしておいたほうが間違いが少ないと思います。

テーブルリスト(一覧表示)

//テーブルの一覧(表題及びデータ)
 Form::macro('tableList',function($items,$data){
 $markup='';
 $markup='<thead>'."\n";
 $markup.='<tr>'."\n";
 foreach($items as $label){
 $markup.='<th>'.$label.'</th>'."\n";
 }
 $markup.='</tr>'."\n";
 $markup.='<tr>'."\n";
 foreach($data as $datum){
 foreach($items as $key=>$value){
 $markup.='<td>'.$datum->$key.'</td>'."\n";
 }
 $markup.='</tr>'."\n";
 }
 return $markup;
 });
  • Form::tableList(‘表示項目’,’データ’) で使用します。
  • 第1引数に、フィールド名と項目名を連想配列で指定します。
  • 第2引数に、表示するデータを指定します。

tableListの使い方

{{Form::tableOpen()}}
  {{Form::tableList(['name'=>'ユーザー名','email'=>'Eメール'],$users)}}
{{Form::tableClose()}}
  • 第2引数の$usersから取得したデータを元に、第1引数の’name’と’email’のフィールドの値をデータの数だけ繰り返して表示します。そして、’ユーザー名’と’Eメール’はテーブルのラベル(<th>項目)として表示します。

テーブルビュー(明細表示)

//テーブルの詳細(表題及びデータ)
 Form::macro('tableView',function($items,$data,$options=[]){
 $markup='';
 foreach($items as $key=>$label){
 $markup.='<tr>'."\n";
 if(!empty($options['width'])){
 $markup.='<th width="';
 $markup.=$options['width'];
 $markup.='">';
 }else{
 $markup.='<th>';
 }
 $markup.=$label;
 $markup.='</th>'."\n";
 $markup.='<td>'.$data->$key.'</td>'."\n";
 $markup.='</tr>'."\n";
 }
 return $markup;
 });
  • Form::tableView()は、テーブルでデータの詳細を表示するために作成しました。
  • Form::tableView()は、3つの引数を持ちます。
  • 第1引数は、詳細表示する項目です。’フィールド名’=>’ラベル名’(例:’name’=>’ユーザー名)で指定します。
  • 第2引数は、詳細表示するデータです。
  • 第3引数は、オプションです。デフォルトでは、何も指定していません。とりあえず、テーブルのサイズ(thカラムのサイズ)だけを作成しました。 [‘width’=>’20%’] みたいな感じで指定します。

tableViewの使い方

@extends('tbs.base')
 @section('content')
 <h3>ユーザー詳細</h3>
 {{Form::tableOpen('table-bordered table-striped')}}
 {{Form::tableView([
 'id'=>'ID',
 'name'=>'ユーザー名',
 'email'=>'Eメール',
 'tel'=>'電話番号',
 'z_code'=>'郵便番号',
 'address'=>'住所',
 ],$user,['width'=>'20%'])}}
 {{Form::tableClose()}}
 @stop

リンク付きテーブルリスト

上記のテーブルリストを若干修正しましたので、下記に記述しておきます。

//テーブルの一覧(表題及びデータ)
 Form::macro('tableList',function($items,$data,$links){
 $markup='';
 $markup.='<thead>'."\n";
 $markup.='<tr>'."\n";
 foreach($items as $label){
 $markup.='<th>'.$label.'</th>'."\n";
 }
 $markup.='</tr>'."\n";
 $markup.='<tr>'."\n";
 foreach($data as $datum){
 foreach($items as $key=>$value){
 $markup.='<td>'.$datum->$key.'</td>'."\n";
 }
 foreach($links as $url=>$label){
 $url=$url."/".$datum->id;
 $markup.='<td>';
 $markup.=link_to($url,$label);
 $markup.='</td>'."\n";
 }=
 $markup.='</tr>'."\n";
 }
 return $markup;
 });
  • Form::tableList()の引数を3つに修正しました。
  • 第1引数は、項目フィールドを項目名とラベル名の連想配列で、指定します。
  • (例)[‘name’=>’氏名’,’email’=>’Eメール’] のように指定します。
  • 第2引数は、一覧表示するデータを指定します。
  • そして、第3引数にリンク先を追加しました。’url’=>’ラベル名’で指定します。
  • (例)[‘user/view’=>’詳細’,’user/delete’=>’削除’] のように指定します。

開発メモ10_MessageControllerの作成

今日は、メッセージ管理の基本となるMessageControllerを作成したいと思います。

  • 当初、messagesテーブルには、グループ(ロール)宛てのメッセージ用をrole_idで管理しようと作成していましたが、計画変更で、role_nameを配列(シリアライズ)で保存することにしました。
  • 前回のSetupControllerのmessagesテーブル作成の19行目は下記に変更します。
    $table->text(‘role_name’)->nullable();

それでは、MessageControllerを下記のように作成します。ちょっと長いので、分解して表示しています。

app/controllers/MessageController.php

<?php
 class MessageController extends BaseController{
 /*
 |----------------------------------------
 | コンストラクター
 |----------------------------------------
 */
 public function __construct(){
 //authフィルター
 $this->beforeFilter('auth');
 //全POSTにcsrfフィルターの適用
 $this->beforeFilter('csrf',array('on'=>'post'));
 }
//ユーザー受信メッセージ
 protected function own(){
 $user=Auth::user();
 $role_id=DB::table('role_user')
 ->where('user_id',$user->id)
 ->lists('role_id');
 $role_names=Role::whereIn('id',$role_id)->lists('name');
 $query=Message::where('recipient_id',$user->id)
 ->orderBy('created_at','desc');
 foreach($role_names as $role_name):
 $query->orWhere('role_name','LIKE',"%$role_name%");
 endforeach;
 $query->where('user_id','<>',$user->id);
 return $query;
 }
 //ユーザー送信メッセージ
 protected function sent(){
 $user=Auth::user();
 $query=Message::where('user_id',$user->id)
 ->orderBy('created_at','desc');
 return $query;
 }
 //カラム内の配列の取得
 protected function order($column){
 $work=Work::find(Auth::user()->id);
 $order=count($work->$column == 0) ? unserialize($work->$column) : array();
 return $order;
 }
  •  とりあえず、MessageController内で使用するメンバ関数を作成しました。
  • own()は、ログインユーザーの受信メッセージを取り出すために作成しています。
  • 4-7行目:EloquentORMでは、簡単にログインユーザーのrole_nameを取り出せなかったので、クエリービルダーで取り出しました。
  • 10-12行目:現状では、ユーザーごとにRoleも一つで管理しようと考えていますが、後々ユーザーに複数のRoleを与えることになった場合の為に作成しています。
  • 13行目:これを入れておかないと、自分で送ったメッセージが自分へ届くことになります。
  • 17-23行目:sentメソッドは、特に作るほどのことでもないのですが、own()のついでに作りました。
  • 24-28行目:worksテーブルのカラム内に配列を保存することが多いので、簡単に、取り出すために作成しました。
/*
 |------------------------------------
 | TOPページ
 |------------------------------------
 */
 public function getIndex($action=null){
 if($action == 'sent'):
 $messages=$this->sent()->paginate();
 $data['title']='送信メッセージ一覧';
 $data['action']='sent';
 else:
 //ユーザーの受信メッセージを取得
 $messages=$this->own()->paginate();
 $data['title']='受信メッセージ一覧';
 $data['action']=null;
 endif;
 $data['messages']=(count($messages) != 0) ? $messages : null;
 return View::make('message/index',$data);
 }
  •  TOPページは、メッセージの一覧表示用に使用しますが、受信メッセージと送信メッセージは、全く同じビューで表示しますので、引数で、受信と送信を変えるようにしました。つまり、message/indexで受信メッセージ、message/index/sentで送信メッセージを表示します。
  • 8、13行目:先ほど作成したメンバ関数を呼び出し、ページネーション処理します。
  • 17行目:$messagesは呼び出したのがオブジェクトなので、isset()では、trueになりますので、coutn()で、該当メッセージがあるかどうかを判断しました。
/*
 |------------------------------------
 | 新規メッセージ作成
 |------------------------------------
 */
 public function getCreate($action=null){
 if($action == 'user'){
 $recipi=User::all()->lists('name','id');
 $data['user']=array_except($recipi,array(1));
 $data['title']='個人宛メッセージ';
 $url='/user';
 }elseif($action == 'role'){
 $roles=Role::all()->lists('name','id');
 $data['roles']=array_except($roles,array(1));
 $data['title']='グループ宛メッセージ';
 $url='/role';
 }else{
 $roles=Role::all()->lists('name','id');
 $data['roles']=array_except($roles,array(1));
 $data['title']='全体メッセージ';
 $url='/create';
 }
 return View::make('message'.$url,$data);
 }
  • 8行目:全受信ユーザーのリストを作成します。
  • 9行目:Super Adminは、業務に絡むことはありませんので、リストから除外しました。
  • 13行目:Roleリストを作成します。
  • 14行目:RoleリストからもSuper Adminは除外しました。
  • Createメソッドは、引数で、アクションごとに分けました。http://ドメイン名/message/userでは、個人宛のメッセージ作成、message/roleでは、ロール(グループ)ごとのメッセージ作成、引数指定が無い場合、全体メッセージのビューへ移動します。
  • このビューも同じビューで代用できますが、見づらくなりますので、別々のビューとして作成しました。別々ビューで作成する場合、$data[‘title’]は、不要ですが(^0^)
  • グループ宛メッセージとRoleメッセージは全く同じです。違うのは、デフォルトでRoleが全選択されているのとされていないだけです。
/*
|------------------------------------
| 新規メッセージ作成POST処理
|------------------------------------
*/

public function postCreate(){
$inputs=Input::all();
$rules=array(
'subject'=>'required',
'body'=>'required',
);
//バリデーション
$val=Validator::make($inputs,$rules);
if($val->fails()){
return Redirect::back()
->withInput()
->withErrors($val);
}
//データ登録
$role_name=Input::get('role_name');
$role_name=isset($role_name) ? serialize($role_name) : null;
$inputs['role_name']=$role_name;
$message=Message::create($inputs);
  •  7-19行目:POST処理の一連の流れは特に問題は無いと思います。データを受け取ってバリデーション処理するだけです。
  • 21-23行目:データ登録の時に、受け取ったrole_nameをシリアライズ処理して、$inputs[‘role_name’]に差し替えています。
/*****************************************
* 未読処理
* worksテーブルのmessage項目に配列保存
*****************************************/

$recipient_id=Input::get('recipient_id');
//個人宛メッセージなら
if(isset($inputs['recipient_id'])){
//受信メッセージの整理
$work=Work::find($recipient_id);
//旧メッセージ
$old=isset($work->message) ? unserialize($work->message) : array();
//新メッセージ
$new=array($message->id);
//配列の併合
$merge=!isset($old) ? array_merge($old,$new) : $new;
//登録データの保存
$work->message=serialize($merge);
$work->save();
  •  ここは、個人宛メッセージのworksテーブルへの登録処理になります。worksテーブルは、各ユーザーの未読メッセージ、未読コメント、未処理Todoを保存するために作成しています。
  • 8行目:各Createビューから、送られてきたデータにrecipient_idがあるかどうかで、個人宛かRole宛てかを判断しています。
  • 12行目:旧メッセージをworksテーブルのmessageカラムから取り出しアンシリアライズ処理します。
  • 16-19行目:旧メッセージと新メッセージを併合します。旧メッセージがなければ、新メッセージだけをシリアライズ処理して保存します。
/*****************************************
* メール送信
*****************************************/

$data['recipient']=User::find($recipient_id)->name;
$data['sender']=Auth::user()->name;
$data['title']=Input::get('subject');
$data['body']=Input::get('body');
Mail::send('emails.user.message',$data,function($m){
$user=User::find(Input::get('recipient_id'));
$email=$user->email;
$name=$user->name;
$m->to($email,$name)->subject('Builwing通信');
});
  • メール送信処理をします。個人宛てメッセージには、自動的にメール送信するように作成しました。
  • 5-8行目:メール送信用のビューファイルに送るためのデータです。
  • 10-12行目:ユーザーのメールアドレスとユーザー名を取得するための処理です。
  • ※当たり前のことですが、Mail::send()メソッド内には、外部(Createメソッド内)の変数は使えません。(^0^)。私も最初、勘違いをして、$mailと$nameを5-8行目あたりに、記述して、メールが送れずに悩みましたので、一言書いておきます。
//Role宛てメッセージなら
}else{
//新メッセージ
$new=array($message->id);
//指定ロールIDを取得
$role_id=Role::whereIn('name',Input::get('role_name'))->lists('id');
//指定ユーザーIDを取得
$user_id=DB::table('role_user')
->whereIn('role_id',$role_id)
->lists('user_id');
//指定ユーザーを取得
$users=User::whereIn('id',$user_id)->get();
//ユーザーの数だけ繰り返し
foreach($users as $user):
//旧メッセージの取得
$work=Work::find($user->id);
$old=isset($work->message) ? unserialize($work->message) : array();
//配列の併合
$merge=isset($old) ? array_merge($old,$new) : $new;
//登録データの保存
$work->message=serialize($merge);
$work->save();
endforeach;
  • Role宛てに送るメッセージの未読処理です。
  • 4行目:worksテーブルの各カラムには、idの配列しか登録していませんので、新しく作成したメッセージのidを取得します。
  • 6-12行目:送られてきたrole_nameの配列からユーザーを取得する為の処理です。
  • 6行目:結構whereInメソッドは重宝します。まず、rolesテーブルのnameカラムからgetで取得したrole_nameの配列内に含まれるname(ロール名)をidでリスト化します。
  • 8-10行目:そのロールのidリストからユーザーのidリストを取得しています。Eloquent ORMでもっと簡単にできるのかも知れませんが、私にはこれが一番簡単でした。
  • 試していませんが、9-10行目の間に->distinct()の重複処理が必要かも知れません。
  • 14-23行目:取得したユーザーの数だけ未読データの保存処理をします。
/*****************************************
* メール送信
*****************************************/
if(Input::get('mail') == 1){
//メール送信手続き
$data['recipient']=Role::find(Input::get('role_name'))->name;
$data['sender']=Auth::user()->name;
$data['body']=Input::get('body');
Mail::send('emails.user.message',$data,function($m){
//指定ロールIDを取得
$role_id=Role::whereIn('name',Input::get('role_name'))
->lists('id');
//指定ユーザーIDを取得
$user_id=DB::table('role_user')
->whereIn('role_id',$role_id)
->lists('user_id');
//指定ユーザーを取得
$users=User::whereIn('id',$user_id)
->get();
//ユーザーの数だけ繰り返し
foreach($users as $user):
$m->cc($user->email,$user->name);
endforeach;
$m->subject(Input::get('subject'));
});
}
}
//トップページへ移動
return Redirect::to('message/index');
}
  • Role宛てのメール処理です。
  • 4行目:ビューにメールを送信するかどうかの判定用のラジオボタンを設置しました。
  • 6-8行目:メール送信用のビューに送るデータです。
  • 9-25行目:メールの送信処理です。これもworksテーブルとの処理同様、受け取ったrole_nameの配列からユーザーを取得してユーザーの数だけccに追加してメールを送信しています。
 /*
|------------------------------------
| 未読メッセージ
|------------------------------------
*/
public function getUnread($id=null,$key=null){
//ログインユーザーのWorkオブジェクトを取得
$work=Work::find(Auth::user()->id);
//未読メッセージのID配列取得
$message=$this->order('message');
//未読メッセージがなければ
if(count($message)== 0 or $message == null){
return View::make('message/unread')
->with('warning',' 未読メッセージはありません');
}
//指定IDが未読メッセージの中にあれば
if(isset($id) and in_array($id,unserialize($work->message))){
//未読メッセージIDの配列を取得
$unread=unserialize($work->message);
//未読メッセージを削除
array_pull($unread,$key);
//配列のキーを前に詰める
$unread=array_values($unread);
$message=serialize($unread);
$work->message=$message;
$work->save();
//削除後に明細ページへ移動
return Redirect::to('message/view/'.$id);
}
//配列の数だけオブジェクトを取得
foreach($message as $key=>$value):
$data['message'][$key]=Message::find($value);
endforeach;
return View::make('message/unread',$data);
}
  • Role(グループ)宛て、メッセージの未読処理です。
  • getUnreadメソッドは、2つの引数を持っています。第1引数が、メッセージのID、第2引数が配列のキー番号です。
  • 10行目:最初の方で作成したメンバ関数order()を使って、worksテーブルのメッセージカラムから配列を取得しています。
  • 17-21行目:ビューより指定した$idがユーザーのworksテーブルのメッセージカラム内に配列として保存されていたら、array_pullヘルパー関数で未読メッセージのIDを削除して、既読処理をします。
  • 23行目:この処理を施さないと、配列のキーに隙間ができるので、ビューで指定するキー番号と保存されているキー番号が一致しなくなります。
  • worksテーブルのmessageカラム内にある配列の数だけメッセージオブジェクトを取得します。
/*
|------------------------------------
| 明細ページ
|------------------------------------
*/
public function getView($id=null){
$own=$this->own()->where('id',$id)->get();
if($own->count() == 0 ):
$data['title']='指定のメッセージはありません';
$data['action']=null;
return View::make('message/index',$data);
endif;
$data['message']=Message::find($id);
$data['title']='メッセージ明細';

return View::make('message/view',$data);
}
  • viewメソッドで、明細ページを表示します。
  • 7-12行目:ユーザー所有のメッセージの中から引数で指定したidのオブジェクトを取得します。この処理は、ブラウザからid番号で指定したときにユーザーが受け取っていないメッセージの表示を防ぐために作成しました。この処理を行っていないと、他人宛のメッセージをのぞき見ることができます。
/*
|------------------------------------
| メッセージ検索
|------------------------------------
*/
 public function postSearch(){
 $input=Input::get('search');
 $action=Input::get('action');
 if($action == 'sent'):
 $data['messages']=$this->sent()
 ->where('subject','LIKE','%'.$input.'%')
 ->paginate();
 $data['title']='送信メッセージ検索';
 $data['action']='sent';
 else:
 $data['messages']=$this->own()
 ->where('subject','LIKE','%'.$input.'%')
 ->paginate();
 $data['title']='受信メッセージ検索';
 $data['action']=null;
 endif;
 return View::make('message/index',$data);
 }
  • メッセージ検索するための処理です。
  • ここで大事なのが、やはり10行目と16行目の処理です。この処理を行っていないと、自分宛でないメッセージまで見ることができます。
  • 社内サイトは、表示ページとかに関する事は、基本的にユーザー専用ページとAdmin専用のページですので、難しくはないのですが、一番気を付けないといけないのは、個人情報の点です。
  • とりあえず簡単な曖昧検索で作成しましたが、複合語の曖昧検索とかができるように作り替える予定です。

Messageの各ビュー

ビューは特に難しくはありませんが、indexビューと全体宛てのメッセージ作成用のビューを記述しておきます。

app/views/message/index.blade.php

@extends('layouts.f4.user.base')
@section('content')
<div class='row'>
<div class='large-8 column'>
@if(isset($title))
 <h4>{{ $title }}</h4>
@endif
</div>
<div class='large-4 column'>
<div class='row collapse'>
 {{ Form::open(array('url'=>'message/search')) }}
 <div class="large-9 small-6 columns">
 {{ Form::text('search','',array('placeholder'=>' メッセージ検索')) }}
 </div>
 <div class="large-3 small-6 columns">
 {{ Form::submit('検索',array('class'=>'button small')) }}
 </div>
 {{ Form::hidden('action',$action) }}
 {{ Form::close() }}
 </div>
</div>
</div>
@if(isset($messages))
<table width="100%" border="0">
<tr>
 <th>タイトル</td>
 <th>送信者</td>
 <th>日付</td>
</tr>
 <tr>
@foreach($messages as $message)
 <td width="70%">{{ HTML::link('message/view/'.$message->id,$message->subject,array('class'=>'button expand')) }}</td>
 <td width="12%">{{ User::find($message->user_id)->name }}</td>
 <td width="12%">{{ date('n月j日',strtotime($message->created_at)) }}</td>
 </tr>
@endforeach
</table>
<h5>{{ $messages->links() }}</h5>
@endif
@stop

laravel031

app/views/message/create.blade.php

@extends('layouts.f4.user.base')
@section('content')
{{ Form::open(array('class'=>'custom'))}}
<div class="panel">
<h3>{{ $title }}</h3>
<p>全体メッセージは、メールを送信するかどうかチェックを入れてください。</p>
</div>
<div class="large-4 columns">
<h4>{{ Form::label('送信先部署') }}</h4>
<?php $i=0;?>
<h5>
@foreach($roles as $role)
{{ Form::checkbox("role_name"."[$i]",$role,true) }} {{ $role }}<br>
<?php $i++;?>
@endforeach
</h5>
</div>
<div class="large-8 columns">
<h4>{{ Form::label('タイトル') }}</h4>
{{ Form::text('subject','',array('style'=>'ime-mode:active')) }}
 @if($errors->has('subject'))
 <div data-alert class="alert-box alert radius">
 {{ $errors->first('subject') }}
 </div>
 @endif
<h4>{{ Form::label('内容') }}</h4>
{{ Form::textarea('body','',array('style'=>'ime-mode:active')) }}
 @if($errors->has('body'))
 <div data-alert class="alert-box alert radius">
 {{ $errors->first('body') }}
 </div>
 @endif
 <br>
</div>
<div class="row">
 <div class="large-12 columns">
 <h5>{{ Form::radio('mail',1,true) }} メール送信</H5>
 <h5>{{ Form::radio('mail',0) }} メール未送信</h5>
 </div>
</div>
<div class="row">
 <div class="small-6 large-centered columns">
 {{ Form::submit('メッセージ送信',array('class'=>'button')) }}
 </div>
</div>
{{ Form::hidden('user_id',Auth::user()->id) }}
{{ Form::close() }}
@stop
  • 10-15行目:送信先Roleをチェックボックスで選択するように作成しました。
  • これで、role_nameが配列データとしてCreateアクションに送られます。
  • 受け取ったCreateアクションでは、role_nameカラムにシリアライズ処理して保存します。
  • 当初、role_idを保存して作成していましたが、各ユーザーがRole一つだけならいいのですが、Roleを複数所有するとなると検索が面倒になりそうでしたので、role_nameで保存することにしました。

laravel030

後は、メッセージの削除とそれに伴う、worksテーブルの未読該当データの削除処理あたりですが、この辺はあまり急ぎませんので後日作成します。

本日は以上です。

開発メモ9_Message管理

さて今日から、Message管理のプログラムを作成していこう。Message管理と言っても単純に社内の伝言板ではなく、Messageを管理するために、いろいろな機能を付ける必要がある。

考察

  1. 基本的に全てのユーザーが投稿できるようにすること。
  2. 個人宛のメッセージとグループ(Role)宛のメッセージを作成できるようにすること。
    1. 個人宛のメッセージはメールだけでもいいのかも知れないけれど、過去データの検索は、データベース管理した方が便利。
    2. 個人宛のメッセージは、自動的にメール送信も行う。
    3. グループ宛のメッセージは、メール送信の可否を選択できるようにする。
  3. 送信先に制約を付けること。
    1. Roleによる制約
    2. 社外グループ宛のメッセージをどうするかは思案中。
  4. 既読か未読かを判定する
    1. 未読データをユーザーに認識させる。
    2. 既読でもコメントが入力されると再度未読にする。
    3. 読むだけでなく処理しなくてはいけない案件もあるので、処理済みかどうかを判定する。
  5. 検索機能をつける
    1. 過去、自分が受け取ったメッセージ及び送信したメッセージを簡単に検索できるようにする。
    2. 送信者別検索、期間別検索、曖昧検索。
  6. 各個人のTODOに取り込む。
    1. メッセージをワンクリックで、TODOに取り込める機能を付ける。
    2. 現状のシステムでは、既読ボタンとTODOボタンを作成してワンクリックで取り込めるように作成しているが、ボタンを押さずに未読データとして残すことで、TODO代わりに使っている社員が多い。
    3. データを読み込んだ時点で、既読判定して、TODOに残したいなら、TODOボタンをクリックするように仕様変更予定。

テーブル構成

以上のことを考慮した上で、下記のテーブル構成を考えてみたいと思います。

messagesテーブル

  • 送信者ID、受信者ID(個人宛メッセージ専用)、ロール用ID(指定ロール以上の人は含む)、タイトル、メッセージ内容、目メール送信チェックの構成で作成使用と思います。尚、メッセージに対するコメントは、各コメントのカラムに配列保存する方法もが、検索とか並べ替えとかが面倒なので、今回はテーブルで作成します。

commentsテーブル

  • コメント送信者ID、受信者ID、ロール用ID、コメント内容のカラムで構成します。

worksテーブル

  • 今回、worksテーブルも一緒に作成します。worksテーブルは、各ユーザーのhasOneテーブルとして作成し、未読メッセージや未読コメント、また、後日作成しますが、未処理TODOのデータを配列保存したいと思います。このworksテーブルのデータは各個人のトップページに情報を提供するために使用します。Topページで、未読メッセージや未読コメント、あるいは未処理案件の情報を各ユーザーに促して、仕事の効率化に貢献できればいいなと考えています。

SetupControllerに追加

それでは、上記の各テーブルを作成するために、SetupControllerに下記のコードを追加します。

app/controllers/SetupController.php

/*
|---------------------------------------------
| messagesテーブルの作成
|---------------------------------------------
*/
 public function getMessages(){
 if(Schema::hasTable('messages')){
 $data['warning']='messagesテーブルが存在しますので、処理を中止します。';
 return View::make('setup/index',$data);
 }
 Schema::create('messages',function($table){
 $table->increments('id');
 //送信者ID
 $table->integer('user_id');
 //受信者ID(個人宛ポストの場合)
 $table->integer('recipient_id')->nullable();
 //受信RoleID
 $table->integer('role_id')->nullable();
 //タイトル
 $table->string('subject',200);
 //メッセージ内容
 $table->text('body');
 //メールチェック用
 $table->boolean('mail')->default(0);
 //コメント
 $table->text('comment')->nullable;
 //created_atとupdated_atの同時作成
 $table->timestamps();
 //ソフトデリート用
 $table->softDeletes(); 
 });
 $data['warning']='messagesテーブルを作成しました。';
 return View::make('setup/index',$data);
 } 
/*
|---------------------------------------------
| comments(コメント)テーブルの作成
|---------------------------------------------
*/
 public function getComments(){
 if(Schema::hasTable('comments')){
 $data['warning']='commentsテーブルが存在しますので、処理を中止します。';
 return View::make('setup/index',$data);
 }
 Schema::create('comments',function($table){
 $table->increments('id');
 //メッセージID
 $table->integer('message_id');
 //投稿者ID
 $table->integer('user-_id');
 //受信者ID(個人宛ポストの場合)
 $table->integer('recipient_id')->nullable();
 //RoleID(Role宛ポストの場合)
 $table->integer('role_d')->nullable();
 //コメント内容
 $table->text('body');
 //created_atとupdated_atの同時作成
 $table->timestamps();
 //ソフトデリート用
 $table->softDeletes(); 
 });
 $data['warning']='commentsテーブルを作成しました。';
 return View::make('setup/index',$data);
 } 
/*
|---------------------------------------------
| works(労務管理)テーブルの作成
|---------------------------------------------
*/
 public function getWorks(){
 if(Schema::hasTable('works')){
 $data['warning']='worksテーブルが存在しますので、処理を中止します。';
 return View::make('setup/index',$data);
 }
 Schema::create('works',function($table){
 $table->increments('id');
 //未読メッセージの配列(シリアライズ)
 $table->text('message')->nullable();
 //未読メッセージの配列(シリアライズ)
 $table->text('comment')->nullable();
 //未処理TODOの配列(シリアライズ)
 $table->text('todo')->nullable();
 //created_atとupdated_atの同時作成
 $table->timestamps();
 //ソフトデリート用
 $table->softDeletes(); 
 });
 $data['warning']='worksテーブルを作成しました。';
 return View::make('setup/index',$data);
 }

各モデルの作成

app/models/Message.php

<?php
class Message extends Eloquent{

 protected $softDelete=true;
 protected $guarded=array('id');

 //MessageはたくさんのCommentを持つ
 public function comments(){
 return $this->hasMany('Comment');
 }
 //MessageはUser(作成者)に属しています。
 public function user(){
 return $this->belongsTo('User');
 }
}

app/models/Comment.php

<?php
class Comment extends Eloquent{
 protected $softDelete=true;
 protected $guarded=array('id');

 //コメントはメッセージに属しています。
 public function message(){
 return $this->belongsTo('Message');
 }

 //コメントはユーザー(送信者)に属しています。
 public function user(){
 return $this->belongsTo('User');
 } 
}

app/models/Work.php

<?php
class Work extends Eloquent{
 protected $softDelete=true;
 protected $guraded = array('created_at', 'updated_at'); 

 public function user(){
 return $this->hasOne('User','id');
 }
}
  •  worksは各ユーザーを作成するときに同時に作成しますので、ユーザーのIDで検索しやすいように、リレーションのキーをuser_idではなくて、idにしました。
  • それに伴い、idも複数代入できるようにguradedから除外しました。

基本的なテーブルは作成したので、次回からいよいよ作成開始です。

開発メモ8_Verifyパッケージ

認証システムをSentry2を使って作った方が簡単かな、と思いSentry2でちょっと開発をしてみましたが、コマンド(メソッド)名が長く、Laravelの良さである(私がLaravelの良さだと思っている)記述のしやすさが損なわれるような気がしたので、Sentry2ほど多機能ではないですが、Verifyというパッケージの認証システムで開発を行うことにしました。

VefifyはLaravelのAuthシステムを継承して作成されていますので、Authの基本コマンドがそのまま使えますし、ModelもToddish\Verify\Models\Userを継承すれば、自前のUserモデルをそのまま使えますので、機能は少ないですが、便利です。

只、Artisanコマンドで、使用テーブルを取り込もうとしたら、エラーが発生して取り込めなかったので、いつものようにSetupコントローラにVerify関連テーブルを作成するためのコマンドを追加しました。

SetupControllerの作成

/*--------------------------------------------
|    verify関連テーブルの作成
|---------------------------------------------
*/
    //usersテーブルの存在確認
    public function getVerify(){
     if(Schema::hasTable('users')){
        $data['warning']='usersテーブルが存在しますので、処理を中止します。';
        return View::make('setup/index',$data);
    }

    Schema::create('permissions', function($table)
    {
            $table->engine = 'InnoDB';

            $table->increments('id');
            $table->string('name', 100)->index();
            $table->string('description', 255)->nullable();
            $table->timestamps();
        });

        // Create the roles table
        Schema::create('roles', function($table)
        {
            $table->engine = 'InnoDB';

            $table->increments('id');
            $table->string('name', 100)->index();
            $table->string('description', 255)->nullable();
            $table->integer('level');
            $table->softDeletes();
            $table->timestamps();
        });

        // Create the users table
        Schema::create('users', function($table)
        {
            $table->engine = 'InnoDB';

            $table->increments('id');
            $table->string('name', 30)->index();
            $table->string('password', 64)->index();
            $table->string('salt', 32);
            $table->string('email', 255)->index();
            $table->boolean('verified')->default(0);
            $table->boolean('disabled')->default(0);
            $table->timestamps();
            $table->softDeletes();
        });

        // Create the role/user relationship table
        Schema::create('role_user', function($table)
        {
            $table->engine = 'InnoDB';

            $table->integer('user_id')->unsigned()->index();
            $table->integer('role_id')->unsigned()->index();
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users');
            $table->foreign('role_id')->references('id')->on('roles');
        });

        // Create the permission/role relationship table
        Schema::create('permission_role', function($table)
        {
            $table->engine = 'InnoDB';

            $table->integer('permission_id')->unsigned()->index();
            $table->integer('role_id')->unsigned()->index();
            $table->timestamps();

            $table->foreign('permission_id')->references('id')->on('permissions');
            $table->foreign('role_id')->references('id')->on('roles');
        });

        $role_id = DB::table('roles')->insertGetId(array(
            'name' => Config::get('verify::super_admin'),
            'level' => 10,
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s')
        ));

        $user_id = DB::table('users')->insertGetId(array(
            'name' => 'admin',
            'password' => '$2a$08$rqN6idpy0FwezH72fQcdqunbJp7GJVm8j94atsTOqCeuNvc3PzH3m',
            'salt' => 'a227383075861e775d0af6281ea05a49',
            'email' => 'admin@example.com',
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s'),
            'verified' => 1,
            'disabled' => 0,
        ));

        DB::table('role_user')->insert(array(
            'role_id' => $role_id,
            'user_id' => $user_id,
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s')
        ));
        $data['warning']='全Verify関連の一括作成が完了しました。';
        return View::make('setup/index',$data);
    }
  • 上記のコマンドで、users,roles,permissons,role_user,pemission_roleの5つのテーブルが作成されます。
  • 只、既存テーブルの確認は、usersテーブルでしか行っていませんので、残りのテーブルの存在確認は、自分で行ってください。
  • 初期ユーザーは、adminでパスワードは「パスワード」でアクセスすることができます。パスワードは、Laravel3の時と同じなのですが、ドキュメントには記述がありませんでしたので、初めて使用する人は迷うと思います。
  • パスワードは標準のHashにsaltを掛け合わせて作成されていますので、標準のパスワードより複雑になっていますので、パスワードは解読しづらいように作成されています。
  • デフォルトからの変更点は、usernameをnameに変更しました。これは、Auth::user()->username、よりもAuth::user()->nameの方がすっきりすると言う私の私見で変更しました。
  • 上記の変更に伴い、vendor\toodish\verify\src\config.phpとapp\config\packages\toodish\verify\config.phpのidentyfied_byをusernameからnameに変更しましたが、これは、app\configの方だけで大丈夫だと思います。
  • また、もう1箇所vendor\toodish\verify\src\Toodish\Verify\Models\User.phpの28行目protected $fillableのusernameも同様にnameに変更しましたが、自前のモデルにprotected $fillableを記述しても大丈夫のはずです。

モデルの作成

UserモデルとRoleモデル、Permissonモデルは作成しなくても大丈夫ですが、やはり自前のモデルを使用したいので、モデルを下記のように作成します。

app/models/User.php

<?php
use Toddish\Verify\Models\User as VerifyUser;
//use Illuminate\Auth\UserInterface;
//use Illuminate\Auth\Reminders\RemindableInterface;

//class User extends Eloquent implements UserInterface, RemindableInterface {
class User extends VerifyUser{
    protected $fillable = array('name', 'password', 'salt', 'email', 'verified', 'deleted_at', 'disabled');
}
  • デフォルトのモデルにすぐに戻せるように、デフォルトは、コメントアウトしました。
  • これで、他のモデルとのリレーションとかも簡単に作成することができますし、スコープなども作成できます。

app/models/Role.php

<?php
use Toddish\Verify\Models\Role as VerifyRole;

class Role extends VerifyRole{
 protected $softDelete=true;
 protected $guarded=array('id'); 
}

app/models/Permissio.php

<?php
use Toddish\Verify\Models\Permission as VerifyPermission;

class Permission extends VerifyPermission{    
}

最後にapp/config/auth.phpの使用ドライバーをeloquentからverifyに変更すればVerifyが使えるようになります。

それでは、次回から基本的な認証システムは、Verifyを使用しながらサイトを構築していきたいと思います。これで、やっとメッセージシステムの開発に取りかかれます。

開発メモ7_Historyモデルの作成

データの更新情報を記録するためにhistoriesテーブルを作成することにします。

考察

  1. 過去データの更新履歴を1つのテーブルで管理するか、各テーブルごとの履歴専用テーブルを作成した方がいいのか思案中。
  2. とりあえず全てのプロフィールの更新履歴を保存するためのhistoriesテーブルを作成してから考えることにしよう。
  3. まず、必要な情報を整理してみよう。
    1. id=>increments(‘id’)
    2. 更新者名=>intger(‘user_id’)
    3. プロフィールID=>string(‘profile_id’)
    4. 更新項目名=>integer(‘item_id’)
    5. 更新前データ=>text(‘old’)
    6. 更新後データ=>text(‘new’)
    7. 更新理由=>text(‘reason’)->nullable()
    8. 作成日&更新日=>timestamps()……作成日だけでいいのかな?
    9. ソフトデリート用=>softDeletes()

SetupControllerに追加

SetupControllerにhistoriesテーブルとhistory_profileテーブルの作成を追加しよう。

app/controllers/SetupController.php
/*
|---------------------------------------------
| histories(履歴)テーブルの作成
|---------------------------------------------
| 履歴管理のテーブル
*/
 public function getHistories(){
 //categoriesテーブルの存在確認
 if(Schema::hasTable('histories')){
 $data['warning']='historiesテーブルが存在しますので、処理を中止します。';
 return View::make('setup/index',$data);
 }
 //categoriesテーブルの作成
 Schema::create('histories',function($table){
 $table->increments('id');
 //更新者ID
 $table->integer('user_id');
 //プロフィーID
 $table->integer('profile_id');
 //更新項ID
 $table->integer('item_id');
 //更新前データ
 $table->text('old');
 //更新後データ
 $table->text('new');
 //更新理由
 $table->text('reason')->nullable();
 //created_atとupdated_atの同時作成
 $table->timestamps();
 //ソフトデリート用
 $table->softDeletes(); 
 });
 $data['warning']='historiesテーブルを作成しました。';
 return View::make('setup/index',$data);
 }
app/controller/SetupController.php
/*
|---------------------------------------------
| history_profileピポットテーブルの作成
|---------------------------------------------
| HistoryとProfileテーブル管理用のテーブル
*/
 public function getHistoryProfile(){
 //history_profilesテーブルの存在確認
 if(Schema::hasTable('history_profile')){
 $data['warning']='history_profileテーブルが存在しますので、処理を中止します。';
 return View::make('setup/index',$data);
 }
 //history_profileテーブルの作成
 Schema::create('history_profile',function($table){
 $table->increments('id');
 //更新履歴
 $table->integer('history_id');
 //テーブル
 $table->integer('profile_id');
 //ソフトデリート用
 $table->softDeletes(); 
 });
 $data['warning']='history_profileピポットテーブルを作成しました。';
 return View::make('setup/index',$data);
 }

Historyモデルの作成

app/models/History.php
<?php
class History extends Eloquent{

 protected $softDelete=true;
 protected $guarded=array('id');

/*
|------------------------------------
| 多対多関係のリレーション
|------------------------------------
*/
 public function profile(){
 return $this->belongsToMany('Profile','history_profile');
 }
/*
|------------------------------------
| 1対多関係のリレーション
|------------------------------------
*/
 public function item(){
 return $this->belongsTo('Item');
 }
 public function user(){
 return $this->belongsTo('User');
 }
/*
|---------------------------------------------
| クエリースコープ
|---------------------------------------------
*/
 //ログイン中のユーザーの更新履歴を取得
 public function scopeOwner($query){
 $query->where('user_id','=',Auth::user())
 ->orderBy('created_at','desc')
 ->get();
 }
}

Profileデータ更新用のCreateアクション

app/controllers/ProfileController.php
/*
|----------------------------------------
| データ更新ページ
|----------------------------------------
*/
 public function getUpdateList(){
 $data['profile']=Profile::owner()->first();
 $data['address']=Profile::item('address');
 $data['body']=Profile::item('body');
 $data['license']=Profile::item('license');
 $data['labor']=Profile::item('labor');
 $data['family']=Profile::item('family');
 $data['note']=Profile::item('note');
 return View::make('profile/update-list',$data);
 }

 public function getUpdate($cat,$itm){
 //データの取得
 $data['cat']=$cat;
 $data['itm']=array_only(Profile::item($cat),array('name',$itm));
 //データ更新ページの表示
 return View::make('profile/update',$data);
 }

 public function postUpdate(){
 $cat=Input::get('cat');
 $itm=Input::get('itm');
 //データ受信
 $inputs=Input::except('_token','cat','itm','reason','old_itm');
 //バリルール
 foreach($inputs as $key=>$value){
 $rules=array($key=>'required');
 }
 //バリ処理
 $val=Validator::make($inputs,$rules);
 //バリNGなら
 if($val->fails()){
 return Redirect::back()
 ->withInput()
 ->withErrors($val);
 }

 //旧データと新データが同じで無ければ
 if(Input::get('old_itm') != Input::get($key)){

 /*******************
 * データ更新処理
 ********************/

 //受け取ったカテゴリの配列を取得
 $old_pro=Profile::item($cat);
 //受け取ったデータで旧データに修正
 $new_pro=array_merge($old_pro,$inputs);
 //受け取ったデータをシリアライズ
 $new_pro=serialize($new_pro);
 //受け取ったデータを保存
 $pro=Profile::owner()->first();
 $pro->$cat=$new_pro;
 $pro->save();

 /***********************
 * 更新履歴処理
 ***********************/

 //更新履歴の必要項目取得
 $user_id=Auth::user()->id;
 $profile_id=Profile::owner()->pluck('id');
 $item_id=Item::where('name','=',$itm)->pluck('id');
 //古い項目データの取得
 $old_itm=Input::get('old_itm');
 //新しい項目データの取得
 $new_itm=Input::get($key);
 $reason=Input::get('reason');

 $profile=Profile::find($profile_id);
 //更新履歴
 $history=new History();
 $history->user_id=$user_id;
 $history->profile_id=$profile_id;
 $history->item_id=$item_id;
 $history->old=$old_itm;
 $history->new=$new_itm;
 if(isset($reason)){
 $history->reason=$reason;
 }
 //修正プロフィールを取得
 $profile=Profile::find($profile_id);
 //更新履歴を保存
 $profile->history()->save($history);

 }
 //トップページへ戻る
 return Redirect::to('profile/view/'.Auth::user()->id);
 }

Profileモデルの修正

app/models/Profile.php
<?php
class Profile extends Eloquent{
 protected $softDelete=true;
 protected $guarded=array('id');
/*
|--------------------------------------------
| リレーションの指定
|--------------------------------------------
*/
 public function user(){
 return $this->hasOne('User');
 }

 public function history(){
 return $this->belongsToMany('History');
 }

/*
|--------------------------------------------
| 各項目内の配列を取得する
|--------------------------------------------
| 第2引数がtrueなら、未入力配列をリターンする
*/
 static public function item($item,$empty=false){
 //ログイン中のユーザーの最新情報を取得
 $query=Profile::where('user_id','=',Auth::user()->id)
 ->orderBy('created_at','desc')
 ->first();
 //第2引数がfalseなら項目内の配列をリターン
 if($query->$item != null and $empty==false){
 //アンシリアライズして、配列データを返します。
 $data=unserialize($query->$item);
 return $data;
 //第2引数がtrueなら項目内の未入力配列をリターン
 }elseif($query->$item != null and $empty == true){
 //カテゴリIDの取得
 $cat=Category::where('name',$item)->first();
 //登録アイテムを取得
 $regist_item=Item::where('category_id',$cat->id)->lists('id','name');
 //登録済みアイテムを取得
 $my_item=unserialize($query->$item);
 //登録アイテムから登録済みアイテムを取り除く
 foreach($my_item as $key=>$value):
 if(array_key_exists($key,$my_item)){
 array_pull($regist_item,$key);
 }
 endforeach;
 return $regist_item;
 //項目内が未入力で、第2引数がtrueなら
 }elseif($query->$item == null and $empty == true){
 //カテゴリIDの取得
 $cat=DB::table('categories')->where('name','=',$item)->pluck('id');
 //登録アイテムを取得
 $regist_item=DB::table('items')->where('category_id','=',$cat)->lists('id','name');
 return $regist_item;
 }else{
 //項目がNULLなら
 return null;
 }
 }
/*
|--------------------------------------------
| スコープ
|--------------------------------------------
*/
 public function scopeOwner($query){
 return $query->where('user_id','=',Auth::user()->id)
 ->orderBy('created_at','desc');
 }
}

更新履歴用ビューの作成

とりあえず、レイアウトを考えるのが面倒になってきたので、ほとんど全部リスト<ul>で作成しています。レイアウトは後で考えよう。

app/views/history/index.blade.php
@extends('layouts.f4.admin.base')
@section('content')
<h2>更新履歴</h2>
<ul>
@foreach($profiles as $profile)
 <li>プロフィール : {{ HTML::link('history/view/'.$profile->id,$profile->user->name) }} </li>
@endforeach
</ul>
@stop
app/views/history/view.blade.php
@extends('layouts.f4.admin.base')
@section('content')
@if(count($histories) != 0)
<ul>
 @foreach($histories as $history)
 <li>項目名:{{ Item::find($history->item_id)->name }}</li>
 <li>更新内容:{{ $history->old }}=>{{ $history->new }}</li>
 <li>更新日:{{ $history->created_at }}</li>
 <li>更新者:{{ $history->user->name }}</li>
 <hr>
 @endforeach
 </li>
</ul>
@else
<h2>更新履歴はありません</h2>
@endif
@stop

課題

作成していく上で、いくつかの課題が発生したので下記に記述しておきます。

  1. 今回は、ユーザー自身が更新した場合でプログラムを組んだので簡単だったが、Adminが修正する場合は、ユーザーの特定を間違わないようにする事。
  2. profile内のデータを配列で保存すると、配列内のデータの追加は簡単なのだが、配列内のキー(項目名)の修正がちょっとやっかいだ。値の修正は、各ユーザーごとなのだが、キーは、全ユーザー一斉に変更しなければならない。普通にテーブルで組んだ方が簡単なのだが。(^0^)

とりあえず、あとは、細かい修正を加えれば使用できると思うので、次回からは、Messsage管理用のプログラムの制作にかかろう。

開発メモ6_ProfileControllerの修正

Profileの追加項目をAdminが作成して、その内容を各ユーザーが登録するように変更したので、以前作成したProfileControllerを下記の内容で修正しました。

修正内容及び検討内容

  1. createメソッドは、Adminが作成した項目内容の未入力部分を入力するために使用します。
  2. Aminへの権限移行に伴い、項目作成のためのItemメソッドを削除。
  3. updateメソッドは、既に入力済みの値を変更するために使用する予定。
  4. 只、旧データを保存するのに、わざわざ新しいファイルを作成する必要があるかどうか検討中。
  5. 修正データは、履歴専用のテーブルを作成して、そこに保存した方がいいのではないのかという案もある。
  6. 修正履歴で必要な項目、①修正日、②修正した人、③修正項目名、④旧データ、⑤修正データ、⑥修正理由?

ProfileControllerの修正

app/controllers/ProfileController.php
<?php
class ProfileController extends BaseController{
/*
|----------------------------------------
| コンストラクター
|----------------------------------------
*/
 public function __construct(){
 //authフィルター
 $this->beforeFilter('auth');
 //全POSTにcsrfフィルターの適用
 $this->beforeFilter('csrf',array('on'=>'post'));
 }

/*
|----------------------------------------
| Topページ
|----------------------------------------
*/
 public function getIndex(){
 //データの取得
 $data['profiles']=Profile::all();
 return View::make('profile/index',$data);
 }
/*
|----------------------------------------
| 新規入力ページ
|----------------------------------------
| 新規フィールドの作成ではなくて、
| カテゴリ内の未入力項目データ入力
*/
 public function getCreate($column){
 //itemsテーブルの未入力データを取得
 $data['items']=Profile::item($column,true);
 //配列数が0なら
 if(count($data['items']) == 0){
 $message='未入力データはありません';
 }else{
 $message=null;
 }
 //カテゴリデータを取得
 $category=Category::where('name','=',$column)->first();
 //項目名データを取得
 $data['description']=$category->description;
 $data['column']=$category->name;
 //新規作成ページの表示
 return View::make('profile/create',$data)
 ->with('message',$message);
 }

 public function postCreate(){
 //POSTデータの取得
 $inputs=Input::except('id','column','_token');
 //バリデーションルールの作成
 foreach($inputs as $key=>$value):
 $rules[$key]='required';
 endforeach;
 //バリデーション処理
 $val=Validator::make($inputs,$rules);
 //バリデーションNGなら
 if($val->fails()){
 return Redirect::back()
 ->withInput()
 ->withErrors($val);
 }
 $column=Input::get('column');
 //データの新規作成
 $profile=Profile::where('user_id','=',Auth::user()->id)->first();
 //カラム内がNULLなら
 if($profile->$column == null){
 //入力値の配列を保存
 $profile->$column=serialize($inputs);
 $profile->save();
 }else{
 //カラム内がNULLでなければ
 $a=unserialize($profile->$column); //元データ
 $b=$inputs; //受信データ
 //2つの配列を併合して一つの配列へ
 $merge=array_merge($a,$b);
 //Proileを取得
 $profile=Profile::where('user_id','=',Auth::user()->id)->first();
 //シリアライズ処理した配列を入力
 $profile->$column=serialize($merge);
 //データ保存
 $profile->save();
 }
 //一覧ページへ移動
 return Redirect::to('profile/index')
 ->with('message','データを作成しました');
 }
/*
|----------------------------------------
| 詳細ページ
|----------------------------------------
*/
 public function getView($id){
 //データの取得
 $data['profile']=Profile::find($id);
 $data['address']=Profile::item('address');
 $data['body']=Profile::item('body');
 $data['license']=Profile::item('license');
 $data['labor']=Profile::item('labor');
 $data['family']=Profile::item('family');
 //プロフィール数が0で無ければ
 if(isset($data['profile'])){
 return View::make('profile/view',$data);
 }
 return Redirect::action('ProfileController@getCreate')
 ->with('message','プロフィールを作成してください');
 }
}

Profileモデルの修正

app/models/Profile.php
<?php
class Profile extends Eloquent{
 protected $softDelete=true;
 protected $guarded=array('id');
/*
|--------------------------------------------
| リレーションの指定
|--------------------------------------------
*/
 public function user(){
 return $this->belongsTo('User');
 }

/*
|--------------------------------------------
| 各項目内の配列を取得する
|--------------------------------------------
| 第2引数がtrueなら、未入力配列をリターンする
*/
 static public function item($item,$empty=false){
 //ログイン中のユーザーの最新情報を取得
 $query=Profile::where('user_id','=',Auth::user()->id)
 ->orderBy('created_at','desc')
 ->first();
 //第2引数がfalseなら項目内の配列をリターン
 if($query->$item != null and $empty==false){
 //アンシリアライズして、配列データを返します。
 $data=unserialize($query->$item);
 return $data;
 //第2引数がtrueなら項目内の未入力配列をリターン
 }elseif($query->$item != null and $empty == true){
 //カテゴリIDの取得
 $cat=Category::where('name',$item)->first();
 //登録アイテムを取得
 $regist_item=Item::where('category_id',$cat->id)->lists('id','name');
 //登録済みアイテムを取得
 $my_item=unserialize($query->$item);
 //登録アイテムから登録済みアイテムを取り除く
 foreach($my_item as $key=>$value):
 if(array_key_exists($key,$my_item)){
 array_pull($regist_item,$key);
 }
 endforeach;
 return $regist_item;
 //項目内が未入力で、第2引数がtrueなら
 }elseif($query->$item == null and $empty == true){
 //カテゴリIDの取得
 $cat=DB::table('categories')->where('name','=',$item)->pluck('id');
 //登録アイテムを取得
 $regist_item=DB::table('items')->where('category_id','=',$cat)->lists('id','name');
 return $regist_item;
 }else{
 //項目がNULLなら
 return null;
 }
 }
}

次回予定

次回は、履歴テーブルの作成について考えてみたいと思います。

開発メモ5_CategoryControllerの作成

ProfileControllerで考察したとおり、各個人で、項目名を作成するのは、統制がとれないので、Adminが作成した項目名に対して各個人が内容を入力するように変更したいと思います。

作成手順

  1. SetupControllerによる(items、categories)テーブルの作成
  2. CategoryControllerの作成
  3. ItemControllerの作成
  4. 各コントローラのルーターへの登録(結構忘れがち)
  5. Categoryモデル、Itemモデルの作成及びリレーションの設定
  6. category/createビューの作成
  7. item/createビューの作成

categoriesテーブルの追加作成

app/controller/SetupController.php
/*
|---------------------------------------------
| category(分類)テーブルの作成
|---------------------------------------------
| 1. 分類項目管理用のテーブル
| 2. profilesやbelongsで使用予定
*/
 public function getCategories(){
 //categoriesテーブルの存在確認
 if(Schema::hasTable('categories')){
 $data['warning']='categoriesテーブルが存在しますので、処理を中止します。';
 return View::make('setup/index',$data);
 }
 //categoriesテーブルの作成
 Schema::create('categories',function($table){
 $table->increments('id');
 //categorye名
 $table->string('name',50);
 //categoryの説明
 $table->text('description')->nullable();
 //created_atとupdated_atの同時作成
 $table->timestamps();
 //deleted_atカラムを追加
 $table->timestamp('deleted_at')->nullable(); 
 });
 $data['warning']='categoriesテーブルを作成しました。';
 return View::make('setup/index',$data);
 }

itemsテーブルの追加作成

app/controller/SetupController.php
/*
|---------------------------------------------
| items(項目)テーブルの作成
|---------------------------------------------
| 1. 項目管理用のテーブル
| 2. profilesやbelongsで使用予定
*/
 public function getItems(){
 //itemsテーブルの存在確認
 if(Schema::hasTable('items')){
 $data['warning']='itemsテーブルが存在しますので、処理を中止します。';
 return View::make('setup/index',$data);
 }
 //itemsテーブルの作成
 Schema::create('items',function($table){
 $table->increments('id');
 //iteme名
 $table->string('name',100);
 //分類コード
 $table->integer('category_id')->nullable();
 //created_atとupdated_atの同時作成
 $table->timestamps();
 //deleted_atカラムを追加
 $table->timestamp('deleted_at')->nullable(); 
 });
 $data['warning']='itemsテーブルを作成しました。';
 return View::make('setup/index',$data);
 }

CategoryControllerの作成

app/controllers/CategoryControllers.php
<?php
class CategoryController extends BaseController{
/*
|----------------------------------------
| コンストラクター
|----------------------------------------
*/
 public function __construct(){
 //adminフィルター
 $this->beforeFilter('admin');
 //全POSTにcsrfフィルターの適用
 $this->beforeFilter('csrf',array('on'=>'post'));
 }

 public function getIndex(){
 echo 'getIndexです。';
 }
 //新規分類項目の作成
 public function getCreate(){
 $data['categories']=Item::all();
 return View::make('category/create',$data);
 }
 public function postCreate(){
 //受信データ
 $inputs=Input::only('name','description');
 //バリルール
 $rules=array(
 'name'=>'required|alpha',
 'description'=>'required',
 );
 //バリチェック
 $val=Validator::make($inputs,$rules);
 if($val->fails()){
 return Redirect::back()
 ->withInput()
 ->withErrors($val);
 }
 //分類項目の登録
 Category::create($inputs);
 return Redirect::intended('admin');
 }
}

ItemControllerの作成

app/controllers/ItemController.php
<?php
class ItemController extends BaseController{
/*
|----------------------------------------
| コンストラクター
|----------------------------------------
*/
 public function __construct(){
 //adminフィルター
 $this->beforeFilter('admin');
 //全POSTにcsrfフィルターの適用
 $this->beforeFilter('csrf',array('on'=>'post'));
 }

 public function getIndex(){
 echo 'getIndexです。';
 }
 //新規項目の作成
 public function getCreate(){
 $data['categories']=Category::all()->lists('name','id');
 return View::make('item/create',$data);
 }
 public function postCreate(){
 //受信データ
 $inputs=Input::only('name','category_id');
 //バリルール
 $rules=array(
 'name'=>'required',
 );
 //バリチェック
 $val=Validator::make($inputs,$rules);
 if($val->fails()){
 return Redirect::back()
 ->withInput()
 ->withErrors($val);
 }
 //Itemの新規作成
 Item::create($inputs);
 return Redirect::intended('admin');
 }
}
  • 20行目:listsメソッドは、カラム値のリストを取得するメソッドです。第1引数にカラム名を指定します。第2引数は、オプションで、キーを指定することができます。

ルーターに各コントローラを追加

結構、このルーターにコントローラを追加する作業を忘れて、エラーになる。気を付けよう。

app/controllers/routes.php
Route::controller('category','CategoryController');
Route::controller('item','ItemController');

Categoryモデルの作成

app/models/Category.php
<?php
class Category extends Eloquent{
 protected $softDelete=true;
 protected $guarded=array('id');

/*
|------------------------------------
| 1対多関係のリレーション
|------------------------------------
| CategoryはたくさんのItemを持つ
| Category::find(1)->items;
| でCategoryのItemへアクセスできます。
|
*/
 //1対多関係のリレーション
 public function items(){
 return $this->hasMany('Item');
 }
}

Itemモデルの作成

app/models/Item.php
<?php
class Item extends Eloquent{

 protected $softDelete=true;
 protected $guarded=array('id');

/*
|------------------------------------
| 1対多関係のリレーション
|------------------------------------
| ItemはCategoryに属している
| Item::find(1)->categories;
| でItemのcategoryへアクセスできます。
|
*/
 public function categories(){
 return $this->belongsTo('Category');
 }
}

カテゴリ作成用ビュー

app/view/category/create.blade.php
@extends('layouts.f4.admin.base')
@section('content')
@if(isset($message))
<div data-alert class="alert-box alert radius">
 {{ $message }}
 <a href="#" class="close">&times;</a>
</div>
@endif
 {{ Form::open(array('url'=>'category/create')) }}
 {{ Form::label('カテゴリ名') }}
 {{ Form::text('name','',array('style'=>'ime-mode:disabled')) }}
 @if($errors->has('name'))
 <h5 style="color:red;text-align:center">
 {{ $errors->first('name') }}
 </h5>
 @endif
 {{ Form::label('説明') }}
 {{ Form::text('description','',array('style'=>'ime-mode:active')) }}
 @if($errors->has('description'))
 <h5 style="color:red;text-align:center">
 {{ $errors->first('description') }}
 </h5>
 @endif
 {{ Form::submit('送信',array('class'=>'button')) }}
 {{ Form::close() }}
@stop

Item作成用ビュー

app/views/item/create.blade.php
@extends('layouts.f4.admin.base')
@section('content')
@if(isset($message))
<div data-alert class="alert-box alert radius">
 {{ $message }}
 <a href="#" class="close">&times;</a>
</div>
@endif
 {{ Form::open(array('url'=>'item/create')) }}
 {{ Form::label('項目名') }}
 {{ Form::text('name','',array('style'=>'ime-mode:active')) }}
 @if($errors->has('name'))
 <h5 style="color:red;text-align:center">
 {{ $errors->first('name') }}
 </h5>
 @endif
 {{ Form::label('カテゴリ') }}
 {{ Form::select('category_id',$categories) }}
 {{ Form::submit('送信',array('class'=>'button')) }}
 {{ Form::close() }}
@stop

次回の予定

今回、項目名の作成は、Adminが行うように変更したのに伴い、ProfileControllerの修正を行う必要あり。次回は、ProfileControllerの修正をします。

開発メモ4_ProfileController

AdminControllerの作成はひとまず置いといて、ProfileControllerの作成にとりかかります。プロフィール情報は、基本情報と追加情報に分かれるが、結構追加情報が多くて、今までは結構、整理が大変でした。

そこで、追加情報を基本的に、いくつかに分類して、中身は配列情報として保存しておくことにします。そうすれば、追加情報が増えるたびにデータベーステーブルのカラム(フィールド)を増やさなくても済むはず。

ProfileControllerの作成

とりあえずプロフィール作成上のいくつかの問題点を列記しておきます。

問題点

  1. プロフィールの作成で、一番気を遣うのは、会社として把握しておかなければならない基本情報と、各ユーザー間で連絡を取り合うのに必要な情報、この辺の情報をどのユーザーにどの程度公開するのかが問題になります。
  2. ユーザー情報も、社内のユーザー(社内でも役職により相違)、取引先のユーザーによっても公開の範囲が異なります。
  3. 仕事のスムーズな運営と個人情報保護の観点から、十分に配慮する必要あり。
  4. 追加項目は、各ユーザーによって作成できるようにしたが、各ユーザーに一任するのは、情報が煩雑になりすぎる可能性あり。
  5. よって追加項目は、Adminが作成して、各ユーザーはその情報内で入力していく形へ後日変更予定。
  6. 追加項目用のテーブル(items)の作成を検討。
app/controllers/ProfileController.php
<?php
class ProfileController extends BaseController{
/*
|----------------------------------------
| コンストラクター
|----------------------------------------
*/
 public function __construct(){
 //authフィルター
 $this->beforeFilter('auth');
 //全POSTにcsrfフィルターの適用
 $this->beforeFilter('csrf',array('on'=>'post'));
 }

/*
|----------------------------------------
| Topページ
|----------------------------------------
*/
 public function getIndex(){
 //データの取得
 $data['profiles']=Profile::all();
 return View::make('profile/index',$data);
 }
/*
|----------------------------------------
| 新規作成ページ
|----------------------------------------
*/
 public function getCreate(){
 //新規作成ページの表示
 return View::make('profile/create');
 }
 public function postCreate(){
 //POSTデータの取得
 $inputs=Input::except('id');
 //バリデーションルールの作成
 $rules=array(
 'tel'=>'required',
 );
 //バリデーション処理
 $val=Validator::make($inputs,$rules);
 //バリデーションNGなら
 if($val->fails()){
 return Redirect::back()
 ->withInput()
 ->withErrors($val);
 }
 //データの新規作成
 Profile::create($inputs);
 //一覧ページへ移動
 return Redirect::to('profile/index')
 ->with('message','データを作成しました');
 }
/*
|----------------------------------------
| 詳細ページ
|----------------------------------------
*/
 public function getView($id){
 //データの取得
 $data['profile']=Profile::find($id);
 $data['address']=Profile::item('address');
 $data['body']=Profile::item('body');
 $data['license']=Profile::item('license');
 $data['labor']=Profile::item('labor');
 $data['family']=Profile::item('family');
 //プロフィール数が0で無ければ
 if(isset($data['profile'])){
 return View::make('profile/view',$data);
 }
 return Redirect::action('ProfileController@getCreate')
 ->with('message','プロフィールを作成してください');
 }
/*
|----------------------------------------
| 項目作成
|----------------------------------------
*/
 public function getItem($item){
 $data['item']=$item;
 $profiles=Profile::orderBy('created_at','desc')->first();
 return View::make('profile/item',$data);
 }
 public function postItem(){
 //受信データ
 $inputs=Input::only('name','detail');
 //バリデーションルール作成
 $rules=array(
 'name'=>'required',
 'detail'=>'required',
 );
 //バリデーション処理
 $val=Validator::make($inputs,$rules);
 //バリデーションNGなら
 if($val->fails()){
 return Redirect::back()
 ->withInput()
 ->withErrors($val);
 }
 //データの整理
 $item=Input::get('item');
 $name=Input::get('name');
 $detail=Input::get('detail');
 $profile=Profile::where('user_id','=',Auth::user()->id)
 ->orderBy('created_at','desc')
 ->first();
 //項目内が空でなければ
 if($profile->$item != null){
 $data=unserialize($profile->$item);
 $data+=array($name=>$detail);
 $profile->$item=serialize($data);
 //項目内が空なら
 }else{
 $profile->$item=serialize(array($name=>$detail));
 }
 //データの登録
 $profile->save();
 //Profile/viewへ移動
 return Redirect::to('profile/view/'.Auth::user()->id)
 ->with('message','データを作成しました');
 }
}
  • 25-54行目:新規作成に関しては、運用未定。データの変更があった場合、過去データとして残しておくつもりだが、いつの時点で、どのように新規作成するかが、未定。
  • また、User作成時点で、Profileも作成して、profilesテーブルのuser_idだけは、入力しておくこと。後日修正予定。
  • 63-67行目:Profileモデル内に下記の新規メソッドを作成。

Profileモデルにitemメソッドの追加作成

app/models/Profile.php
/*
|--------------------------------------------
| 各項目内の配列を取得する
|--------------------------------------------
*/
 static public function item($item){
 //ログイン中のユーザーの最新情報を取得
 $query=Profile::where('user_id','=',Auth::user()->id)
 ->orderBy('created_at','desc')
 ->first();
 //引数に指定した項目がNULLでなければ
 if($query->$item != null){
 //アンシリアライズして、配列データを返します。
 $data=unserialize($query->$item);
 return $data;
 //項目がNULLなら
 }else{
 return null;
 }
 }
  • itemメソッドを作成。モデル内のメソッドの作成方法がいまいちわからないので、この記述方法が、正しいのかどうかは不明。一応コードは動きます。
  • itemメソッドは、引数と同じ名前のフィールドから、ログイン中のユーザーの個人情報を取得して配列としてリターンしています。
  • ProfileController内で、毎回同じようなコードを作成するのは、面倒なので、作成しました。
  • 指定の情報項目がNULL値なら、NULLをリターンします。

Profile用ビューの作成

app/profile/view.blade.php
@extends('layouts.f4.user.base')
@section('content')
<table width="100%" border="1">
<tr>
<th width="23%" scope="row">ユーザー名</th>
<td width="77%">{{Auth::user()->name}}</td>
</tr>
<tr>
<th scope="row">電話番号</th>
<td>{{$profile->tel}}</td>
</tr>
<tr>
<th scope="row">{{ HTML::link('profile/item/address','住所情報') }}</th>
<td>
@if($address)
@foreach($address as $key=>$value)
{{ $key }} : {{ $value }}<br />
@endforeach
@endif
</td>
</tr>
<tr>
<th scope="row">{{ HTML::link('profile/item/body','身体情報') }}</th>
<td>
@if($body)
@foreach($body as $key=>$value)
{{ $key }} : {{ $value }}<br />
@endforeach
@endif
</td>
</tr>
<tr>
<th scope="row">{{ HTML::link('profile/item/license','資格情報') }}</th>
<td>
@if($license)
@foreach($license as $key=>$value)
{{ $key }} : {{ $value }}<br />
@endforeach
@endif
</td>
</tr>
<tr>
<th scope="row">{{ HTML::link('profile/item/labor','労務情報') }}</th>
<td>
@if($labor)
@foreach($labor as $key=>$value)
{{ $key }} : {{ $value }}<br />
@endforeach
@endif
</td>
</tr>
<tr>
<th scope="row">{{ HTML::link('profile/item/family','家族情報') }}</th>
<td>
@if($family)
@foreach($family as $key=>$value)
{{ $key }} : {{ $value }}<br />
@endforeach
@endif
</td>
</tr>
</table>

@stop
app/views/profile/create.blade.php
@extends('layouts.f4.user.base')
@section('content')
@if(isset($message))
<div data-alert class="alert-box alert radius">
 {{ $message }}
 <a href="#" class="close">&times;</a>
</div>
@endif
 {{ Form::open(array('url'=>'profile/create')) }}
 {{ Form::label('電話番号') }}
 {{ Form::text('tel','') }}
 @if($errors->has('tel'))
 <h5 style="color:red;text-align:center">
 {{ $errors->first('tel') }}
 </h5>
 @endif
 {{ Form::label('住所情報') }}
 {{ Form::text('address','') }}
 {{ Form::label('身体情報') }}
 {{ Form::text('body','') }}
 {{ Form::label('資格情報') }}
 {{ Form::text('license','') }}
 {{ Form::label('労務情報') }}
 {{ Form::text('labor','') }}
 {{ Form::label('家族情報') }}
 {{ Form::text('family','') }}
 {{ Form::label('その他') }}
 {{ Form::text('note','') }}
 {{ Form::hidden('user_id',Auth::user()->id) }}
 {{ Form::submit('送信',array('class'=>'button')) }}
 {{ Form::close() }}
@stop
app/views/profile/item.blade.php
@extends('layouts.f4.user.base')
@section('content')
@if(isset($message))
<div data-alert class="alert-box alert radius">
 {{ $message }}
 <a href="#" class="close">&times;</a>
</div>
@endif
 {{ Form::open(array('url'=>'profile/item')) }}
 {{ Form::label('項目名') }}
 {{ Form::text('name','',array('style'=>'ime-mode:active')) }}
 @if($errors->has('name'))
 <h5 style="color:red;text-align:center">
 {{ $errors->first('name') }}
 </h5>
 @endif
 {{ Form::label('項目内容') }}
 {{ Form::text('detail','',array('style'=>'ime-mode:active')) }}
 @if($errors->has('detail'))
 <h5 style="color:red;text-align:center">
 {{ $errors->first('detail') }}
 </h5>
 @endif
 {{ Form::submit('送信',array('class'=>'button')) }}
 {{ Form::hidden('item',$item) }}
 {{ Form::close() }}
@stop

開発メモ3_AdminController及びAdminフィルター

UserControllerを作成して、ログインできるようになったら、Adminで作成されているユーザーの名称とメールアドレス、パスワード等を変更するために、AdminControllerを作成。

AdminControllerの作成

UserController内をRoleで制御して、Adminで実行できるコマンド、Userが実行できるコマンドと振り分けてもいいのですが、Adminの作業は特殊ですので、別途AdminControllerを作成した方が簡単だと判断。よってAdminControllerを作成。

app/controllers/AdminController.php
<?php
class AdminController extends BaseController{
/*
|----------------------------------------
| コンストラクター
|----------------------------------------
*/
 public function __construct(){
 //adminフィルター
 $this->beforeFilter('admin');
 //全POSTにcsrfフィルターの適用
 $this->beforeFilter('csrf',array('on'=>'post'));
 }
/*
|------------------------------------
| TOPページ
|------------------------------------
*/
 public function getIndex(){
 return View::make('admin/index');
 }
/*
|------------------------------------
| ユーザー一覧ページ
|------------------------------------
| 1. getで全ユーザー表示
| 2. postでユーザー検索
*/
//全ユーザー表示
 public function getUser(){
 $data['users']=User::paginate(10);
 return View::make('admin/user/index',$data);
 }
 //検索ユーザー表示
 public function postUser(){
 $inputs=Input::get('search');
 $data['users']=User::where('name','LIKE','%'.$inputs.'%')->paginate(10);
 return View::make('admin/user/index',$data);
 }
/*
|-----------------------------------
| ユーザー新規作成
|-----------------------------------
| 1.GETでビューの表示
| 2.POSTでユーザー登録
*/
 //GETの処理
 public function getCreate(){
 //ロール名リスト作成
 $data['roles']=Role::orderBy('id','desc')->lists('name','id');
 //グループ名リスト作成
 $data['groups']=Group::lists('name','id');
 return View::make('admin/user/create',$data);
 }
 //POSTの処理
 public function postCreate(){
 //受信データの整理
 $inputs=Input::all();
 //バリデーションの指定
 $rules=array(
 'name'=>'required',
 'email'=>'required|email|unique:users',
 'password'=>'required|min:4',
 );
 //バリデーションチェック
 $val=Validator::make($inputs,$rules);
 //バリデーションNGなら
 if($val->fails()){
 return Redirect::back()
 ->withErrors($val)
 ->withInput();
 }
 //ユーザーの新規作成
 $inputs['onepass']=md5(Input::get('name').time());
 //新規作成
 $user=User::create($inputs);
 //コントローラアクションへパラメーターを渡し、リダレクト
 return Redirect::action('AdminController@getView',array('id'=>$user->id));
 }
/*
|------------------------------------
| ユーザー詳細ページ
|------------------------------------
*/
 public function getView(){
 $id=Input::get('id');
 $data['user']=User::find($id);
 return View::make('admin/user/view',$data);
 }
/*
|------------------------------------
| ユーザー更新ページ
|------------------------------------
*/
 public function getUpdate(){
 $id=Input::get('id');
 //更新ユーザーデータの取得
 $data['user']=User::find($id);
 //ロールリストの作成
 $data['roles']=Role::orderBy('id','desc')->lists('name','id');
 //グループリストの作成
 $data['groups']=Group::lists('name','id');
 return View::make('admin/user/update',$data);
 }
 public function postUpdate(){
 //受信データの整理
 $inputs=Input::all();
 //バリデーションの指定
 $rules=array(
 'name'=>'required',
 'email'=>'required|email',
 );
 //バリデーションチェック
 $val=Validator::make($inputs,$rules);
 //バリデーションNGなら
 if($val->fails()){
 return Redirect::back()
 ->withErrors($val)
 ->withInput();
 }
 //ユーザー情報の更新
 $id=Input::get('id');
 $user=User::find($id);
 $user->name=Input::get('name');
 $user->email=Input::get('email');
 $user->activate=Input::get('activate');
 $user->role_id=Input::get('role_id');
 $user->group_id=Input::get('group_id');
 $user->save();
 $data['users']=$user;
 //コントローラアクションへ名前付きパラメーターを渡し、リダレクト
 //return Redirect::action('AdminController@getUpdate',array('id'=>$id))
 return Redirect::back()
 ->with('warning','データを更新しました');
 }
/*
|------------------------------------
| ユーザー削除ページ
|------------------------------------
*/
 public function getDelete(){
 $id=Input::get('id');
 $data['user']=User::find($id);
 return View::make('admin/user/delete',$data); 
 }
 public function postDelete(){
 $id=Input::get('id');
 User::destroy($id);
 return Redirect::to('admin/user/index'); 
 }
/*
|------------------------------------
| 削除ユーザー一覧
|------------------------------------
*/
 public function getDeleted(){
 $data['users']=User::onlyTrashed()->paginate(10);
 return View::make('admin/user/index',$data)
 ->with('warning','削除ユーザー一覧');
 }
 public function postDeleted(){
 $inputs=Input::get('search');
 $data['users']=User::onlyTrashed()
 ->where('name','LIKE','%'.$inputs.'%')
 ->paginate(10);
 return View::make('admin/user/index',$data)
 ->with('warning','削除ユーザー一覧');
 }
/*
|------------------------------------
| 削除ユーザーの復活
|------------------------------------
*/
 public function getRestore(){
 $id=Input::get('id');
 $user=User::onlyTrashed()->where('id',$id)->restore();
 return Redirect::back()
 ->with('waning','ユーザーを復活させました');
 }
}
  • 134行目:フラッシュデータが引き渡されない。どこのコードを間違えているのだろう。?

AdminControllerをルーターに追加

AdminコントローラーをRESTフルコントローラとしてルーターに追加

Route::controller('admin','AdminController');

Adminフィルターの作成

次にAdmin以外がAdmin専用ページへアクセスできないように、adminフィルターを作成

app/filters.php
Route::filter('admin', function(){
 //ログインしていなければ、ログインページへ
 if(Auth::guest()) return Redirect::guest('user/login');
 //ログインしていてもAdminで無ければ、Userページへ
 if(Auth::user()->role_id<>1){
 return Redirect::intended('user');
 }
});
  • 今回は、Adminフィルターを作成したが、いずれは、Roleフィルターを作成して、Roleに応じてアクセス権限を作成する予定。
  • また、フィルターはRoldeだけでなく、グループでもフィルターを作成する予定(Adminグループ以外は、各グループ内の情報しか取得できないようにする予定、データベースの検索条件の限定)。

Admin用レイアウトテンプレート

Admin用ページを表示するために、Admin用レイアウトのベーステンプレートを作成。

app/views/layouts/f4/admin/base.blade.php
{{--Admin用ベーステンプレート--}}
<!DOCTYPE html>
<!--[if IE 8]> <html class="no-js lt-ie9" lang="ja"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="ja"> <!--<![endif]-->

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Builwing管理室</title>

{{ HTML::style('f4/css/normalize.css') }}
{{ HTML::style('f4/css/foundation.css') }}
{{ HTML::style('f4/css/mystyle.css') }}
{{ HTML::script('f4/js/vendor/custom.modernizr.js') }}

</head>
<body style="background:#FCF">

@section('topbar')
{{ View::make('layouts.f4.admin.topbar') }}
@show

<!-- メインコンテンツとサイドバー -->
<div class="row">
<!-- メインコンテンツ -->
<div class="large-9 push-3 columns" role="content">

@yield('content')

</div><!-- メインコンテンツの終了 -->

<!-- サイドバー -->
<aside class="large-3 pull-9 columns">

@section('sidebar')
{{ View::make('layouts.f4.admin.sidebar') }}
@show
</aside>
<!-- サイドバーの終了 -->
</div><!-- メインコンテンツとサイドバーの終了 -->

@section('footer')
<hr>
{{ View::make('layouts.f4.admin.footer') }}
@show

<script>
document.write('<script src="f4/js/vendor/'
+ ('__proto__' in {} ? 'zepto' : 'jquery')
+ '.js"><\/script>');
</script>
{{ HTML::script('f4/js/foundation.min.js') }}
<script>
$(document).foundation();
</script>

</body>
</html>
  • 48行目:”f4/js/vendor/’の箇所は、状況によってはjavaが起動しないので、”http://ドメイン名/f4/js/vendor/’に変更すれば起動します。
  • このベーステンプレートでトップバー、サイドバー、フッターを変更できるので、ユーザーに応じて変更可能。いずれ対応する予定。そうすれば、Admin用ベーステンプレートとかUser用ベーステンプレートとかを作成する必要は無くなる。

一覧表示用ビューの作成

Adminのユーザー操作用トップページは、ユーザー一覧を表示するように作成。

app/views/admin/user/index.blade.php
@extends('layouts.f4.admin.base')
@section('content')
@if(isset($warning))
<div data-alert class="alert-box alert">
{{ $warning }}
</div>
@else
<div data-alert class="alert-box success">
ユーザー一覧
</div>
@endif
<table width="100%" border="1">
<thead>
 <tr>
 <th scope="col">ユーザー名</th>
 <th scope="col">Eメール</th>
 <th scope="col">処理</th>
 </tr>
</thead>
@foreach($users as $user)
 <tr>
 <td>
@if($user->deleted_at== null)
 {{ HTML::linkAction('AdminController@getView',$user->name,
 array('id'=>$user->id)) }}
@else
 {{ $user->name }}
@endif
 </td>
 <td>{{ $user->email }}</td>
 <td>
@if($user->deleted_at== null)
 {{ HTML::linkAction('AdminController@getUpdate','修正',array('id'=>$user->id)) }}&nbsp;
 {{ HTML::linkAction('AdminController@getDelete','削除',array('id'=>$user->id)) }}
@else
 {{ HTML::linkAction('AdminController@getRestore','復活',array('id'=>$user->id)) }}
@endif 
 </td>
 </tr>
@endforeach
</table>
{{ $users->links() }}
@stop
  • 23-24行目:Laravel3では、ブレードテンプレート内で、改行するとコードを認識できなかったが、{{}}内で、改行してもきちんとコードが動くように改良されているみたいです。使いやすくなりました。
  • 32-37行目:削除ユーザーの表示ページもindex.blade.phpで兼用するために作成。これだとワンクリックで削除ユーザーを復活させることができる。

Update用ビューの作成

ユーザーの基本情報を変更するための、Updateビューファイルを下記のように作成。とりあえずは簡単に変更できるように作成しているが、いずれは、メール認証か何かで修正許可するように変更した方がいいのかどうか思案中。

app/views/admin/update.blade.php
@extends('layouts.f4.admin.base')
@section('content')
@if(isset($warning))
<div data-alert class="alert-box alert">
{{ $warning }}
</div>
@else
<div data-alert class="alert-box success">
ユーザー更新
</div>
@endif
{{ Form::open() }}
<fieldset>
<legend><h4>ID:{{$user->id}}</h4></legend>
<div class="row">
 <div class="small-3 columns">
 {{ Form::label('','氏名',array('class'=>'right')) }}
 </div>
 <div class="small-9 columns">
 {{ Form::text('name',$user->name,array('style'=>'ime-mode:active')) }}</td>
 </div>
</div>
<div class="row">
 <div class="small-3 columns">
 {{ Form::label('','Eメール',array('class'=>'right')) }}
 </div>
 <div class="small-9 columns">
 {{ Form::text('email',$user->email,array('style'=>'ime-mode:inactive')) }}
 </div>
</div>
<div class="row">
 <div class="small-3 columns">
 {{ Form::label('','アクティベート',array('class'=>'right')) }}
 </div>
 <div class="small-9 columns">
 @if($user->activate==1)
 {{ Form::radio('activate','1',true)}} 認証済み
 {{ Form::radio('activate','0') }} 未認証
 @else
 {{ Form::radio('activate','1')}} 認証済み
 {{ Form::radio('activate','0',true) }} 未認証
 @endif
 </div>
</div>
<div class="row">
 <div class="small-3 columns">
 {{ Form::label('','ロール',array('class'=>'right')) }}
 </div>
 <div class="small-9 columns">
 {{ Form::select('role_id',$roles,isset($user->role_id) ? $user->role->id :null) }}
 </div>
</div>
<div class="row">
 <div class="small-3 columns">
 {{ Form::label('','グループ',array('class'=>'right')) }}
 </div>
 <div class="small-9 columns">
 {{ Form::select('group_id',$groups,isset($user->group_id) ? $user->group->id :null) }}
 </div>
</div>
<div class="row">
 <div class="small-3 small-centered columns">
 {{ Form::submit('更新',array('class'=>'button')) }}
 </div>
</div>
</fieldset>
{{ Form::hidden('id',$user->id) }}
{{ Form::close() }}
@stop
  • 20行目:ime-mode*で初期入力モードを日本語に指定。
  • 28行目:同じくime-modeで初期入力モードを英数字に指定。disabledに指定してもいいのかも?
  • 36-42行目:ちょっとコードがださい。何かいい方法はないかな?

Admin用トップバーの作成

{{--Admin用トップバー--}}
<nav class="top-bar">
<!--タイトルエリア-->
<ul class="title-area">
<li class="name">
 <h1>{{ HTML::link('admin','Builwing管理室') }}</h1>
</li>
<!-- smallサイズ表示用 -->
<li class="toggle-topbar menu-icon"><a href="#"><span>menu</span></a></li>
</ul><!--タイトルエリア終了-->
<section class="top-bar-section">
<!--左メニュー開始-->
<ul><!--タイトルエリアの終了-->
 <li class="divider"></li>
 {{ Form::open(array('url'=>'admin/user')) }}
 <li>
 <div class="small-10 large-centered columns">
 <div class="row collapse">
 <div class="small-8 columns">
 {{ Form::text('search','',array('placeholder'=>'ユーザー検索')) }}</div>
 <div class="small-4 columns">
 {{ Form::submit('検索',array('class'=>'button')) }}</div>
 </div>
 </li>
{{ Form::close() }}
</ul><!--左メニュー終了-->
<!--右メニュー開始-->
<ul class="right">
 <li class="divider"></li>
 <li class="has-dropdown">
 <a href="#">ユーザー管理</a>
 <ul class="dropdown">
 <li>{{ HTML::link('admin/user','登録ユーザー') }}</li>
 <li>{{ HTML::link('admin/deleted','削除ユーザー') }}</li>
 <li>{{ HTML::link('admin/create','ユーザー作成') }}</li>
 <li>{{ HTML::link('#','その他') }}</li>
 </ul>
 </li>
 <li class="divider"></li>
 <li class="has-dropdown">
 <a href="#">グループ管理</a>
 <ul class="dropdown">
 <li>{{ HTML::link('#','登録グループ') }}</li>
 <li>{{ HTML::link('#','削除グループ') }}</li>
 <li>{{ HTML::link('#','グループ作成') }}</li>
 <li>{{ HTML::link('#','その他') }}</li>
 </ul>
 </li>
 <li class="divider"></li>
 <li class="has-dropdown">
 <a href="#">ログ管理</a>
 <ul class="dropdown">
 <li>{{ HTML::link('#','一覧') }}</li>
 <li>{{ HTML::link('#','その他') }}</li>
 </ul>
 </li>
</ul><!--右メニュー終了-->
</section>
</nav>

laravel028

サイドバーとフッターのイメージがまだできていないので、本日はここまで。