Laravel4.1、個人用セットアップ(メモ)

タグ: Laravel4  

私個人用のLaravel4.1セットアップメモです。インストール時のMVC構造をそのまま生かし、名前空間を明示的に利用した開発を行わない場合です。ちょっとしたサイトを作成する場合の設定です。

チーム開発ではないため、CIを大げさに行うとかは行いません。個人開発ですので、環境セットアップツールを使うほど、同じ環境を繰り返して利用しません。その時点で興味があるものを取り込みます。(大抵、見つけたCSSフレームワークや、JSフレームワークをpublicの中に放り込むだけです。セットアップツールでそれを代替してくれる設定ファイルを探し、インストールされる内容を調べ、好みでない部分を変更し…、余計な時間がかかります。:D 同じ環境を何度も使用する状況では便利ですが、一度きりの環境を作るためには、逆に面倒がかかります。)

ですが、基本的な部分を忘れがちですので、自分用にメモしておきます。(特に、4.2へ移行してしまうと、4.1時代を忘れそうです。)

インストール

Laravelインストールツールの日本向け改造版を使用します。(自分で作成したから使うというよりは、設定ファイルのコメントをとり、スッキリさせたいためにです。)

laravel new -l ja -m -s ディレクトリー名

コメントを'-m'オプションで取り除いています。オリジナル英語版も、コメント日本語版('-l ja')でも、同じものになります。どちらも時々ダウンロードが遅くなるため、その時点で早く落とせる傾向のある方を利用しています。

英語版でダウンロードした場合、日本語の言語設定ファイルが無いため、別途取り込む必要があります。そのため、できるだけ日本語版を利用したほうが、設定自身は簡単でしょう。

'-s'オプションは、app/storage下のディレクトリーに対し、その他ユーザーに対する書き込みパーミッションを付けます。Apache2の動作ユーザーが書き込めるようにするためです。この設定は、自分の環境に合わせ指定し直す必要がある場合があります。

'git clone'でリポからクローンするのは、おすすめしません。バージョンアップ時は、ドキュメントに修正方法が追記されますので、それを確認しながら手動で修正したほうが、差分を調べながら修正するよりも楽でしょう。(差分を調べ判断しながら、修正したとしても、差分に入っていないDBの項目の追加やコードの変更などの変更点の修正は行わなくてはなりません。二度手間です。)

バージョン4.2に上がると、インストーラーでは4.1をインストールできなくなるでしょう。でも、新しい開発に、私が4.1を使うことは、もう無いでしょう。

動作環境の設定

動作環境はデフォルトのまま、production(実働環境、デフォルト)、local(開発環境)、testing(ユニットテスト)を使用しています。testing環境のDBなどはオンメモリで設定しておきたいため、実際のDBを使用する機能/結合テストが必要な場合は、別に環境を作成することがあります。

URLによる環境の決定は、アクセス時のURLを偽れる可能性からセキュリティーリスクになる可能性が指定されており、そのため現在では推奨されていません。標準的にはホストマシン名を利用する方法ですが、ホストマシン名が第三者に漏れる可能性がある場合、同様なセキュリティーリスクになり得ると私は考えています。そこで、環境変数により環境を指定する方法を採用しています。

開発環境のサーバーは、アクセスをこなす必要もありませんので、情報がたくさんあり、Linuxディストリビューションでは、設定も簡単なApach2を使用しています。

環境変数を開発環境の仮想ホストごとに毎回設定するのは無駄ですので、大本の設定ファイルで'SetEnv APP_ENV local'と指定してあります。(openSUSEの場合、YaSTのGUIから指定できます。「HTTPサーバー」の「メインホスト」で項目を追加するだけです。)これでサーバー経由のアクセス時の環境変数が設定できました。

続いて、端末からコマンドを実行した時の環境変数を設定するため、シェルの初期設定ファイルにexport APP_ENV=localを指定します。私はBashを使用しているため、~/.bash_profileに付け加えています。

これで、Webアクセス、端末からのコマンド実行の両方に、APP_ENV環境変数がセットできました。最後に、Laravelの環境決定コードを変更します。bootstrap/start.phpの該当箇所を以下のように設定します。

$env = $app->detectEnvironment(function(){
    return isset($_SERVER['APP_ENV']) ? $_SERVER['APP_ENV'] : 'development';
});

これにより、APP_ENVが設定されていない場合は、実機環境(production)として判断されます。つまり、本番環境では、別段追加で指定する必要が無いわけです。また、本番環境として認識されるということは、開発環境では表示されると便利な情報が、表示されないということです。設定が行われない場合に、よりセキュリティーが強化されるようにしておくのは、安全策になります。

情報を隠す

Laravelは、アプリケーションキー、DBや外部APIとの接続情報をドットファイルに隠す方法が用意されています。これを利用し、こうした秘匿情報をPHPソースコードから外し、見つかりづらくしておきましょう。

プロジェクトのルートディレクトリーに、.env.local.phpを作成します。本番用に.env.php、テスト用に.env.testing.phpも必要に応じ、作成します。.env.local.phpのサンプルです。(他のファイルも同じように、設定ファイルの形式です。)

<?php

    return [
        'APP_KEY' => 'A89C6x30z...ISdX9d223PqX',
        'DB_USERNAME' => 'UserName',
        'DB_PASSWORD' => 'I98dZX&!p@D',
    ];

環境ファイルを書き換えます。例えば、アプリケーションキーの指定は、次のようになります。

<?php
    return [
        ....
    'key' => getenv('APP_KEY'),
        ...
    ];

後は、適切にアイテムを追加しましょう。両方のファイルはデフォルトの.gitignoreで指定されていますので、Gitの管理からちゃんと外してくれます。

設定ファイル

app/config/app.php

return array(
    'debug' => false,
    # 本番環境のURLのほうが好ましいはず
    # ただ、本番環境でコマンドラインを使用しない場合は、
    # 変更する必要は無いのではないか?
    'url' => 'http://localhost', 
    'timezone' => 'Asia/Tokyo',
    'locale' => 'ja',
    'fallback_locale' => 'en',
    'key' => getenv('APP_KEY'),
    ...
);

localeは、多言語の仕組みを利用する場合の、デフォルト言語です。このlocaleに指定されたコードに対する言語ファイルが用意されていない場合、fallback_localeに指定された'en'、つまり英語が利用されます。

app/config/local/app.php

return array(
    'debug' => true,
    'url' => 'http://localhost',
);

local環境では、デバッグ機能を有効にします。

app/config/database.php

<?php
return array(
    'fetch' => PDO::FETCH_CLASS,
    'default' => 'production',
    'connections' => array(
        'production' => array(
            'driver'    => 'mysql',
            'host'      => 'localhost',
            'database'  => 'db_prod',
            'username'  => getenv('DB_USERNAME'),
            'password'  => getenv('DB_PASSWORD'),
            'charset'   => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix'    => '',
        ),
    ),
    'migrations' => 'migrations',
    'redis' => array(
        'cluster' => false,
        'default' => array(
            'host'     => '127.0.0.1',
            'port'     => 6379,
            'database' => 0,
        ),
    ),
);

この例はMySQL使用です。LAMPという言葉が定着しているように、MySQLは大抵のシステムで、開発に使用する程度なら、インストールし、ほぼ無設定で利用できます。Linuxでは、必ず標準リポに用意されています。そのため、苦労なく使用できるため、開発環境ではMySQLを第一選択肢にしています。

ただし、この設定ファイルは実機用です。私の場合、個人開発ですので、実機で何を利用するかこの段階では決まっていません。一応用意しているだけです。実機用の設定のため、DB_USERNAMEとDB_PASSWORDの値は、.env.phpから取得されます。

SQLiteのほうがお手軽ですが、PHPMyAdminなどのツールもLinuxではお手軽に利用できるため、ツールという面からも、やはりMySQLが第一選択肢になります。もちろん、標準的な機能だけを使用するなら、Laravelではドライバーの切り替えで、他のDBエンジンへ移行するのは簡単です。

app/config/local/database.php

<?php
return array(
    'default' => 'development',
    'connections' => array(
        'development' => array(
            'driver'    => 'mysql',
            'host'      => 'localhost',
            'database'  => 'db_dev',
            'username'  => getenv('DB_USERNAME'),
            'password'  => getenv('DB_PASSWORD'),
            'charset'   => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix'    => '',
        ),
    ),
);

開発環境用のDB設定です。本番環境と重複するアイテムは省きます。

開発環境ですので、getenv()で取得している接続情報は、.env.local.phpに記述します。

**app/config/testing/database.php

<?php
return array(
    'default' => 'sqlite',
    'connections' => array(
        'sqlite' => array(
            'driver'   => 'sqlite',
            'database' => ':memory:',
            'prefix'   => '',
        ),
    ),
);

単体テスト用のDB設定です。本来、単体テストではDBを直接使用することは好ましくありませんが、リポジトリーの機能テストを含めたい場合に利用する設定です。SQLiteですが、DBをメモリー上に置く設定です。そのため、永続性はありません。

手元のopenSUSEの環境において、以前はDBのセッションごとに消えていましたが、最近はOSのシャットダウンまでは残っているようです。各ソフトウェアのバージョンや設定なのでしょうが、現在の動作のほうが、合理的ですので、多分正しいのでしょう。

app/config/mail.php

return array(
    'driver' => 'smtp',
    'host' => 'smtp.gmail.com',
    'port' => 587,
    'from' => array('address' => 'お好みで!', 'name' => 'お好みで!'),
    'encryption' => 'tls',
    'username' => 'ユーザー名@gmail.com',
    'password' => '自分のアドレスのパスワード',
    'pretend' => false,
);

Gmailの場合です。

app/config/local/mail.php

return array(
    'pretend' => true,
);

実際に送信されないように、'pretend'をtureにしておきましょう。

app/config/workbench.php

<?php
return array(
    'name' => 'HiroKws',
    'email' => 'hirokws@gmail.com',
);

Laravelのワークベンチは、Laravel上で動作するcomposerパッケージを開発するための環境です。使用する場合、上記の項目を設定しておかないと、Artisanコマンド生成時に叱られます。これは、PSR-0規約で生成するため、ベンダー名として名前が必要になるためです。

メール情報を必要としているのは、このワークベンチの機能が作成された時点では、Composerも開発中で、最低限指定しておく情報として、メールアドレスを指定させようという考えだったのでしょう。実際にパッケージ作成時に、composer.jsonに指定できる情報は、もっとたくさんあり、当然ながら情報をきちんと書いておくと、利用者の利便性につながります。メールアドレスだけでは不親切です。

現在、Composer上で動作するパッケージを作成するのでしたら、ワークベンチを利用しない選択肢もあります。Composerに関する情報もたくさんありますので、無理にワークベンチを利用することもないでしょう。

app/config/session.php

    'cookie' => 'com_sample_session',

セッションクッキーの名前を変更しましょう。デフォルトは、'laravel_session'で、Laravelを使っているのがバレバレになります。どんなソフトウェアでも脆弱性は発生します。その発表が合った時、そのソフトウェアは攻撃されやすくなります。ですから、できるだけどのソフトウェアを使用しているかは、隠しておいたほうが、よりセキュアなのです。

FollowSymLinks

Apacheで構成されたサーバーの場合、FollowSymLinksもしくは、SymLinksIfOwnerMatchを付けないと、動作しない場合があり、openSUSEの標準設定は、これに当ります。

ドキュメントルートのpublic/.htaccessを書き換えても良いのですが、デプロイ先で必要ない場合、セキュリティ的に弱くなるため、好ましくありません。

そこで、仮想ホストの設定へ追加しておきます。ドキュメントルートのディレクティブに、'Options +FollowSymLinks'を追加します。public/.htaccessは、オリジナルのままです。

認証

Laravel自身も基本的な認証に加え、パスワードリセットとRemember me機能を持っています。また、外部パッケージもたくさん揃っており、どう実装するか迷うところです。

代表的な認証パッケージには、どのフレームワーク上でも動作するSentyを簡単に使用できるようにするパッケージや、Laravel専用のConfideがあり、人気が高いです。また、認証機能を含め、統合的な機能のために作成されているパッケージ、例えばOrchestraなどの認証機能だけを利用する方法もあります。

それらのパッケージは、高機能ですが、Composerでインストールし、サービスプロバイダーとエイリアスをapp/config/app.phpに設定し、マイグレーションを走らせると使用できるという、お手軽さです。内容を理解するには、READMEやドキュメントサイトを読み解く必要はあります。

Laravelの認証をそのまま使用する場合、先ずusersテーブルを作成します。'artisan migrate:make'でマイグレーションファイルを生成し、スキーマを指定します。up()メソッドの内容は、だいたい以下の通りです。

Schema::create( 'users', function($table)
{
    $table->increments( 'id' );
    $table->string( 'name', 64 );
    $table->string( 'email', 320 );
    $table->string( 'password', 60 );
    $table->smallInteger( 'active' )->default( 0 );
    $table->smallInteger( 'suspended' )->default( 0 );
    $table->string( 'remember_token' );
    $table->timestamps();
} );

最低限度必要なフィールドは、id、name、passwordでしょう。nameの代わりにメールアドレスなど、重複しないフィールドが必要です。(ユーザー認証に関しては、使いづらいでしょうが、実際は複数フィールドで一意に決まってもかまいません。)

個人的に付け加えているのは、activeでユーザーのアクティベイトを管理し、確認メールのリンクをクリックしてもらったら有効(1)にしています。

もう一つ付け加えており、suspendedは、ユーザー資格取り消しの場合、1に設定しています。

この場合、認証時にまとめてアクティベイト済で、ユーザー資格が有効であるかをチェックするには、以下のようにします。

$credentials = array_merge(
    Input::only( [ 'name', 'password' ] ),
    array( 'active' => '1', 'suspended' => '0' )
);

if( Auth::attempt( $credentials ) )
{
    // ログイン成功の処理
}

nameとpasswordだけでチェックし、その後Auth::user()を使い、細かく処理を分けることもできます。

remember_tokenは、Remember me機能のために、Laravelが管理するフィールドです。

タイムスタンプを付け加えています。作成日時と、更新日時フィールドが追加されます。ユーザー情報には必須でしょう。

ソフトデリートを実現したい場合は、'$table->softDeletes();'で、削除日付フィールドを追加します。このフィールドに日付がセットされていると、Eloquentは削除されたフィールドとして扱います。

マイグレーションファイルを更新したら、'artisan migrate'コマンドで実行し、テーブルを作成しましょう。

Userモデル

app/models/User.phpがデフォルトで用意されていますので、2つだけ付け加えます。

protected $guarded = [ ];

複数代入の保護を全フィールド解除しています。複数代入をできないフィールドを指定するのは、まとめて指定した時に、うっかり値を書き込むのを防ぐためですが、さほど利便性を感じていません。一つ一つ代入することは許されていますし、うっかりフィールドを設定してしまうのは、何も複数代入時だけではないからです。

せいぜい利用して、idフィールドを書き込めないようにしておく程度です。

(追記:この部分を書いて、眠り、起きた翌朝に、4.1.29がリリースされました。複数代入に関する脆弱性の修正です。それで思い出しましたが、そもそも保護が必要な理由は、お手軽にフィールドからの全項目をEloquentモデルに渡すコーディングスタイルに対するものです。例えば、'User::create(Input::all());'のようにです。ところが、フォームに通常はcsrfフィルターを付けます。そのためにトークンが埋め込まれます。トークンを埋め込まれたフォームの入力内容をそのまま前記のように全部渡しても、テーブルに存在しない項目を保存しようとしたとエラーになります。そこで私が多用しているのは、Input::only()とarray_only()ヘルパーです。要は保存する項目の絞り込みをコードで指定するか、モデルで宣言するかの違いです。)

    public function setPasswordAttribute( $value )
    {
        $this->attributes['password'] = Hash::make( $value );
    }

パスワードのミューティターです。内容を変更するセッターです。パスワードを設定する場合に、毎回ハッシュ変換を書くのが馬鹿らしいので、使用しています。弱点は既にハッシュされた値を書き込む手段がないことですが、ユーザーに関する限り、これで困ったことがありません。

逆に、他のモデルで使用したこともありません。

Composer 開発時使用パッケージ

Composerは、開発時だけに必要なパッケージと実働環境でも必要なパッケージを分けて使用できます。例えばコード補完のためのコードを生成してくれる、IDEヘルパーやデバッガー/プロファイラーは実働環境では必要ありません。composer.jsonに次のように記述します。

    "require-dev": {
        "phpunit/phpunit": "4.1.0",
        "mockery/mockery": "0.9.*@dev",
        "barryvdh/laravel-ide-helper": "dev-master",
        "barryvdh/laravel-debugbar": "dev-master"
    },

Laravel上では、開発環境は'local'の動作環境です。サービスプロバイダーの指定にちょっと工夫が必要です。

**app/config/local/app.php

return array(
    'debug' => true,
    'url' => 'http://localhost',
    'providers' => append_config( [
        'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider',
        'Barryvdh\Debugbar\ServiceProvider',
    ] )
);

直接、プロバイダーの配列を記述してしまうと、app/config/app.phpの'providers'配列の内容が上書きされてしまいます。append_configヘルパー関数は正にこれを解決するために追加されたものです。app/config/app.phpの'providers'配列に、引数で指定したプロバイダーの配列をマージした値を返します。

なお、これの手段を使用する場合、IDEヘルパーのReadmeに書かれている、composer.jsonのpost-update-cmdに'ide-helper:generate' Artisanコマンドを指定し、パッケージ更新のたびに実行する方法は使用できません。本番環境でコマンドが見つからずにエラーになります。

app/config/app.phpに書いておいても、本番環境で使用しなければ、サービスプロバイダーはロードされませんので、オーバーヘッドはさほどかかりません。local環境に分けて記述する理由は、artisanコマンドに余計な表示をしたくないからです。もし、ビジネスで作成するとしたら、使用者に余計な情報を与えることは、混乱を引き起こします。

エイリアスはapp/config/app.phpに直接記述します。こちらも、使用しなければオーバーヘッドはほとんどかかりません。実働環境で、エイリアスが呼び出されなければ、問題ありません。

実働環境では、デバッガータイプのパッケージも不必要です。変数配列の値を表示するような、デバッグコードは直接書かずに、環境をチェックするようにしましょう。そうしないと、エイリアス(下の例の場合はDebug)が呼び出されてしまいます。

if (App::environment('local')) Debug::log($user);

本番環境でのデプロイ時には、Composerコマンドに'--no-dev'を付け、installかupdateをします。これで、require-devセクションに指定されたパッケージはインストールされません。

CSSフレームワークの設定

CSSやJavascriptフレームワークは基本的にpublicに放り込むだけです。

CSSフレームワークは、最近Gumbyを使用しています。その設定方法をメモしておきます。他のフレームワークを使用し始めると忘れてしまうからです。

Gumbyのインストール方法はいろいろありますが、結局zipを落とし、必要なファイルをコピーする方法が一番簡単でした。展開後のディレクトリーに含まれる、css、js、fonts、imgファイルはそのままpublicに移動します。私の使用している命名規則と同じなので、リネームの必要もありません。

次に展開ディレクトリーをgumbyとし、プロジェクトルートへ移動します。

Gumbyをいじる時間は、PHPなどのコードをいじりません。デザインの調整だけを行います。その間だけ、compass watchでファイル変更監視・gumby.css生成を自動で行わせます。

ただ、個人の好みですが、compassコマンドをプロジェクトルートで実行したいため、gumbyディレクトリーに含まれるconfig.rbをプロジェクトルートへ移し、変更しています。

(前略)
# If you followed directions and ran 'gem install modular-scale' comment the next two lines out:
extensions_dir = "gumby/sass/extensions"
(途中省略)
# Set this to the root of your project when deployed:
http_path = "public"
css_dir = "public/css"
sass_dir = "gumby/sass"
images_dir = "public/img"
(後略)

上記の部分だけを変更しています。

CSSのminifyや、リソースの管理を行うパッケージやツール、さらにワークフローを実行してくれるツール類もたくさんありますが、いまだ決定的に便利だと感じるものがありません。そのため、特定のツールを使用するとは決めていません。

ルーティング

POSTで送られるフォームにはCSRFフィルターを適用します。ルートにいちいち指定するのは面倒なため、まとめて指定します。

Route::when('*', 'csrf', ['post']);

認証が必要なページは、一つ、もしくは複数のURIの下にまとめる設計をし、まとめて認証フィルターを適用します。

Route::when('admin/*', 'auth');

これで、'admin/'で始まるルートには全部、'auth'フィルターがかかります。例えば'admin*'と指定すれば、'administor'とか'admin-check'とかのURIに適用できます。もちろんこの場合でも、'admin/'で始まるURIにも適用されます。

エラーページ

開発中はapp.debugがtrueですので、エラーページは表示されませんが、エラーページは準備しておきます。

app/start/global.phpの中の例外ハンドラーを消し、app/exception_handlers.phpを作成し、それを読み込むようにします。

<?php

if( !Config::get( 'app.debug' ) )
{
    // 全例外の処理(主にPHPエラー)
    App::error( function( Exception $e )
    {
        Log::error( $e );

        return Response::view( 'errors.500', array( 'reason' => '内部でエラーが発生しました。' ), '500' );
    } );

    // HTTPエラー
    App::error( function (Symfony\Component\HttpKernel\Exception\HttpException $e, $code)
    {
        $message = $e->getMessage() == '' ? $code.'エラーです。' : $e->getMessage();

        if( File::exists( app_path().'/views/errors/'.$code.'.blade.php' ) )
        {
            return Response::view( 'errors.'.$code, array( 'reason' => $message ), $code );
        }
        else
        {
            return Response::view( 'errors.500', array( 'reason' => '内部でエラーが発生しました。' ), '500' );
        }
    } );
}

app/views/errorsディレクトリーを作成し、最低でも500.blade.phpと404.blade.phpを用意します。もし、個別に表示したいエラーがあれば、そのレスポンスコードのビューを作成します。ただ、余り内部の状態をユーザーに見せるのはおしゃれではないので、この2つで十分ではないかと思います。

続きます。随時修正、追加していきますので、永久に完成しません。