Laravel5 を試したメモ

他のフレームワークを利用している人が、Webアプリを作成するにあたって抑えておくべきポイントだけピックアップしてみた

インストール

ローカル開発環境の準備

Laravel5は、PHPやNginx、MySQLなど必要なパッケージをインストールずみのVagrantローカル開発環境 Homestead 提供している

Homesteadのセットアップ手順については、Laravel 5 ローカル開発環境 homestead 構築手順にまとめている

Laravel プロジェクトの作成

すでにPHP実行環境がある場合は、Composerでプロジェクトを生成して開発を始める

Laravelインストール手順は、DocumentationのInstallationを参照

$ composer create-project laravel/laravel --prefer-dist <project name>

Laravel 5のディレクトリ構成は以下の通り

$ tree -L 2
.
├── app
│   ├── Commands
│   ├── Console
│   ├── Events
│   ├── Exceptions
│   ├── Handlers
│   ├── Http
│   ├── Providers
│   └── User.php
├── artisan
├── bootstrap
│   ├── app.php
│   └── autoload.php
├── composer.json
├── composer.lock
├── config
│   ├── app.php
│   ...
│   ├── database.php
│   ...
│   ├── session.php
│   └── view.php
├── database
│   ├── migrations
│   └── seeds
├── gulpfile.js
├── package.json
├── phpspec.yml
├── phpunit.xml
├── public
│   ├── favicon.ico
│   ├── index.php
│   └── robots.txt
├── readme.md
├── resources
│   ├── assets
│   ├── lang
│   └── views
├── server.php
├── storage
│   ├── app
│   ├── framework
│   └── logs
├── tests
│   ├── ExampleTest.php
│   └── TestCase.php
└── vendor
    ├── autoload.php
    ├── bin
    ...
    ├── symfony
    └── vlucas

48 directories, 31 files

Laravelは、ApacheやNginxなしでも組み込みの開発用サーバーで動作確認できる
artisanコマンドで開発用サーバーを起動

$ php artisan serve
Laravel development server started on http://localhost:8000/

artisanコマンドは、RailsのRake的なコマンド
使い方は、Documentation Artisan CLI Usage を参照
または、artisan listで使用可能なコマンド一覧を参照できる

$ php artisan list

ルーティング

ルーティングは app/Http/routes.php に記述する

<?php
Route::get('/', 'WelcomeController@index');

上記は、プロジェクトの作成後のデフォルト、’/’をWelcomeControllerのindexメソッドにマッピングしている

以下のように、無名関数を指定することもできる

Route::get('/', function()
{
    return 'Hello World';
});

また、コントローラー全体をパスにマッピングすることも可能
以下のようにRoute::controllerメソッドを使用する
Implicit Controllers – HTTP Controllers

<?php
Route::controller('/', 'WelcomeController');

暗黙的なマッピングを利用する場合、コントローラーのアクションメソッドの名前の先頭にHTTPメソッド名をつける必要がある
GETメソッドであれば、get<アクション名>のような形式で記述する
上記の設定で、WelcomeController#indexをgetIndexに変更すると初期表示の画面を確認できる

class WelcomeController extends Controller {
    public function getIndex()
    {
        return view('welcome');
    }
}

ミドルウェア (Http フィルター)

Laravelは、Httpリクエストをフィルタする機能としてHttp ミドルウェアという仕組みを用意している
(4.xまではフィルターだった)

デフォルトでは、CSRFや認証などのミドルウェアが用意されている

ミドルウェアの設定は、コントローラではなく、ルーティングに記述する

CSRF対策

Laravelは、ミドルウェアを明示的に設定しなくても、デフォルトでCSRFプロテクション機能が有効になっている

そのため、POST、PUTおよびDELTEメソッド アクションを含むビューでは、CSRFトークンを含める必要がある

Bladeテンプレートの場合

<?php echo csrf_field(); ?>

Bladeテンプレートの場合

{!! csrf_field() !!}

コントローラー

コントローラーは、app/Http/Controllersに、App\Http\Controllers\Controllerを継承するクラスを置く

artisanコマンドでもコントローラーを生成可能

$ php artisan make:controller ArticleController

ところでこのmake:controllerコマンドは、アクション名などを引数に取る機能は無いのだが、生成されるコントローラーはRestfullなリソースアクセスを提供する
RESTful Resource Controllers

以下生成されたコード(コメントは割愛)ようにRESTリソースのCRUDのアクションを含むコントローラーが生成されている

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

class ArticleController extends Controller
{
    public function index()
    {
        //
    }

    public function create()
    {
        //
    }

    public function store()
    {
        //
    }

    public function show($id)
    {
        //
    }

    public function edit($id)
    {
        //
    }

    public function update($id)
    {
        //
    }

    public function destroy($id)
    {
        //
    }
}

ルーティングは追加されないので、app/Http/routes.phpに手動で追加する必要がある
RSETFullリソース用のルーティングのためのメソッドRoute::resourceが用意されているので利用する

Route::resource('article', 'ArticleController');

リクエストパラメータは、Requestファサードで取得
Requestはuseでインポートする必要あり

<?php namespace App\Http\Controllers;
use Request;
class UserController extends Controller {
    public function postLogin()
    {
        $username = Request::input('username');
        $password = Request::input('password');
        // Login process..
    }
}

ビュー

基本的なビューの呼び出し方

概要は、The BasicsのViews を参照

ビューのファイルは、resources/viewsに置く

ビューへ渡すデータは、view関数の第二引数に渡す

    return view('greeting', ['name' => 'James']);

ビュー側は以下のように、渡されたマップのキーで値を参照できる

<html>
    <body>
        <h1>Hello, <?php echo $name; ?></h1>
    </body>
</html>

resources/viewsのサブディレクトリに配置したビューの呼び出しは、以下のようにドットで連結して呼び出す(‘/’でも呼び出せる)

    return view('article.create');

Bladeテンプレート

Laravelで、共通のヘッダーやサイドバーなどレイアウトを定義する場合、組み込みのテンプレートエンジン Bladeを使う

Bladeの場合、RailsやFuelphpなどの他のフレームワークのように、コントローラーでレイアウトテンプレートをしていするのではなく、子テンプレート側でレイアウトが定義されている親テンプレードを@extendsアノテーションに指定する形で使う

また、ビューのファイルがbladeテンプレートである場合、.blade.phpのように接頭辞に.bladeをつける

以下、Documentations Blade Templatesの例

<html>
    <head>
        <title>App Name - @yield('title')</title>
    </head>
    <body>
        @section('sidebar')
            This is the master sidebar.
        @show

        <div class="container">
            @yield('content')
        </div>
    </body>
</html>

このレイアウトを使うビュー

@extends('layouts.master')

@section('title', 'Page Title')

@section('sidebar')
    @parent

    <p>This is appended to the master sidebar.</p>
@stop

@section('content')
    <p>This is my body content.</p>
@stop

JsやCSSなどの読み込みについても柔軟に子供のビューから操作でき便利

補足
4.2のドキュメントのViews Passing A Sub-View To A View では、.phpのテンプレートで子ビューをセットするような形でレイアウトを組む説明もあるが、このようにレイアウトを記述することはなさそう

Asset

Laravel5 では、CSS、JavascriptをビルドするGulp タスク Laravel Elixir が標準で提供されている
この辺りが標準で提供されているあたりは素晴らしい

プロジェクト直下のgulpfile.jsを見てみると、resources/assets/less/app.lessをビルドするタスクがはじめから設定されている

var elixir = require('laravel-elixir');

elixir(function(mix) {
    mix.less('app.less');
});

npm実行環境とgulpをインストールして、プロジェクト直下にて以下のコマンドを実行する

$ npm install

開発中はgulp watchしておけば、lessの修正が自動で反映される

$ gulp watch

データベース操作

Basic Database Usage の Configuration によると対応データベースは、
MySQL, Postgres, SQLite, SQL Server と豊富

接続設定

データベースへの接続設定は、config/database.phpに記述する

Laravelのルートディレクトリ.envがあり、環境ごとの設定はこちらに記述
Environmentの設定については、LaravelはDotEnv PHPを利用する

MySQLを使用し Read/Write接続を分けない場合は、config/database.phpではなく.envに接続情報を記述する

デフォルトは以下の.env.exampleがインストール時に.envにコピーされる
(homesteadは、MySQLが.env.exampleの内容に合わせてセットアップ済み)

APP_ENV=local
APP_DEBUG=true
APP_KEY=SomeRandomString

DB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync

MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

参考: Laravel 5.0 – Environment Detection & Environment Variables

モデル (ORM)

Laravelには、Eloquent ORMというActiveRecord実装がある

モデル生成の artisan コマンドが用意されている

$ php artisan make:model Article

app直下に以下のBlog.phpが生成される

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    //
}

$tableプロパティに明示的にテーブル名を指定していないので、このArticleクラスの場合は、articlesテーブルにアクセスする

マイグレーション

上記のArticleモデルを保存するarticlesテーブルを追加するマイグレーションを作成する場合、以下のようにartisan mke:migrationコマンドを実行する

$ php artisan make:migration create_articles_table
Created Migration: 2015_06_09_164737_create_articles_table

マイグレーションファイルはdatabase/migrationsに作成される
(モデル作成時、artisan make:modelコマンドにオプション-mを付けると、モデルと同時にマイグレーションも生成される)

マイグレーションの書き方はWriting Migrationを参照

オートインクリメントのレコードIDと、文字列のtitleとtextをフィールドに持つarticlesテーブルを作成するマイグレーションは以下のようになる

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateArticlesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('articles', function($table)
     {
            $table->increments('id');
            $table->string('title');
            $table->text('text');
            $table->timestamps();
     });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('articles');
    }
}

timestampsは、インスタンスのdateTime型のupdated_atおよびcreated_at列を追加するメソッド
Eloquentモデルは、デフォルトではタイムスタンプ機能が有効になっているので、明示的に無効にし無い場合は必ず追加する
Defining Models Timestamps

ところで、Laravelは、複合主キーに対応してい無いので、基本はRailsと同じようにオートインクリメントのレコードIDを使う前提と考えて良さそうな印象を受ける

マイグレーションはmigrateコマンドで適用

$ php artisan migrate

モデルの利用

コントローラーでモデルを利用するには、まずは明示的にインポートする
モデル操作は、Modelクラスのファサードおよびインスタンスメソッドで行う
Illuminate/Database/Eloquent/Model – Laravel API

RESTFullコントローラーの実装例

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Article;
use App\Http\Requests;
use App\Http\Controllers\Controller;

class ArticleController extends Controller
{
    public function index()
    {
        $articles = Article::all();
        return view('article.index', ['articles' => $articles];
    }
...
    public function destroy($id)
    {
        $article = Article::find($id);
        $article->delete();
    }
}

テスト

簡単にテストも見ておく、ドキュメントではTestingに説明されている

Laravel5では、プロジェクトを作成するとphpunitがすぐに使えるようになっている

Homesteadはphpunitはグローバルインストールされていないので、Composerでインストールされたphpunitコマンドを使う
プロジェクトのディレクトリで、以下のようにphpunitコマンドを実行する

$ ./vendor/bin/phpunit
PHPUnit 4.7.3 by Sebastian Bergmann and contributors.

.

Time: 9.4 seconds, Memory: 12.75Mb

OK (1 test, 2 assertions)

デフォルトで含まれるExampleTestが実行される

ドキュメントでは、主にApplication Testing(統合テスト)について説明されているので、そこだけ見ていく

テストの作成については、artisanコマンドは用意されていないので、testsディレクトリにTestCaseを継承するクラスを作成する

例えば、ExampleTestと同様に、Httpリクエストの結果を検証するコードは以下のようになる

<?php

class ArticleTest extends TestCase
{
    public function testSomethingIsTrue()
    {
        $this->visit('/article')
             ->see('Listing articles');
    }
}

visitはHttp GETリクエストを実行し、seeで、指定した文字列がレスポンスに含まれているかを検証している

clickpressメソッドで、リンクやボタンをクリックできたりと非常に軽く統合テストが可能

データベースを利用するテスト

次にデータベースアクセスを含むテストを記述してみる

Laravel5では、テスト時にデータベースを切り替える仕組みを標準で用意していない
必要な環境変数はphpunit.xmlに記述する
データベースだけ切り替られれば良いのでDB_DATABASEのみ追加した

        <env name="DB_DATABASE" value="homestead_testing"/>

テストデータベースのマイグレーション

マイグレーションは、DatabaseMigrationsトレイトを利用して、テストごとにMigrationを実行する(重そうだ)

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ArticleTest extends TestCase
{
    use DatabaseMigrations;

    ...
}

Fixtureの導入

Fixtureはdatabase/factories/ModelFactory.phpに記述する
ArticleModelを1つ生成するファクトリを定義してみた

$factory->define(App\Article::class, function ($faker) {
    return [
        'title' => $faker->title,
        'text' => $faker->text,
    ];
});

ファクトリは、factoryグローバル関数で呼び出せる
/articleにアクセスすると、データベースから取得したArticleのリストが表示されることを検証するテストを記述した

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ArticleTest extends TestCase
{
    use DatabaseMigrations;

    public function testDisplayArticles()
    {
        $article = factory(App\Article::class)->create();
        $this->visit('/article')
             ->see($article->title)
             ->see($article->text);
    }
}

基本的な使い方はこんなところ、モックも豊富なので、標準機能でそこそこのカバレッジを保てそう

また、実際には、統合テストの前に、プロバイダーやモデルの単体テストも書く必要があるが、ディレクトリの構成などは別途検討する必要がある