【レッツ 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>