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