tohokuaikiのチラシの裏

技術的ネタとか。

「Laravelリファレンス」 2-3 基本コンポーネント

環境設定

.envファイル

このファイルは、.gitignoreに含める。

APP_ENVで本番環境(production)、ユニットテスト環境(testing)、それ以外の開発環境(任意)を設定できる。

値は、env()ヘルパ関数で取得できる。配列は設定できない。

config/*.phpファイル

アプリケーションの設定値を記述。

値は、Config::get('foo.bar')で、config/foo.phpのbarキーを取得する。.を更につなげてハッシュの更に奥まで指定できる。

動作環境の設定

config/*php内で.envを使って設定する。

$GET/$POST/$COOKIE/$SERVER

Requestファサードで取得。

<?php
$foo = Request::input('foo'); // $_GET['foo']/$_POST['foo']
// デフォルト値付き
$foo = Request::input('foo', 'default value');
// 全取得
$vars = Request::all();
// foo barのみ取得
$foobar = Request::only(['foo', 'bar']);
// foo bar以外取得
$foobar = Request::except(['foo', 'bar']);
// ファイルインスタンス(UploadedFile)を取得
$file = Request::file('foo');
// cookie値を取得
$cookie_foo = Request::cookie('foo');
// $_SERVERを取得
$server = Request::server();
// getallheaders
$headers = Request::header('Accept-Language');

ブラウザに返却するResponseを組み立てるResponseインスタンス

ControllerやRouteの返却値にResponseインスタンスを返すといい感じにしてくれる。

<?php
// Responseの作り方
$response = Response::make('hello world');
$response = response('hello world'); // 上と同じ

// Viewにつながる渡し方
$response = Response::view(View::make('template.file'));
$response = Response::view(view('template.file')); // 上と同じ

// Jsonを出力
$response = Response::json(['foo' => 'bar']);

// ファイルダウンロード
$response = Response::download('file-path');

// Locationによるリダイレクト
$response = Response::redirectTo('/foo/bar');
$response = redirect(action('FooController@getIndex')); 

// Cookieを追加してリダイレクト
$response = Response::redirectTo('/foo/bar')->withCookie('foo', 'bar');
$response = Response::redirectTo('/foo/bar')->withCookie(cookie()->forever('foo', 'bar'));

ルーティング

routes/web.php にマッチするものがあればその時点で評価は終了し、次には回らない。

名前付きルーティング、便利。

ルーティング自体にパラメータをつけてController(Closure)でそれを取得したい場合、

<?php
Route::get('/home', [
  'param1' => 'foo/bar',
  function(){
    $actions = Route::current()->getAction();
    return $actions['param1']; // foo/barが表示される。 
  }
]);

パラメータをパスに含んでいる場合

<?php
// スラッシュによる区切り
Route::get('/foo/{id}/{param}', function($id, $param){ 

// {id}はある正規表現によって制約をつけることができる。ただし、これは{id}を使ったRoute設定の前におかないとダメ
Route::pattern('id', '^[a-z]+$'); // ^と$は省略可能

// このルートの{id}だけ制約をつけたい場合
Route::get('/foo/{id}/{param}', function($id, $param){ 
})->where(['id' => '0[a-z]+', 'param' => 'a[a-z]+']);

暗黙のController

この機能は5.3でなくなったので省略

RESTfulコントローラ

<?php
Route::resource('resource', 'ResourceController', [オプション]);

オプションには下記がある。 * names => メソッド名とルート名の対応の変更ができる。通常は、ルート名は{resource名}.{Controllerメソッド} となる。 * only => RESTメソッドの制約ができる * except => RESTメソッドの除外ができる

ルート名というのは何なんだろう?

・・・あぁ、わかった。RESTのルート名とは関係なくて単にLaravelでルートを識別するための名称のことか。Route::getとかだと単一のルーティングを指定するのに対して、RESTfulにルーティングを決めると複数のルート名をいっぺんに決めるので個々の設定をReoute::get()->name('foo')でできないってことね。URIを自在に変えられるのかと思った。

あと、RESTfulであってRESTを提供しているわけでは無い。

ミドルウェア

よくある感じのフィルタ機能。フィルタというのはLaravelでもその名称であるらしいがミドルウェアに変わっていった。

3段階の設定場所があり、それぞれに実行に入る前の処理(Before)と実行後の処理(After)が行える。登録の方法は、App/Http/Kernel.phpに記述する。

AfterとBeforeの指定の方法

こっちだとAfter

<?php
class AfterHoge
{
    public function handle($request, Closure $next)
    {
        $response = $next($request);
        // ここに処理


        
        return $response;
    }
}

これだとBefore

<?php
class BeforeHoge
{
    public function handle($request, Closure $next)
    {
        // ここに処理
        
        
        
        return $next($request);
    }
}
グローバルなミドルウェア
php artisan make:middleware Hogehoge

で雛形を作れる。これをKernel.phpに登録して完了。

Routerに仕込むミドルウェア
// get/post
<?php
Route::get('user/{id}',[
    'middleware' => ['hoge', 'hoge.b','hoge.a', ],
    function($userId){
    }
]);

// resourceは以下のようにはできないのでControllerに仕込む
Route::resource('user', 'UserController', [
    'middleware' => 'hoge'
]);
Controllerに仕込むミドルウェア

constructで実行させる。

<?php
class UserController extends Controller {
    public function __construct() {
        $this->middleware('hoge.b');
        $this->middleware('hoge.a');
    }

もちろん、これでもBefore/Afterの区別をつけて処理してくれる。

バリデーション

基本的なシンタックス

<?php
$inputs = ['name' => 'Tanaka']; // GET/POSTで与えられる配列
$rules = ['name' => ['required', 'min:4']];
$validator = Validator::make($inputs, $rules);
if ($validator->fail() === true) {
    abort(404);
}

という感じ。バリデーションルールは、「ルール名:パラメータ」という感じ。

バリデーションルールの拡張

簡単にできる。

<?php
use Validator;
Validator::extend(新ルール名, function($attr, $value, $params){
    return false ; // => エラー
});

$attrには、$inputsのキーが入る。上記の場合"name"が出てくる。

・・・・と、これ、どこに仕込ませればいいのだろう?と疑問に思って調べてみると

あたりが見つかった。extend使うよりクラスを作った方がいいよね・・・。こういうところ、この本読んでて思うことがしばしば・・・。なんか、こう、詰めが甘いというかサンプルコードがあって動くしコメント見てな的な・・・。

で、このextendは app/Providers/AppServiceProvider.php のbootメソッド内に書けば使える。

条件付きバリデーションルール

Validator::sometimeメソッドを使う。

<?php
$val = Validator::make($inputs, $rules);
$val->sometimes('追加したいINPUT名', [追加したいルール], function($入力値) use ($他の入力値) {
    // ここに判定条件。trueをリターンすると追加したいルールが適用される。
});
エラーメッセージの変更

Validator::makeの第三引数に配列として入れてやる。

[
'required' => ':attributeは必要です。'
]

この際、:attirbuteは自動的に「name」や「email」などの入力項目に変換される。

あるいは、resources/lang/ja/validation.php

<?php
    'custom' => [
        'name' => [
            'required' => ':attribute は入れなくても良いけど入れないと登録できません。',
            ],
    ],

をいうように指定する。また、このvalidation.phpのrequiredキーに書くとrequiredの際のエラーメッセージを一括で変更できる。

また、nameを「名前」にするのも、このvalidation.phpで指定している。

Controllerにおけるvalidateの簡略化

Validator::makeしてfailsをチェックして元のURLに戻るのは定型なので、Illuminate/Foundation/Validation/ValidatesRequests.php にvalidateメソッドがある。BaseControllerとなるapp/Http/Controllers/Controller.phpはこれをtraitしているのでvalidateメソッド一発で失敗した際に前の画面に戻るなどの処理も行ってくれる。

この「前のURL」を取る処理は、Illuminate/Routing/UrlGenerator->previous()であり、Refererがあればそれを、なければSessionから取り出す。てか、いまさら気づいたけどLaravelってセッションスタート前提なのか。

なので、アクションメソッドの範囲で

<?php
$this->validate($request, $rules, $custom_message);
// バリデーション通過後

とするだけで構わない。

フォームリクエストでこれらの設定をControllerからRequestクラスへ

$GET/$POSTはRequestクラスで受け取るのだけど、ControllerにFormの情報であるnameの羅列やValidationルールを書きたくない。そんな感じでロジックをRequestクラスに詰め込んでしまう。

$ ./artisan make:request NameFormRequest

でひな形作成。

<?php
    public function authorize()
    {
        // このフォームへのアクセス権限。とりあえずtrueにしておかないと403 Forbiddenが返る
        return true;
    }

    public function rules()
    {
//        $userId = $this->route()->parameters();
        $userId = $this->route('one');
        $user = User::findOrFail($userId);
        
        return [
            'name' => 'required|max:255',
            // 更新時、ユーザー自身のメールアドレスとの重複は許す
            'email' => 'required|email|max:255|unique:users,email,'.$user->id,
            'password' => 'min:6',
        ];
    }

    /**
     * カスタムエラーメッセージはここに入れる。
     *
     * @return array
     */
    public function messages()
    {
        return [
            'required' => ':attributeが足りません。',
            'name.required' => ':attributeを正しく入れてください。',
            ];
    }

    /**
     * 要素名はここに入れる。
     *
     * @return array
     */
    public function attributes()
    {
        return [
            'name' => 'お名前'
            ];
    }
    

ルーティングによるパラメータは、$this->route('one')で取得できる。2つ目はtwo... これは vendor/laravel/framework/src/Illuminate/Routing/ControllerInspector.php で設定されている模様。

このフォームクラスを使うには、Controllerのアクションメソッドで

<?php
    public function postEdit(NameFormRequest $request, $userId) {

としてしまえば自動的に$requestにはNameFormRequestクラスのインスタンスになる。

LaravelのClassのオートロードを読んでみて、Facadeがどうやって短い名前でCallされているかを考える

本丸まで

とりあえず、読み込み順としては

  1. public/index.php
  2. bootstrap/autoload.php
  3. vendor/autoload.php
  4. composer/autoload_real.php
  5. vendor/composer/ClassLoader.php
  6. vendor/composer/autoload_classmap.php とくる。

Composer\Autoload\ClassLoaderで実行されていること

このClassで、ClassMapを持ってそのクラスとファイルの対応表が vendor/composer/autoload_classmap.php にあるのでその一覧からClassを読み込んでいる。

とりあえず今手元のautoload_classmap.phpを見てみたら、3000個近くのClassが列挙してあった。

この3000個の中にあるClassであれば、 Symfony\Component\CssSelector\XPath\Extension\FunctionExtension というのも、

new Symfony\Component\CssSelector\XPath\Extension\FunctionExtension

だけで使えるようになる。

Facadesは何故いきなりuseだけで使えるようになるのか?

これ、結構わかりづらい。

  1. vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php で config/app.php のClassエイリアスを登録
  2. vendor/laravel/framework/src/Illuminate/Foundation/AliasLoader.php のloadによりautoloadの一番優先度の高いautoloadとしてregisterされる
  3. autoloadされた時点で、class_aliasによりClassのエイリアスを作る

これにより、例えばMiddlewareファイルで

<?php
namespace App\Http\Middleware;

use Auth;
error_log(class_exists('Auth') ? 'Auth exists': 'none');

とすると、

  • use Authの時点ではまだAuthというクラスに対する実装は決まっていない
  • class_existsを実行した時点でautoloadが働く
  • Authクラスのエイリアスは、config/appでIlluminate\Support\Facades\Authとなっている

ことから、Illuminate\Support\Facades\Authクラスがautoloadされるようになっている。

ついでに、このIlluminate\Support\Facades\Authクラスから実際の$authインスタンスが決められる手順

結論から言うと、

vendor/laravel/framework/src/Illuminate/Foundation/Application.php

のregisterCoreContainerAliases()により決定されている。

このregisterCoreContainerAliases()により、authファサードだと'Illuminate\Auth\AuthManager', 'Illuminate\Contracts\Auth\Factory'の2つの・routeファサードだと、'Illuminate\Routing\Router', 'Illuminate\Contracts\Routing\Registrar'の2つのクラスがその対象になるのである。

エイリアスの一覧は、そのregisterCoreContainerAliasesメソッドに20個ほど記述がある。

aliasからのインスタンス生成

このファサードエイリアス名と関連付けられたClassのインスタンスがFacadeの実体として実行されるのだが、この候補のうちからどれを選択するか?なのか?

候補が詰まった、Facade::$app(Illuminate/Foundation/Applicationのインスタンス)は、Illuminate/Container/Container.phpを継承しておりこれはArrayAccessをインプリメントしている。なので、このFacade::$app[$name]した時の挙動はContainerの実装に委ねられている。

・・・と考えてまでコード読み切れなかったので挫折。

なんだけど、これ実は候補じゃなくって単に1つ目のClassがインスタンスになっているだけという

このregisterCoreContainerAliases()内のローカル変数$aliasesって、キーの値の配列が

2つの場合 (router, auth) =>
* 1つ目が普通のClass * 2つ目がinterface

3つの場合 (cookie)=>
* 1つ目が普通のClass * 2つ目がinterfaceの親クラス * 3つ目が普通のClassがimplementしているinterface

3つの場合 (queue)=>
* 1つ目が普通のClass * 2つ目がinterface * 3つ目もinterface(2個目)

となっている。ということは、Facadeで呼ばれるのは1つ目のClassになる。

これに意味があるんだろうか?

と思って、authの2つ目のinterfaceクラスを削除したら

Illuminate \ Contracts \ Container \ BindingResolutionException
Target [Illuminate\Contracts\Auth\Factory] is not instantiable while building [Illuminate\Auth\Middleware\Authenticate].

ってエラーが出た。

なんじゃこりゃ・・・。

デバッグトレースを見てみると(filp/whoopsが無かったらとてもできる代物ではない)、middlewareの"auth"呼び出しの際にそのインスタンス解決に対して

array:1 [▼
  0 => ReflectionParameter {#169 ▼
    +name: "auth"
    position: 0
    typeHint: "Illuminate\Contracts\Auth\Factory"
  }
]

となっていた。

おそらくであるが、middlewareのauthに対して「"Illuminate\Contracts\Auth\Factory"」を実装したものであればFacadeで登録されていたものであっても使えるようにするという方針があるのではないか?

ということで、それっぽいところがMiddleware使ってるところで無いかと調べてみると

vendor/laravel/framework/src/Illuminate/Session/Middleware/AuthenticateSession.php

のコンストラクタでタイプヒントに使っていた。

ということは、AuthenticateSessionをnewする際の引数として

  • Illuminate\Contracts\Auth\Factory がTypeHintとして与えられる
  • そのTypeHintから実装可能なClassを探す
  • 探す手段として、FacadeのApplicationインスタンスで登録されているものを探す
  • 無かった場合、そのTypeHintを直接Newしてみる。
  • TypeHintがinterfaceだった場合、Fatalで終了

ということなんだな。

ところで、PHPって関数やクラスをNewする際にその関数名やクラス名からタイプヒントのClassを取れるの?

ReflectionParameterクラスを使うことでできるっぽい。

んだけど、Reflection使うのは本当に大変そうなのでちょっと実際にどうやってるかはあとで。

LaravelのFacadeがどうやってメソッドを決定しているかコード読んだのでメモ

例えばAuthの場合。

Auth::guest()のメソッドはどのインスタンスが作られるのか?

  1. Authクラスは直接的には、Illuminate/Support/Facades/Auth.php のStaticメソッドがコールされる。
  2. Authクラスは、getFacadeAccessor() return 'auth'; のみだが、このauthは Illuminate/Foundation/Application.php のregisterCoreContainerAliases()により、'Illuminate\Auth\AuthManager'に結び付けられている。
  3. AuthManagerは、create***Driver()がコールされる。このメソッドでドライバが作られる。このドライバはGuardクラスのObjectを返す
  4. この場合、***の部分は、AuthManager->getDefaultDriver()で取得でき、この値は、applicationのconfig/auth.phpで設定される。EloquentはLaravelのデフォルトORM
  5. Illuminate/Auth/Guard.phpインスタンスのメソッドがFacadeで実行されるメソッドである。
Illuminate\Support\Facades\Auth 
Illuminate\Support\Facades\Facade 
Illuminate\Auth\AuthManager
Illuminate\Auth\Guard

んー、つか。これGuardクラスが完全に外部になっているのか。

DatabaseとEloquentと2個あって、それぞれがUserテーブルに対する実装になっていてこれはサンプルとしてなかなか参考になりそう。

PHPのself::とstatic::の違い

PHP: static キーワード - Manualのコメント読んでわかったのでメモ。

<?php
class a {

    static protected $test="class a\n";

    public function static_test(){

        echo static::$test;
        echo self::$test;

    }

}

class b extends a {

    static protected $test="class b\n";

}

$obj = new a();
$obj->static_test();

echo "\n";

$obj = new b();
$obj->static_test();
?>

$ php static_test.php
class a
class a

class b
class a
  • static::$foo は、上書きされる可能性がある
  • self::$foo は、上書きされない基底クラスのstaticを読みに行く

なんつーか、感覚的には逆な感じのするキーワードだな。

追記。自分の中でfunctionが定義されている場合は、selfも上書きされる

<?php
class A
{
    const HOGE = "hoge";
    const UKI = "uki";

    function printx()
    {
        print "\n\nconst test \n\n";
        printf("self HOGE => %s\n", self::HOGE);
        printf("self UKI => %s\n", self::UKI);
        printf("static HOGE => %s\n", static::HOGE);
        printf("static UKI => %s\n", static::UKI);
    }
}

class B extends A{
    const HOGE = "hoge a";
    const UKI = "uki a";
}

class C extends A{
    const HOGE = "hoge c";
    const UKI = "uki c";
}

B::printx();

C::printx();

const test

self HOGE => hoge
self UKI => uki
static HOGE => hoge a
static UKI => uki a


const test

self HOGE => hoge
self UKI => uki
static HOGE => hoge c
static UKI => uki c

というように、上書きされないselfであるが

<?php
class A
{
    const HOGE = "hoge";
    const UKI = "uki";

    function printx()
    {
        print "\n\nconst test \n\n";
        printf("self HOGE => %s\n", self::HOGE);
        printf("self UKI => %s\n", self::UKI);
        printf("static HOGE => %s\n", static::HOGE);
        printf("static UKI => %s\n", static::UKI);
    }
}

class B extends A{
    const HOGE = "hoge a";
    const UKI = "uki a";
      
    function printx()
    {
        print "\n\nconst test \n\n";
        printf("self HOGE => %s\n", self::HOGE);
        printf("self UKI => %s\n", self::UKI);
        printf("static HOGE => %s\n", static::HOGE);
        printf("static UKI => %s\n", static::UKI);
    }
}

class C extends A{
    const HOGE = "hoge c";
    const UKI = "uki c";
    
    function printx()
    {
        print "\n\nconst test \n\n";
        printf("self HOGE => %s\n", self::HOGE);
        printf("self UKI => %s\n", self::UKI);
        printf("static HOGE => %s\n", static::HOGE);
        printf("static UKI => %s\n", static::UKI);
    }
    
}

B::printx();

C::printx();

const test

self HOGE => hoge a
self UKI => uki a
static HOGE => hoge a
static UKI => uki a


const test

self HOGE => hoge c
self UKI => uki c
static HOGE => hoge c
static UKI => uki c

となる。

ということで

self::は、functionが定義されているクラスのものを見に行く、staticはそのfunctionが呼び出された時点での定義を見に行く。

というように考えればいいのではないか。分かりやすい。

「Laravelリファレンス」 2-2 はじめてのアプリケーション

はじめてのルート定義

app/Http/routes.phpに下記のコードを加える。

<?php
Route::get('who', function(){
    return 'Hello'.Request::input('name', 'World').' ,sir.';
});

いきなり動かない。

気を取り直して、サンプルプログラムをダウンロードして再度実行

$ git clone https://github.com/laravel-jp-reference/chapter2.git sample2
$ cd sample2/
$ composer install (書籍ではcomposer updateと書いてあったがupdateだとエラーが出て動かなかった)
$ mv .env.example .env
$ php artisan key:generate
$ php artisan serve --host 0.0.0.0

ここまでやって、app/Http/routes.php に上記のコードを書き足すと確かに上手くいった。

ということは、ルーティングに関してapp/Http/routes.phpに書き足すだけでは上手く動かないのである。困った...

ルートの確認

サンプル2では、$php artisan route:list するとずらずらっとルーティングの設定一覧が出てくる。

ところが自前Laravelは出てこない。先ほどのClosureで設定したものが出てこない。

検索すると、5.3でルーティングを設定するファイルが変わったらしいということである。5.1から5.3でこのレベルの変更かよ・・・激しいな。

[Laravel]routes.phpはどこに消えたのか(5.3)

ということで、routes/web.php に app/Http/routes.php に加えたコードを書くことで無事対応完了

$ php artisan route:list
+--------+----------+----------+------+---------+--------------+
| Domain | Method   | URI      | Name | Action  | Middleware   |
+--------+----------+----------+------+---------+--------------+
|        | GET|HEAD | /        |      | Closure | web          |
|        | GET|HEAD | api/user |      | Closure | api,auth:api |
|        | GET|HEAD | who      |      | Closure | web          |
+--------+----------+----------+------+---------+--------------+

Closureによるルーティング

<?php
Route::get('who', function(){
    return 'Hello'.Request::input('name', 'World').' ,sir.';
});
Route::get('/who/{name}', function(){
    return 'Hello'.Request::input('name', 'World').' ,sir.';
});
  • whoは/whoでも同じ。
  • QueryStringsで取りたい場合は、Closure内でRequest::inputで取れる。
  • Pathで取りたい場合は、pathを{xxx}のブレースで囲み、それをClosureの引数にする。

この辺りはJavaアノテーションっぽい感じ。

ちなみに、このRoute::getとかの頭の悪そうなStaticメソッドは名前空間のお蔭で頭悪いような実装はされておらず、Facadeにより決められたInstanceがコールされている。分かりやすいけどコスト高くないですか?・・・と思ったらちゃんとObjectキャッシュを使っていた。

vendor/laravel/framework/src/Illuminate/Support/Facades/Route.php

に存在するクラスである(多分)。

というか、単にこのweb.phpは app/Providers/RouteServiceProvider.php からrequireで単純読み込みをしているだけなのでこのクラスでuseしている名前空間のClassが使えるようになっている。そのため、上記のRoute.phpがいきなりRoute::でコールできている。

staticコールできるのは、

vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php

の__callStaticによるオーバーロードであり、実際には、vendor/laravel/framework/src/Illuminate/Routing/Router.phpのgetメソッドをコールしている。

引数の「...」って可変引数なのね。関数側で受ける時は、以前のfunc_get_args()を使ってるのと同じ。関数をコールする時は call_user_func_array()でコールしているのと同じ。

はじめてのビュー

ビューはPHPファイルを直接。最近のFrameworkはSmartyなんて使わない。

ベタでグローバルに vendor/laravel/framework/src/Illuminate/Foundation/helpers.php で定義されているヘルパ関数view()を使ってViewインスタンスを返すのである。

RoutesやControllerでViewインスタンスを返さなかった場合、例えば文字列を返すとその文字列が表示される。

Controller

app/Http/Controllers/HomeController.php

<?php
class HomeController extends Controller
{
    public function index()
    {
         return view('front');
    }

routes/web.php

Routerで直接view()を使ってViewインスタンスを返す。

<?php
Route::get('/', function () {
    return view('welcome');
});

レスポンスコード

Viewインスタンスを返す際に、responseヘルパでレスポンスコードを付与することができる。

<?php
response(view('welcome2'), 404);

こんな感じ。

Bladeテンプレート

Laravel独自の軽量テンプレート。またテンプレート憶えないといけないのか・・・。Smartyにしてくれや・・・。

レイアウトをbladeから指定する。

@extends('layouts.master')

@section('content')
Home
@endsection

とすると、resources/view/layouts/master.blade.php にて@section('content')で囲んだ部分が

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

というように得ることが出来る。

Controllerで設定する。

bladeでのレイアウト設定しかなかったので「レイアウトを決定するのって、ここに書くのか。。。Controllerごとに設定してくれた方が良いような気もするのだけど・・・」と思ったら当然ありますよね。

Laraval4.2 テンプレート:コントローラーでレイアウトを宣言する

....と思ったけど、Laravel5にはそれっぽいのが見当たらない。4.2の方法でやっても通用しない。どういうことだ?無くなったのか?

Laravel5でControllerからのLayout設定は無くなったっぽい。

Laravel 5: $this->layout->content not working?

ここのスレッドで、「何でLayout機能を無くしたんだろう?」「ControllerにViewのロジックが入っているのはおかしいからさ」っていう流れなんだけど、これはかなりショック・・・。

まぁでも後でapp/Http/Controllers/Controller.phpのcallActionメソッドをごにょっといじれば何とかなるのかなぁ。というのが↑のスレッド読んだ感じ。

Bladeテンプレートについて

要点だけ * 構造式は @ で始める。 * <?php .... ?>でPHPがそのまま使える。 * {{$foo}}で変数$fooをエスケープして出力 * {!! $foo !!} で変数$fooをそのまま出力 * Smartyの{foreach} ... {foreachelse} ... {/foreach} に相当するのは、@forelse ... @empty ... @endforelse になる。

はじめてのORM

サンプルコードで、DBを作成する。

$ touch storage/database.sqlite
$ php artisan migrate


  [PDOException]
  could not find driver

んー、・・・と思ってphpinfo()を見ると、

| PDO support | enabled |
| PDO drivers | mysql   |

ってなってた。

あれー、PHP5からSQLiteは標準で・・・と思ってたら、SQLite サポートの変更によると

以前のバージョンの PHP で動作している SQLite ベースのプロジェクトがあるのなら、 ext/sqlite を使用し続ければ何の問題もありません。しかし、PDO および sqlite を明示的に有効にする必要があります。新しいプロジェクトの場合は、PDO および 'sqlite' (バージョン 3) ドライバを使用すべきです。 これは SQLite 2 に比べて高速で、ロック処理が改善されており、 プリペアドステートメントやバイナリ列をネイティブにサポートしています。

SQLite3は違うようだ。

$ sudo apt-get install -y php5-sqlite

これで、$ php artisan serve --host 0.0.0.0 をし直すと確かにphpinfo()にpdo_sqliteができていた。改めてmigrateコマンドを実行。

$ php artisan migrate
Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table
  • Routeで「Illuminate\Database\Eloquent\Model」を継承したClassを返すとそのJSONを表示させるっぽい。
  • all: 全件取得 find:IDでfind findOrFail:失敗時に例外ModelNotFoundExceptionを飛ばす
  • Modelオブジェクトのsetter/getterにより、$mode->attrで取得できる。

はじめてのController

ルーティングでControllerを指定した場合

route.php / web.php

<?php
Route::controller('user', 'UserController');

とすると、/user/以下のルーティングをこのControllerで担当することになる。

実行されるメソッドは、/user/foo にGETでアクセスされたら getFooメソッド。各メソッドで、view(テンプレート名)をreturnする。

暗黙のコントローラ参照については、HTTPコントローラー 5.1 Laravelを参考に。

であるが・・・5.3ではControllerルーティングが無くなった模様

なんとなく、HTTPメソッドをControllerメソッドにするのがダサいというかもにょりを感じていた。というのは、このControllerルーティングだと意味が被るからである。HTTPメソッドとControllerのメソッドを対応させようということらしい。

コントローラ 5.3 Laravelより

HTTPメソッド URI アクション ルート名
GET /photos index photos.index
GET /photos/create create photos.create
POST /photos store photos.store
GET /photos/{photo} show photos.show
GET /photos/{photo}/edit edit photos.edit
PUT/PATCH /photos/{photo} update photos.update
DELETE /photos/{photo} destroy photos.destroy

当然ブラウザはPUT/DELETEメソッドが無いので、擬似的に作る。

ミドルウェア

Controller内で共通の処理をさせる場合は、ミドルウェアと呼ばれる機構を使うと便利。

ミドルウェアの設定

ところで、サンプルでは

<?php
class UserController extends Controller {
    public function __construct() {
        $this->middleware('auth.first');

ミドルウェアを呼んでいる。

このauth.firstは、app/Http/Kernelで

<?php
class Kernel extends HttpKernel
{
    protected $routeMiddleware = [
        'auth.first' => \App\Http\Middleware\FirstUser::class,

というようにrouteMiddlewareで設定することで使えるようになる。このauth.firstはルーティングの際に使えるMiddlewareということらしい。

ミドルウェアの実行

ミドルウェアのFirstUserクラスは、インスタンスを作成され、handleメソッドが自動実行される。

class FirstUser
{
    public function handle($request, Closure $next)
    {
        if (Auth::guest()) {
            ....(処理)
        }
        return $next($request);
    }
Authクラスについて

Authは、Facadeで実行される静的呼び出しっぽい処理。

実際にどのインスタンスが作られるかというのは、LaravelのFacadeがどうやってメソッドを決定しているかコード読んだのでメモ - tohokuaikiのチラシの裏に書いた。

また、このAuthがどのようにFacadeのAuthとしてautoloadされるかは、LaravelのClassのオートロードを読んでみて、Facadeがどうやって短い名前でCallされているかを考える - tohokuaikiのチラシの裏に書いた。

Viewへの遷移
  • view(テンプレート名)->with('foo', $foo)で変数渡しを行う。
    これは、view(テンプレート名)->withFoo($foo)と等価。
  • redirect()->back()でRedirectレスポンスで、Refererを参照にした1つ前のLocationを作っている。
  • redirect(action('UserController@getIndex'))->with('status', 'success') とかでなんで変数を引き回せるか・・・・は、RedirectResponse::withによると、session->flash()を使っている・・・これはおそらく1回で消えるセッション変数のStoreに使うメソッド。最初session->flushはクリアする方でこれと間違えた。

はじめてのフォーム

フレームワークにおけるフォームで大切なのはひとえにバリデーションとフォーム値の引継ぎだと思っている。

  • 入力する際に、フォームのデフォルト値を如何にスムーズに設定するか
  • エラーが起こった際に、当該エラーをどう表示するか?当該エラーを引き起こしたフォームにどう対応させるエラー表示を行えるか
  • 各フォーム値のバリデーションをいかに簡単に行えるか
  • CSRF対策

ここではそういうものがあるということをのべているだけだった。