Laravel3、小さなクラスで更新
タグ: Laravel3
この記事は次の記事の内容の続きとして書いています。
- 参照:小さなクラスで読み込み削除;
- 参照:例外と小さなクラス達
また上記の記事は、更に以下の記事の内容を元に書いています。
今回は、更新処理を実装しましょう。これでりんごテーブルのCRUD処理を一通り、小さなクラスと例外を使用して実現することになります。
ルート
routes.phpです。
// りんご更新 Route::get( 'apple/update/(:num)', array ( 'as' => 'update-apple', 'uses' => 'apple@update' ) ); Route::post( 'apple/update/(:num)', array ( 'as' => 'update-apple', 'before' => 'csrf', 'uses' => 'apple@update' ) );
コントローラー
function get_update( $id ) { $inputs = Input::only( array ( 'name', 'color' ) ); if ( empty( $inputs ) ) { try { $apple = $this->appleRepo->read( $id ); } catch ( IdNotFoundException $e ) { return Redirect::error( '404' ); } Input::replace( $apple->to_array() ); } return view( 'apple.create' ); } function post_update( $id ) { try { $this->appleRepo->update( $id, Input::only( array ( 'name', 'color' ) ) ); } catch ( IdNotFoundException $e ) { return Redirect::error( '404' ); } catch ( ValidationFaildException $e ) { return Redirect::back() ->with_input() ->with_errors( $e->validator ); } return Redirect::back(); }
コントローラーの更新部分です。getでフォーム表示、postでフォームの内容を処理しています。
postの更新処理を最初に見てください。追加と削除の処理をくっつけたようなコードです。とても単純です。
IDが見つからない場合と、バリデーションが失敗した場合の例外を拾い、それぞれ適切にリダイレクトしています。拾える例外は一つだけでありません。いくつも捕捉することが可能です。
続いてgetのフォーム表示を見てみましょう。今までコントローラーではif文を使用しなくても済みましたが、今回は必要です。フォームが未入力状態の場合、つまり最初にフォームを表示する場合は、テーブルから指定されたレコードを読み出し、その内容を表示しなくてはなりません。その処理を入れるため、読み込みの処理が入っています。
Laravel的な技芸コードとして読み込んだEloquentモデルをto_arrayメソッドで配列に変換し、それをInput::replaceメソッドで入力された値としてLaravelに扱わせます。
ビューは新規作成の時のビューを共用します。今回のために変更を加えました。create.bldade.phpです。
{{ Form::open() }} {{ Form::label('name', 'りんご名') }} {{ Form::text('name', Input::old('name', Input::get('name', ''))) }} @if ( $errors->has('name') ) {{ $errors->first('name') }} @endif <br> {{ Form::label('color', 'りんご色') }} {{ Form::text('color', Input::old('color', Input::get('color', ''))) }} @if ( $errors->has('color') ) {{ $errors->first('color') }} @endif <br> {{ Form::submit('送信') }} {{ Form::token() }} {{ Form::close() }}
nameとcolorのフィールドに表示する値を2段階で扱うようにします。Input::oldでセッションの中に保存されているかチェックし、保存されている場合はその値を表示します。これはpostからエラー時にリダイレクトされた場合に対応するためです。
セッションの中に保存されていない場合、Input::getでフォームの入力値を調べます。これはInput::replaceで保存した内容を表示させるためです。
Input::replaceを使用せず、セッションに直接保存すればビューは以前のままInput::oldだけチェックすれば済みます。しかし、セッションはファイルやデータベースの場合であるかも知れず、その場合オーバーヘッドが大きくなります。しかも、配列でまとめてセッションに保存する方法がSessionクラス、Inputクラスには無く、一つ一つフィールドを指定しながら記述しなくてはなりません。そのため、配列をまとめて置き換えられ、メモリ操作だけで済む、Input::replaceを使用しました。
リポジトリー
これはほとんど新規の場合のコードと同じになります。リポクラスの全コードを紹介します。
<?php namespace ProjectH\Repositories; use IoC; use ProjectH\Exceptions\ExecutionErrorExceptions\IdNotFoundException; use ProjectH\Exceptions\ProgramErrorExceptions\ValidatorClassNotFoundException; class AppleRepo { private $apple; public function __construct() { $this->apple = IoC::resolve( 'Apple' ); } public function create( $data ) { if ( IoC::registered( 'AppleValidator' ) ) { $appleValidator = IoC::resolve( 'AppleValidator' ); $appleValidator->validate_create( $data ); } else { throw new ValidatorClassNotFoundException( 'Appleモデルのバリデタークラスが存在していません。' ); } $apple = $this->apple->create( $data ); $apple->save(); } public function read( $id ) { $apple = $this->find( $id ); return $apple; } public function update( $id, $data ) { $apple = $this->find( $id ); if ( IoC::registered( 'AppleValidator' ) ) { $appleValidator = IoC::resolve( 'AppleValidator' ); $appleValidator->validate_update( $id, $data ); } else { throw new ValidatorClassNotFoundException( 'Appleモデルのバリデタークラスが存在していません。' ); } $apple->fill($data); $apple->save(); } public function delete( $id ) { $apple = $this->find( $id ); $apple->delete(); } private function find( $id ) { $apple = $this->apple->find( $id ); if ( $apple === null ) { throw new IdNotFoundException( 'IDが見つかりません。' ); } return $apple; } }
読み込みと更新処理で両方共findメソッドを使用するため、関数として切り出しています。
おかけで更新処理はとてもすっきりです。まずレコードを読み込み、バリデーションし、最後にレコードを更新します。
バリデーションクラス
本日のメインイベントです。バリデーションクラスです。
<?php namespace ProjectH\Services\Validators; use ProjectH\Exceptions\ExecutionErrorExceptions\ValidationFaildException; class AppleValidator { public $rules = array ( 'name' => 'required|max:10|unique:apples', 'color' => 'required|max:10', ); function validate_create( $data ) { $this->excute_validation( $data, $this->rules ); } function validate_update( $id, $data ) { $rules = $this->add_id_with_unique( $id, $this->rules ); $this->excute_validation( $data, $rules ); } private function excute_validation( $data, $rules ) { $val = \Validator::make( $data, $rules ); if ( $val->fails() ) { throw new ValidationFaildException( $val, 'Appleクラスのバリデーションに失敗しました。' ); } } protected function add_id_with_unique( $id, $rules ) { foreach ( $rules as $item => $rule ) { if ( preg_match( '/unique:(\w+)(,(\w+))?/', $rule ) > 0 ) { $patterns = explode( '|', $rule ); $new = array ( ); foreach ( $patterns as $pattern ) { if ( preg_match( '/unique:(\w+)(,(\w+))?/', $pattern, $matches ) > 0 ) { $unique = 'unique:'.$matches[1]; if ( isset( $matches[3] ) ) { $unique .= ','.$matches[3].','.$id; } else { $unique .= ','.$item.','.$id; } $new[] = $unique; } else { $new[] = $pattern; } } $rules[$item] = implode( '|', $new ); } } return $rules; } }
以上に複雑に思えるでしょうが、add_id_with_unique関数が長いだけです。これをまず無視して見てください。とても単純ですよ。
新規の場合は指定されたバリデーションルールをそのまま使用しています。更新の場合はadd_id_with_unique関数から返された配列の値で、バリデーションを行います。
まあ実はadd_id_with_uniqueは他のプロジェクトで予め作っておいたものをコピペしたのです。バリデーションのuniqueルールは特定のレコードを無視することができます。これは更新処理のための仕様です。例えばりんご名「フジ」を変更せずそのまま「フジ」にする場合、uniqueルールをつけている場合既に存在しているため、バリデーションが通りません。かと言ってuniqueを外してしまえば、他のりんごと同じ名前を付けることができてしまいます。そのため、uniqueルールで更新処理を行う場合、無視するレコードIDを指定することができるようになっています。
この複雑なadd_id_with_unique関数はルール定義の配列の中を探しuniqueルールが存在していれば、IDをセットする仕組みになっています。
今回のチュートリアルシリーズは、目的をはっきりさせていませんが、こうしたおまけが隠されているシリーズです。;P