【レッツ Laravel4!】 2013.5.11 by ToR http://php.s3.to/ Laravel4は洗練されたPHPフレームワークです。 http://laravel.com/ Laravel4マニュアル http://four.laravel.com/ ■ComposerとLaravel4のインストール Lravel4のインストールにはComposerが必要となります。 ComposerとはPHP関連のパッケージ管理システムです。 ・・Linuxの場合 Composerのインストール #curl -s http://getcomposer.org/installer | php コマンド化 #mv composer.phar /usr/local/bin/composer Laravel4のインストール #composer create-project laravel/laravel l4(プロジェクト名) --prefer-dist ・Windowsの場合 ComposerをWindowsインストーラーでインストール https://getcomposer.org/Composer-Setup.exe Laravelをダウンロードして解凍 https://github.com/laravel/laravel/archive/develop.zip コマンド実行窓を立ち上げて以下を実行 (コマンド実行窓(cmd.exe)は、Win7なら「スタート」→検索窓に「cmd」) >cd c: mmp\htdocs\l4(解凍した(composer.jsonのある)場所へ移動) >composer install 依存関係のあるパッケージがダウンロードされます。 この解説ではWindows上のXAMMPを使用し、Laravel4を「l4」ディレクトリに展開することにします。 ■設定 ・全般の設定 app/config/app.php 'url' => 'http://localhost/l4/public', 'timezone' => 'Asia/Tokyo', 'locale' => 'ja', 'key' => '適当なキーを設定', ・データベースの設定 app/config/database.php データベースはSQLite、MySQL、PostgreSQL,SQLSeverをサポートしています。 'default' => 'mysql', 'mysql' => array( 'driver' => 'mysql', 'host' => 'localhost', 'database' => 'DB名', 'username' => 'DBユーザー名', 'password' => 'DBパスワード名', 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', ), ■動作確認 http://localhost/l4/public/ Hello World!と表示されればインストール成功です! ■テーブルの作成と初期データ ・・テーブル作成 ・マイグレーション マイグレーションとは、データベースの管理機能のことで、 テーブルの作成や初期データなどを自動追加・削除することができます。 コマンド実行窓を立ち上げて、Laravel4ディレクトリに移動します ・テーブル用マイグレート作成(テーブル名はmembersとする) >php artisan migrate:make foo --create=members この時点ではまだテーブルは作成されない。app/database/migrations/内にPHPファイルが作成される(日付+foo) up()の部分に、テーブル構造を記載する。書き方はスキーマビルダーhttp://four.laravel.com/docs/schemaを参照 作成例) Schema::create('members', function(Blueprint $table) { $table->increments('id'); $table->string('name',10); $table->string('email',200); $table->timestamps(); }); ・実行 >php artisan migrate テーブルが作成されました ・・初期データ追加 ・app/databese/seeds/内に次の内容を「MemberTableSeeder.php」として作成 class MemberTableSeeder extends Seeder { public function run() { $posts = [ [ 'name' => 'あああ いいい', 'email' => 'aaa@aaa.com'], [ 'name' => 'ううう ええ', 'email' => 'bbb@ccc.net'] ]; DB::table('members')->insert($posts); } } ・app/database/seeds/DataBaseSeedr.phpに以下を追加 $this->call('MemberTableSeeder'); ・実行 >php artisan db:seed 初期データが追加されました。 ■基本動作コントローラの自動生成 コマンド実行窓から以下を実行 >php artisan controller:make MemberController app/controllers/内にファイルMemberContoroller.phpが自動作成されます。 ・ルートファイルへの追加 ./routes.phpに以下を追加 Route::resource('member', 'MemberController'); URLの対応表はこちら http://four.laravel.com/docs/controllers#resource-controllers 例えば http://localhost/l4/public/member/2 なら、コントローラのfunction show($id){の部分が実行されます。 ■モデルの作成 app/models/内に以下の内容で、Member.phpを作成。たったの一行! <?php class Member extends Eloquent {} Eloquent ORMを使用しています。 ■ビューの作成 app/views/内に以下の内容で、index.blade.phpを作成 <table border=1 class="table table-striped"> <tbody> @foreach($items as $m) <tr> <td>{{ $m->id }}</td> <td>{{ $m->name }}</td> <td>{{ $m->email }}</td> <td>{{ $m->created_at }}</td> </tr> @endforeach </tbody> ■データ呼び出し ・コントローラー(app/controllers/MemberController.php)に記述 public function index() { return View::make('index', ['items'=>Member::get()]); } ・確認 http://localhost/l4/public/member データの呼び出し方は、クエリービルダーhttp://four.laravel.com/docs/queriesに準拠しています。 例)Member::where('id', '>', 10)->order_by('name','desc')->get(); -------------2013.5.13 ■テンプレート機能 ・拡張子をblade.phpにすることで、Bladeテンプレートが使える ・コントローラでの呼び出しかたは、View::make('test');で、app/views/test.blade.phpが読み込まれる ・・レイアウト機能 ・基本 まずはベースのHTMLを作成。app/views/layoutsフォルダを作成して、以下の内容でapp/views/layouts/master.phpとして保存 <html> <body> <div class="container"> @yield('content') </div> </body> </html> 差し込むコンテンツを作成。app/views/test.blade.phpとして以下の内容で保存 @extends('layouts.master') @section('content') <p>コンテンツ1</p> <p>コンテンツ2</p> @stop 読み込むほうには、yield('名前'); 差し込む方には、先頭にextends('ベースのファイル名'); セクションの区切りは@section~@stop ・親HTML内の文字を使用する場合 ベースのHTML <html> <body> @section('sidebar') ここは共通メニュー @show </body> </html> 子のHTML @extends('layouts.master') @section('sidebar') 子ヘッダー<br> @parent <p>子メニュー</p> @stop ・・その他変数などの書き方 ・変数 {{ $name }} ・if文 @if($a >= 2) 2より大きい @endif ・foreach文 @foreach($users as $user) <p>ユーザー名は {{ $user->id }}</p> @endforeach ■フォームの作成 追加画面を作成してみます。コントローラ(app/controller/MmemberController.php)の public function create()の部分になります。 ・まずはベースのHTMLを作成します。 <!DOCTYPE html> <html> <head> <title>{{ $title }}</title> </head> <body> @yield('content') </body> </html> これをdefault.blade.phpとしてapp/views/layouts/に保存 ・追加画面のフォームHTML作成。 @extends('layouts.default') @section('content') <h2>新規会員追加</h2> {{ Form::open(['route' => 'member.store']) }} {{ Form::token() }} <p> {{ Form::label('name', '名前:') }}<br /> {{ Form::text('name', Input::old('name')) }} </p> <p> {{ Form::label('email', 'E-Mail:') }}<br /> {{ Form::text('email', Input::old('email'), ['size'=>'40']) }} </p> <p>{{ Form::submit('追加する') }}</p> {{ Form::close() }} @stop これをcreate.blade.phpとしてapp/views/に保存 Form::textは、1つ目がname=""の部分、2つ目がデフォルト値、3つ目がオプションを配列で記述する ・コントローラを修正 public function create() { return View::make('create')->with('title','会員追加'); } タイトルが機能ごとに変わるようにしています。 ・動作確認 http://localhost/l4/public/member/create ■Twitter Bootstrapの導入 ・Twitter Bootstrapはデザインテンプレートエンジンです。 http://twitter.github.io/bootstrap/ ダウンロードしたjs,css,imgフォルダを、Laravelの「public」に配置 ・テンプレートの修正。(app/views/layouts/default.blade.php) <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{{ $title }}</title> {{ HTML::style('css/bootstrap.min.css') }} </head> <body> <div class="navbar"> <div class="navbar-inner"> <a class="brand" href="#">メンバー管理</a> <ul class="nav"> <li>{{ HTML::linkRoute('member.index', '一覧') }}</li> <li>{{ HTML::linkRoute('member.create', '追加') }}</li> </ul> </div> </div> <div class="container"> @yield('content') </div> {{ HTML::script('http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js') }} {{ HTML::script('js/bootstrap.min.js') }} </body> </html> HTML::のメソッド名は、vender/laravel/framework/src/Illuminate/Html/HtmlBuilder.phpを参照 ・一覧表示画面の作成(app/views/index.blade.php) @extends('layouts.default') @section('content') <table class="table table-striped"> <thead> <tr><th>ID</th><th>名前</th><th>作成日</th> <tbody> @foreach($items as $m) <tr> <td>{{ $m->id }}</td> <td>{{ HTML::linkRoute('member.show', $m->name, [$m->id]) }}</td> <td>{{ $m->created_at }}</td> </tr> @endforeach </tbody> </table> @stop 名前データから個別表示にリンクを張るようにしてみます。 ・コントローラーの一覧表示修正(app/contoroller/MemberController.php) public function index() { return View::make('index') ->with('items',Member::orderby('id','desc')->get()) ->with('title','会員一覧'); } IDの大きい順に表示して、タイトル追加。とりあえずは全データを表示しています。 ・個別表示画面の作成(app/views/show.blade.php) @extends('layouts.default') @section('content') <p>{{ $member->name }}</p> <p>{{ $member->email }}</p> <p><small>{{ $member->updated_at }}</small></p> {{ HTML::linkRoute('member.index', '戻る') }} @stop ・コントローラー個別表示修正(app/contoroller/MemberController.php) public function show($id) { return View::make('show') ->with('member',Member::find($id)) ->with('title','会員詳細'); } ■バリデーション&保存 バリデーションとはフォームから来たものが条件を満たすかどうかチェックするものです。 ・コントローラーのstore()部分を修正 public function store() { //ルールの作成 $rules = ['name' => 'required|min:2', 'email' => 'required|min:10']; //フォームから来るデータ $input = Input::all(); //データとルールを渡す $validation = Validator::make($input, $rules); //OKの場合の処理 if ($validation->passes()) { // OKの場合はDBにデータ追加して一覧に戻る Member::create($input); return Redirect::route('member.index'); } //NGの場合は戻ってエラー表示 return Redirect::back()->withInput()->withErrors($validation); } ・テーブルに挿入可能な項目を定義 (app/models/Member.phpを修正します) class Member extends Eloquent { protected $fillable = ['name', 'email']; } ・エラーを表示 (app/views/create.blade.phpに以下を挿入します) @if ($errors->any()) <ul> {{ $errors->first('name', '<li>:message</li>') }} {{ $errors->first('email', '<li>:message</li>') }} </ul> @endif 日本語化や項目名の表示についてはまた今度 ・追加確認 http://localhost/l4/public/member/create -------------------------2013.5.16 ■バリデーションルールの共通化? ・バリデーションサービス作成 appフォルダに「services」フォルダを作成します。 app/servicesフォルダに「validator」フォルダを作成します。 TOPフォルダにあるcomposer.jsonにオートロードを追加します。 "autoload": [ ・・・・ "app/tests/TestCase.php", "app/services" //←追加 ] ・バリデーション抽象化クラスの作成 app/services/validatorに、Validator.phpを作ります。 <?php namespace Services\Validators; abstract class Validator { protected $attributes; public $errors; function __construct($attributes = null) { $this->attributes = $attributes ?: \Input::all(); } public function passes() { $validation = \Validator::make($this->attributes, static::$rules); if ($validation->passes()) return true; $this->errors = $validation->messages(); return false; } } ・バリデーションルールクラスの作成 app/services/validatorに、Member.phpを作ります。 <?php namespace Services\Validators; class Member extends Validator { public static $rules = [ 'name' => 'required|min:2', 'email' => 'required|min:10' ]; } ・オートロードの再読込 コマンドラインから以下を実行します。 >composer dumpautoload (もしくは#php composer.phar dumpautoloadかな) serviceフォルダ以下を修正した場合は、適宜実行 ・コントローラーの修正 チェックの部分 $input = Input::all(); $rules = ['name' => 'required|min:2', 'email' => 'required|min:10']; $validation = Validator::make($input, $rules); ↓ $validation = new Services\Validator\Member; エラーを渡す部分 return Redirect::back()->withInput()->withErrors($validation); ↓ return Redirect::back()->withInput()->withErrors($validation->errors); 書き直すとこんな感じです public function store() { $validation = new Services\Validators\Member; if ($validation->passes()) { Member::create(Input::all()); return Redirect::route('member.index'); } return Redirect::back()->withInput()->withErrors($validation->errors); } すっきりしました。 ■個別画面に編集と削除へのリンク追加 app/views/show.blade.phpの下の方に以下を追加します。 {{ HTML::linkRoute('member.edit', '編集', [$member->id]) }} | {{ Form::open(['route'=>['member.destroy', $member->id], 'method'=>'delete', 'style'=>'display:inline;']) }} {{ Form::submit('削除') }} {{ Form::close() }} hiddenでIDを渡すのではなくForm::open(['route'=>['member.destroy', $member->id], 'method'=>'delete'という書き方をするみたいです。 ■編集画面の作成 ・ビューの作成。app/viewsにedit.blade.phpを作ります @extends('layouts.default') @section('content') <h2>会員編集</h2> @if ($errors->any()) <ul> {{ $errors->first('name', '<li>:message</li>') }} {{ $errors->first('email', '<li>:message</li>') }} </ul> @endif {{ Form::open(['route' => ['member.update', $member->id], 'method' => 'put']) }} <p> {{ Form::label('name', '名前:') }}<br /> {{ Form::text('name', $member->name) }} </p> <p> {{ Form::label('email', 'E-Mail:') }}<br /> {{ Form::text('email', $member->email, ['size'=>'40']) }} </p> <p>{{ Form::submit('更新する') }}</p> {{ Form::close() }} @stop ・コントローラーの修正 public function edit($id) { return View::make('edit') ->with('member',Member::find($id)) ->with('title','会員編集'); } ■更新処理の作成 app/controllers/MemberController.phpを修正します。 バリデーションをクラス化?したので非常にすっきり。モデルもシンプル public function update($id) { $validation = new Services\Validators\Member; // OKの場合はDB更新して一覧にリダイレクト if ($validation->passes()) { Member::find($id)->update(Input::all()); return Redirect::route('member.index'); } // NGの場合は戻ってエラー表示 return Redirect::back()->withInput()->withErrors($validation->errors); } ■削除処理の作成 app/controllers/MemberController.phpを修正します。 public function destroy($id) { Member::find($id)->delete(); return Redirect::route('member.index'); } コントローラのソース http://php.s3.to/tt/MemberController.phps リソースフルコントローラによるCRUDはとりあえずここまで ------------ ■ページネーション ・コントローラの修正 get()をpaginate(1ページ表示数)にするだけ ->with('items',Member::orderby('id','desc')->get()) ↓ ->with('items',Member::orderby('id','desc')->paginate(5)) ・ビューの修正 一覧ページ下部に以下を追加するだけ。 {{ $items->links() }} めちゃ簡単 ■ルートファイルの活用 ルートファイルとはapp/routes.phpのことで、「このURLにはこの処理を行う」という役割があります。 また、GETやPOSTの場合を区別したり、前処理を噛ませたりすることも出来ます。 では、とりあえず一覧表示と個別表示を処理するルートを書いてみます。 まずはURLを決めます。 http://~~~/member → 一覧表示 http://~~~/member/123 → ID=123のデータ表示 書き方ですがおおむね2通りの方法があります。 1.ルートファイル内で完結する Route::get('member', function() { return View::make('index') ->with('items',Member::orderby('id','desc')->paginate(5)) ->with('title','会員一覧'); }); Route::get('member/{id}', function($id) { return View::make('show') ->with('member',Member::find($id)) ->with('title','会員詳細'); }); POSTの場合は、Route::post(~で始まります。 この方法ではコントローラーファイル(app/controllers/???)が不要になりますが、 機能が多い場合は冗長になり見づらくなりそうです。 2.コントローラーで処理させる Route::get('member', ['as' => 'index', 'uses' => 'MemberController@index']); Route::get('member/{id}', ['as' => 'show', 'uses' => 'MemberController@show']); 'as'=>には、ルート名(この処理の名前)をつけます。 ビュー内でHTML::linkRoute('index','戻る')と使ったり、 リダイレクト時にRedirect::route('index');と使ったりします。 'uses'=>には、使用するアクションを指定します。 'MemberController@index'だったら、app/controllers/MemberController.phpの function index()が処理されるという意味になります。 ・引数の文字種制限 例えばURLのIDは数字のみに限定したい場合は以下のように書きます。 Route::get('member/{id}', ['as' => 'show', 'uses' => 'MemberController@show'])->where('id', '[0-9]+'); いままでのリソースフルコントローラを書き換えると以下のようになります。 //Route::resource('member', 'MemberController'); Route::get('member', ['as' => 'member.index', 'uses' => 'MemberController@index']); Route::get('member/create', ['as' => 'member.create', 'uses' => 'MemberController@create']); Route::post('member', ['as' => 'member.store', 'uses' => 'MemberController@store']); Route::get('member/{id}', ['as' => 'member.show', 'uses' => 'MemberController@show'])->where('id', '[0-9]+'); Route::get('member/{id}/edit', ['as' => 'member.edit', 'uses' => 'MemberController@edit'])->where('id', '[0-9]+'); Route::put('member/{id}', ['as' => 'member.update','uses' => 'MemberController@update'])->where('id', '[0-9]+'); Route::delete('member/{id}', ['as' => 'member.destroy', 'uses' => 'MemberController@destroy'])->where('id', '[0-9]+'); ■CSRF対策 CSRFとは外部からの意図しない投稿攻撃のことです。 フォームにトークンを埋め込むことによって防ぐことができます。 ・ビュー側の修正 {{ Form::token() }} トークンがhiddenで埋め込まれます。 ・ルートの修正 Route::post('member', ['before' => 'csrf', 'as' => 'member.store', 'uses' => 'MemberController@store']); 'before' => 'csrf'で前処理にトークンのチェックが行われます。 ■ログインページ 認証(Auth)については用意されてるものを使用します。 設定はapp/config/auth.php。 E-mailとパスワードで認証することにします。 ・ユーザーテーブル作成(テーブル名users) >php artisan migrate:make create_users_table --table=users --create app/database/migrateに日付+???.phpが作成されるので修正 public function up() { Schema::create('users', function(Blueprint $table) { $table->increments('id'); $table->string('email')->unique(); $table->string('password'); $table->timestamps(); }); } ・実行 >php artisan migrate テーブルが作成されました ・初期データの追加 新規ファイルをapp/database/seeds/UserTableSeeder.phpとして作成 <?php class UserTableSeeder extends Seeder { public function run() { DB::table('users')->delete(); User::create([ 'email' => 'aaa@email.com', 'password' => Hash::make('pass') ]); } } } app/databese/seeds/DatabaseSeeder.phpを修正 public function run() { Eloquent::unguard(); $this->call('UserTableSeeder'); } ・実行 >php artisan db:seed 初期データが追加されました。 ・コントローラの修正 今回はサンプルとしてmyapgeに行くときに認証を通る設定にしてみます。 app/routes.phpに追加 Route::get('mypage', array('before' => 'auth', function() { return '<h2>Hello!!! '.Auth::user()->email.'!</h2>'; })); 'before' => 'auth'が前処理にauthフィルターを通すという意味です。 実際のフィルター等はapp/filters.php内に記述してあります。 Route::filter('auth', function() { if (Auth::guest()) return Redirect::to('login'); }); 認証が通ってない場合は「login」にリダイレクトされるようになっています。 login時の処理をコントローラに書きます。 // GET時の処理 Route::get('login', function() { return View::make('login');//ログインフォーム表示 }); // POST時の処理 Route::post('login', function() { // バリデーション等はここに書くといいかも Auth::attempt( ['email' => Input::get('email'), 'password' => Input::get('password')] ); return Redirect::to('mypage'); }); ・ログインフォームの作成 新規ファイルapp/views/login.blade.phpを作成します。 <html> <body> <h2>ログイン</h2> {{ Form::open(['url' => 'login']) }} <p> {{ Form::label('name', 'E-mail:') }}<br /> {{ Form::text('email', Input::old('email')) }} </p> <p> {{ Form::label('password', 'Password:') }}<br /> {{ Form::password('password') }} </p> <p>{{ Form::submit('Login') }}</p> {{ Form::close() }} </body> </html>