カテゴリーアーカイブ Laravel

著者:杉浦

【Laravel】モデル結合ルートを使ってコントローラの記述を簡略化

 サーバサイドプログラムのリクエスト処理部は、あるURLに対するリクエストを受け取り、リクエストに応じたデータを探して、もしなければ404を返し、と処理を行う必要があります。これを素で書くと例えば次の様になります。

// ルーティングファイル
Route::post('user/{id}', 'UserController@update');


// コントローラファイル
class UserController {
    public function update(){
        $id = request()->id ?: abort(404);
        $user = User::findOrFail($id);
        
        // $userに関連するレコードを更新する処理
        
        return $user;
    }
}

 Laravelのモデル結合ルートはこの処理を簡略化します。使い方は次の通りの名前と型の指定です。

// ルーティングファイル
Route::post('user/{user}', 'UserController@update');


// コントローラファイル
class UserController {
    public function update(User $user){
        // $userに関連するレコードを更新する処理
        
        return $user;
    }
}

 この様にすると、例えばuser/1365とpostを投げた時、User::findOrFail(1365);と同等の動作が実行されupdate関数にはUser::findOrFail(1365);の返り値のUserインスタンスが渡されます。毎度同じ手順で処理を行う必要がなくなり、楽になります。
ルーティング 5.8 Laravel

 上記まではモデル結合ルートの基本的な使い方であり、Laravelのデフォルトです。これは主キーで見つけるfindと同等の動作を仕組みを用いていますが、実際には主キーでない何らかのカラムでwhereをかけてモデルを取得したいパターンがあります。そのような時には次の様にモデル内でルートのwhereに使うカラムの名前を指定します。

// vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
    /**
     * @inheritDoc
     */
    public function getRouteKeyName()
    {
        return 'モデル結合ルートで使いたいカラム名';
    }

 このgetRouteKeyName()は次の様にルーティング用のクエリ構築メソッドresolveRouteBindingty中で使われています。このメソッドで返されるModelインスタンスがモデル結合ルートによって渡されるModelのインスタンスになります。

// vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
    /**
     * Retrieve the model for a bound value.
     *
     * @param  mixed  $value
     * @return \Illuminate\Database\Eloquent\Model|null
     */
    public function resolveRouteBinding($value)
    {
        return $this->where($this->getRouteKeyName(), $value)->first();
    }

 getRouteKeyNameを書き換えることでwhere句の指定するカラム名を書き換えました。もっと深く掘り進めてresolveRouteBindingを書き換えることによって更に大きくモデル結合ルートを改造できます。例えば次です。

    public function resolveRouteBinding($value)
    {
        return $this->where($this->getRouteKeyName(), $value)
            ->where('status', '<>', config('const.status.freeze'))
            ->first();
    }

 凍結状態のレコードが選ばれた時404を返す様にします。ちょっとしたグローバルスコープのパターンです。

65

  • この記事いいね! (1)
著者:杉浦

【Laravel】Laravelのdumpメッセージをより便利に使うブックマークレット

 Laravelのdump, dd関数は与えた値をきれいに表示してくれます。しかし、ネストの深いオブジェクトであるとクリック数が多くなってしまいます。

 この記事で紹介するブックマークレットはこれを解決します。
ページ中のaタグを全部クリック
中身を整理すると次です。

(function () {
    document.querySelectorAll('a').forEach(function (e) {
        e.click();
    })
})()

 document以下のaタグを全てクリックするのみです。dump, dd関数の結果の折り畳みの開閉はaタグで行われています。

 このブックマークレットはそれを全てクリックすることによって、開閉を一気に操作します。これによってちまちまクリックすることなく全貌を知ることが出来ます。

  • この記事いいね! (1)
著者:杉浦

【Laravel】null値のプロパティ参照を避けるoptional関数

ヘルパ 5.5 Laravel#optional
 optionalはLaravelのヘルパ関数のひとつです。次の様な使い方でnull値のプロパティを参照しようとする致命的エラーを防げます。

return optional($user->address)->street;// address===nullでもstreetを参照せずにnullをretuenする

{!! old('name', optional($user)->name) !!}// $user===nullでもnameを参照せずにnullをoldの第二引数にする

 これは特にプロパティの連鎖するリレーションで強力です。このヘルパ関数は一見素晴らしいですがその実用性は程々です。issetよりいくらかまし程度です。具体的に何が問題化というと

optional($user->address->city)->name // addressがnullだと引数生成時点でnullのプロパティ参照を行おうとして致命的エラー
optional($user->address)->city->name // cityがnullだとnull値のnameプロパティ参照を行おうとして致命的エラー

 の様に3段先を見る時点で安全性が担保されなくなります。

optional(optional($user->address)->city)->name

 とすると無駄に長くなりissetと変わりません。これはoptional関数が行っていることが、Optionalクラスのインスタンスを生成し、そのマジックメソッドを用いて通常のアクセスに代わって直下のメソッド、プロパティの参照ミスをnull値に書き換える、という処理のみであることが原因です。
 Optionalクラスのファイルはvendor/laravel/framework/src/Illuminate/Support/Optional.phpにあります。任意の型の引数を自由に操るためのクラスというだけあってなかなかの抽象度ですが、100行程の短いクラスにまとめられています。これを参考に自前で再帰optional関数を作るのもよいかもしれません(おそらく呼び出された時点のコードから先を実行前に読み取るか、プロパティもoptional型に加工することになる)。
 

  • この記事いいね! (0)
著者:杉浦

【Laravel】report関数で例外を握りつぶさない様にする

 この記事において”例外を握りつぶす”とは次の2の意味を転じて”例外の情報を記録しないまま処理を終端まで続ける”という動作を意味します。

[動サ五(四)]

1 強く握ってつぶす。「粘土を―・す」

2 意見・提案などを手もとに故意にとどめ、処置しないままにして、うやむやにする。「要求を―・す」
握り潰す(にぎりつぶす)の意味 – goo国語辞書

 つまり、真に握りつぶす

try{
  // 色々な処理
} catch (Exception $exception) {}

のみでなく

try{
  // 色々な処理
} catch (Exception $exception) {
  return JsonResponse::create('hoge');
}

といった例外を記録しない例外処理のことも、例外を握りつぶしている、と扱います。
 Laravelはデフォルトで例外処理をよしなに処理してくれます。具体的にはキャッチしてログに残し、例外発生直後は例外の理由と原因を示すリッチなエラー画面を見せてくれます。Laravel謹製の機能ならばより適した個別の処理もしてくれます(バリデーションならエラーメッセージ付きリダイレクト、など)。内部を詳しく知りたい方はエラー画面やAPIエラーから独自エラーまで! フローチャートでちゃんと理解するLaravelの例外処理とケーススタディ – Qiitaなどを読むとよいです。
 なにもせず放っておけばいい感じになるのですが、try, catchしなければいけない時もあります。例えば、画面遷移するフォームにおける例外処理は400番台エラーページにユーザを飛ばすのでなく、特定のページにリダイレクトさせたいです。Exception\Handlerに記述を逐一増やすことでリダイレクトを対応する方法がありますが、これは危険な密結合をしている巻物コードが生まれやすいです。コントローラ毎にtry, catchすることになるでしょう。
 コントローラ内でtry, catchしてリダイレクトさせるならば次の様なコードが考えられます。

        try {
             // いろいろな処理
        } catch (Exception $exception) {
            return redirect()->route('宛先')->with([
                'msg' => '保存に失敗しました' // エラーメッセージ
            ])->withInput(); // リクエストで送られてきた入力を差し戻す
        }

 このようにすると、ユーザに謎の遷移を見せずに済みます。しかし、これだけでは最初に挙げた”例外を握りつぶすコード”そのものです。Laravelの例外処理を一切通さずいきなりレスポンスを返しているため、ログに何も残りません。デバッグは困難になり、プログラムの運用者、保守者は絶句することでしょう。
 Laravelのreport関数は簡単にこの問題を解決します。

        try {
             // いろいろな処理
        } catch (Exception $exception) {
            report($exception);// 例外の内容を詳しくログに残す

            return redirect()->route('宛先')->with([
                'msg' => '保存に失敗しました' // エラーメッセージ
            ])->withInput(); // リクエストで送られてきた入力を差し戻す
        }

 例外処理の頭にreport($exception);と加えただけです。これだけで例外に適したログをLaravelが残してくれます。

  • この記事いいね! (0)
著者:杉浦

【Laravel】Laravel公式ニュースサイトLaravelNewsの紹介

Laravel News
 Laravel NewsはLaravelに関する最新の情報や特にメジャーな情報が配信されているLaravel公式のニュースサイトです。主なコンテンツはブログ、チュートリアル、パッケージ紹介、です。
 ブログはなんというか雑多なモノをまとめています。tagはあるので探しやすいですが、とにかく広範にわたって記事が載っています。
 チュートリアルは実践的というか中級者向けというかニッチな操作のチュートリアルになります。Laravelの狭い範囲を深く扱ったり、SPAなどの特定の設計での扱い方であったり、外部ツールやパッケージとの連携であったりという、Laravelの外部や深部に関するよい手法が載っています。
 パッケージはその名の通り、パッケージ紹介です。githubのstarが多いパッケージやcomposer, npmでダウンロードが多いパッケージやLaravelメイン開発者、LaravelNews運営者本人が作ったパッケージなどの紹介をしています。

  • この記事いいね! (0)
著者:杉浦

【Laravel】factory自動生成ツールlaravel-test-factory-helperの紹介

mpociot/laravel-test-factory-helper: Generate Laravel test factories from your existing models
 Laravelにはfactoryという機能があります。
データベースのテスト 5.5 Laravel#ファクトリの記述
 次の様にfactoryをdatabase/factories/以下に定義し、

use Faker\Generator as Faker;

$factory->define(App\User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
        'remember_token' => str_random(10),
    ];
});
// <a href="https://readouble.com/laravel/5.5/ja/database-testing.html#writing-factories">データベースのテスト 5.5 Laravel#ファクトリの記述</a>より引用

 次の様に呼び出すことでデータを生成し、モデルに代入された状態で使用できます。

public function testDatabase()
{
    $user = factory(App\User::class)->make();

    // モデルをテストで使用…
}

 ダミーの値はfakerというライブラリで生成します。
stympy/faker: A library for generating fake data such as names, addresses, and phone numbers.
 有用なライブラリなのですが様々な値生成方法を選べるためfactoryの定義にも悩みがちです。laravel-test-factory-helperはfactoryを自動生成します。

composer require mpociot/laravel-test-factory-helper

とcomposerで導入。

php artisan test-factory-helper:generate

とartisanを実行。するとモデル定義に従ってデータベースから値を取得、適した型のfakerをつけたfactoryをモデル分全て生成します。

  • この記事いいね! (0)
著者:杉浦

【Laravel】LaravelでDataTables.jsを楽に使うためのパッケージlaratablesの紹介

 freshbitsweb/laratables: Ajax support of DataTables (Laravel 5.5 – Laravel 5.8) Demo @
 JavaScriptにはDataTables.jsというリッチな表を簡単に作成するライブラリがあります。

DataTables | Table plug-in for jQuery

お手軽に機能付きテーブルを作るJavaScriptライブラリDataTables – 株式会社シーポイントラボ | 浜松のシステム開発会社
 DataTables.jsの基本的な使い方は、あらかじめHTMLコード上にtableタグで値を埋め込んで置き、DataTables.jsを適用して並び替え機能、絞り込み機能、ページネーション機能をつける、です。これは本当に1時間もかからずにリッチな表を作れる様なシンプルないい造りのライブラリなのですが問題がありました。それはあらかじめHTMLに埋め込む以上、初期読み込みにとても時間がかかりやすい点です。常に総数の実体をブラウザの初期段階で取得する必要があるため、総数が万件ある様なデータを対象にすると長い読み込み時間や崩れたデザインが現れだします。
 この問題を解決するには、ajaxを用いて適宜必要なデータのみを取得する基本的でない使い方をするのが良い方法です。しかしこの場合、サーバ側でDataTables.jsの提供するインタフェースに合わせたデータ処理のロジックを作る必要がでてきます。せっかく楽できるライブラリであったのに残念なことです。laratablesはこのDataTables.js用のロジックをLaravel内で簡単に記述するためのパッケージです。
 使い方は簡単。composerでプロジェクトに追加して、

composer require freshbitsweb/laratables

 Bladeに次の様にHTMLにテーブルのヘッダ、DataTablesにajaxの宛先と取得データのプロパティ名を記述、

<table id="simple-datatable-example" class="display" style="width:100%">
    <thead>
        <tr>
            <th>First Name</th>
            <th>Last Name</th>
            <th>Mobile</th>
            <th>Email</th>
            <th>Gender</th>
            <th>Country</th>
        </tr>
    </thead>
</table>

@push('scripts')
    <script>
        $(document).ready(function() {
            $('#simple-datatable-example').DataTable({
                serverSide: true,
                processing: true,
                responsive: true,
                ajax: "{{ route('simple_datatables_users_data') }}",
                columns: [
                    { name: 'first_name' },
                    { name: 'last_name' },
                    { name: 'mobile' },
                    { name: 'email' },
                    { name: 'gender' },
                    { name: 'country' }
                ],
            });
        });
    </script>
@endpush

 ajax宛先のコントローラに次の様にLaratables::recordsOf();を検索対象のモデルクラスを呼ぶだけです。

    /**
     * return data of the simple datatables.
     *
     * @return Json
     */
    public function getSimpleDatatablesData()
    {
        return Laratables::recordsOf(User::class);
    }

 これだけで次のデモページの一部、Simpleのテーブルの完成です。

Laratables
 デモページにある様に、データに無関係なカラム、データの格納されたテーブルに関連したテーブルのデータも使えます。全てを把握できていませんがgithubの説明の文量からしてDataTablesそのものより多機能そうです。

freshbitsweb/laratables: Ajax support of DataTables (Laravel 5.5 – Laravel 5.8) Demo @

  • この記事いいね! (0)
著者:杉浦

【Laravel】マイグレーションで扱うカラムにつける属性メソッドの読み取り

 Laravelにはデータベース定義を記述し、作成、ロールバックなどを行えるマイグレーション機能があります。このマイグレーションによるテーブル定義時にはカラムの属性を定義することが出来ます。
データベース:マイグレーション 5.5 Laravel#カラム修飾子
 例えば次です。

Schema::table('users', function (Blueprint $table) {
    $table->string('email')->nullable();
});
<a href="https://readouble.com/laravel/5.5/ja/migrations.html#column-modifiers">データベース:マイグレーション 5.5 Laravel#カラム修飾子</a>より引用

 これでメソッドnullableの動作によりカラムemailはnullを許すカラムになります。しかしnullable()メソッドの実態を見つけることは少し苦労を要します。
 素晴らしいide_helper(皮肉でなく本当にすごく便利)を生成する
barryvdh/laravel-ide-helper: Laravel IDE Helper
 を用いても、IDEはこのnullableへの道を見つけることが出来ません。
 これはメソッドnullrableが接続先のDBの種類によって動的に生成されているメソッドであるためです。
PHP: オーバーロード – Manual#メソッドのオーバーロード
 具体的に何が起きているかがわかる場所は、string()、integer()などの各カラム基本定義メソッドの返り値、ないし返り値が継承しているクラスvendor/laravel/framework/src/Illuminate/Support/Fluent.phpの中、そしてvendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.phpの中にあります。
 先のコードは

Schema::table('users', function (Blueprint $table) {
    $fluent = $table->string('email');
    /** @var Fluent $fluent */
    $fluent->nullable();
});

 となっており、Fluent内にnullable()は実装されていません。実装されていないメソッドが呼び出されようとする時、__call()が記述されているならば、__call()内部に記述された動作が実行されます。Fluentの__call()は次です。

    /**
     * Handle dynamic calls to the fluent instance to set attributes.
     *
     * @param  string  $method
     * @param  array   $parameters
     * @return $this
     */
    public function __call($method, $parameters)
    {
        $this->attributes[$method] = count($parameters) > 0 ? $parameters[0] : true;

        return $this;
    }

 Fluentのインスタンス内に与えられた属性を保持するのみです。この仕組みが理由で次の様なコードを書いてもバグは起きません。

Schema::table('users', function (Blueprint $table) {
    $table->string('email')->hogehoge();// 好きな名前でメソッドを呼び出せる
    $table->string('name')->nulable();// タイポしても気付かないかもしれないので、善し悪し
});

 $table->string(‘email’)->nullable();の様なカラム定義が繰り返される度にBlueprintのインスタンスである$tableはカラム定義のインスタンスであるFluentのインスタンスを新たに持ちます。クロージャ内の処理が終わった後、Schema::table()はBlueprint->build()を実行します。Blueprint->build()の中ではBlueprint->toSql()が実行されます。Blueprint->build()とBlueprint->toSql()の中身は次です。

    /**
     * Execute the blueprint against the database.
     *
     * @param  \Illuminate\Database\Connection  $connection
     * @param  \Illuminate\Database\Schema\Grammars\Grammar  $grammar
     * @return void
     */
    public function build(Connection $connection, Grammar $grammar)
    {
        // $connectionにデータベースとの接続、$grammarにデータベースの文法が入っている
        foreach ($this->toSql($connection, $grammar) as $statement) {
            $connection->statement($statement);
        }
    }
    
    /**
     * Get the raw SQL statements for the blueprint.
     *
     * @param  \Illuminate\Database\Connection  $connection
     * @param  \Illuminate\Database\Schema\Grammars\Grammar  $grammar
     * @return array
     */
    public function toSql(Connection $connection, Grammar $grammar)
    {
        $this->addImpliedCommands($grammar);// 文法クラスGrammarの中に入っているコマンドを取得する

        $statements = [];

        // Each type of command has a corresponding compiler function on the schema
        // grammar which is used to build the necessary SQL statements to build
        // the blueprint element, so we'll just call that compilers function.
        $this->ensureCommandsAreValid($connection);// SQLite用処理

        foreach ($this->commands as $command) {
// 文法クラス中のcompileから始まる名前のメソッドらの中に、要求された命令に対応するメソッドがあるならばそれを実行してSQL文の元の配列に追加
            $method = 'compile'.ucfirst($command->name);

            if (method_exists($grammar, $method)) {
                if (! is_null($sql = $grammar->$method($this, $command, $connection))) {
                    $statements = array_merge($statements, (array) $sql);
                }
            }
        }

        return $statements;
    }

 Bladeと同じです。特定のクラス中の特定の名前のメソッドがそのまま対応文法になっています。
【Laravel】Bladeの制御構文の探し方 – 株式会社シーポイントラボ | 浜松のシステム開発会社
 まだ終わりません。これでcompileCreateTable->getColumns->addModifiersと動きます。これが次です。

    /**
     * Create the main create table clause.
     *
     * @param  \Illuminate\Database\Schema\Blueprint  $blueprint
     * @param  \Illuminate\Support\Fluent  $command
     * @param  \Illuminate\Database\Connection  $connection
     * @return string
     */
    protected function compileCreateTable($blueprint, $command, $connection)
    {
        return sprintf('%s table %s (%s)',
            $blueprint->temporary ? 'create temporary' : 'create',
            $this->wrapTable($blueprint),
            implode(', ', $this->getColumns($blueprint))
        );
    }

    /**
     * Compile the blueprint's column definitions.
     *
     * @param  \Illuminate\Database\Schema\Blueprint $blueprint
     * @return array
     */
    protected function getColumns(Blueprint $blueprint)
    {
        $columns = [];

        foreach ($blueprint->getAddedColumns() as $column) {
            // Each of the column types have their own compiler functions which are tasked
            // with turning the column definition into its SQL format for this platform
            // used by the connection. The column's modifiers are compiled and added.
            $sql = $this->wrap($column).' '.$this->getType($column);

            $columns[] = $this->addModifiers($sql, $blueprint, $column);
        }

        return $columns;
    }

    /**
     * Add the column modifiers to the definition.
     *
     * @param  string  $sql
     * @param  \Illuminate\Database\Schema\Blueprint  $blueprint
     * @param  \Illuminate\Support\Fluent  $column
     * @return string
     */
    protected function addModifiers($sql, Blueprint $blueprint, Fluent $column)
    {
        foreach ($this->modifiers as $modifier) {
            if (method_exists($this, $method = "modify{$modifier}")) {
                $sql .= $this->{$method}($blueprint, $column);
            }
        }

        return $sql;
    }

 このaddModifiersでやっと終わりです。compileHogeHoge同様にmodifyHogeHogeが動きます。modifyHogeHogeがカラムに属性を追加するメソッドになります。modifyHogeHogeは文法クラスに詰まっています。文法クラスはvendor/laravel/framework/src/Illuminate/Database/Schema/Grammars以下に詰まっています。この中から使えるメソッドを抜き出し、ide_helperに追加すればIDEの自動補完が使えるようになります。例えばMySQLならば、次の様になります。

namespace Illuminate\Support{
    /**
     * @method Fluent after()
     * @method Fluent charset()
     * @method Fluent collate()
     * @method Fluent comment()
     * @method Fluent default()
     * @method Fluent first()
     * @method Fluent increment()
     * @method Fluent nullable()
     * @method Fluent srid()
     * @method Fluent storedAs()
     * @method Fluent unsigned()
     * @method Fluent virtualAs()
     */
    class Fluent {}
}

 よく使うuseCurrent()が抜けているので不完全です。少なくともこれで多少はカバーできます。

  • この記事いいね! (0)
著者:杉浦

【Laravel】Laravel開発者ツール拡張Clockworkの紹介

Clockwork
itsgoingd/clockwork: Clockwork – php dev tools integrated to your browser – server-side component
 ClockworkはLaravel内で実行された様々なデータを表示する開発者ツールの拡張です。
 とりあえずの使い方は次の様にcomposerでプロジェクトにインストールして

composer require itsgoingd/clockwork

 次のリンクから拡張をブラウザに追加するのみです。(Larvel5.5以前はconfig/app.phpにProvider追加が必要です。)
Clockwork – Chrome ウェブストア
Clockwork – Get this Extension for 🦊 Firefox (en-US)
 これだけで基本機能は使えます。詳細な機能は製作者のドキュメントとソースコード参照です。
 基本機能だけでも強力です。何ができるかというと次の5つです。
 一つ目はリクエストの中身を見ることです。ルートとコントローラとの対応を見ることができますが、これ自体はChrome標準のNetworkタブの機能と大差ありません。タブ切り替えがいらない利便性が主目的でしょう。

 二つ目は実行時間です。Laravel内部の実行内容で時間を区切れる点が特長です。図はいささか単純ですが、複雑化してくるとボトルネックを探すことが容易になり便利です。

 三つ目はコード内で発行しているログの内容の取得です。図に使ったコードではデータベースを待ち受けてログファイルに吐き出す機能がついており、このようになっています。わざわざサーバにログファイルを取りにいかなくて済む、GUIで整理されて見やすい、といった点が特長です。

 四つ目は発行されたSQL文、その発行場所と実行時間の一覧です。これは特に便利です。SQLの観点から何を改善すれば高速化できるのかがとても分かりやすいです。長時間かかるクエリ、異様に多く発行されるクエリあたりが見つかったら赤信号で、これはそれらを見つけやすくしてくれます。

 五つ目はセッション情報です。普段サーバに隠れている部分であり、いちいちdumpする必要がなくなります。

 基本的な機能は前述の5つですが、xdebugによるコードのステップ実行や、Clockwork備え付けのメソッドを使って特別なログを出力することも可能です。

  • この記事いいね! (0)
著者:杉浦

【Laravel】Cookieのsecure属性とLaravelビルトインサーバ

 Cookieにはいくつか属性がつけられます。この記事で注目するのはsecure属性です

The Secure attribute limits the scope of the cookie to “secure”
channels (where “secure” is defined by the user agent). When a
cookie has the Secure attribute, the user agent will include the
cookie in an HTTP request only if the request is transmitted over a
secure channel (typically HTTP over Transport Layer Security (TLS)
[RFC2818]).

Although seemingly useful for protecting cookies from active network
attackers, the Secure attribute protects only the cookie’s
confidentiality. An active network attacker can overwrite Secure
cookies from an insecure channel, disrupting their integrity (see
Section 8.6 for more details).RFC 6265 – HTTP State Management Mechanism

 要はsecure属性付きのCookieはHTTP通信では送信されず、HTTPS通信では送信されるようにするオプションです。これは特に通信を盗聴されることが致命的な情報、セッション情報(これを盗ると通信を乗っ取れる)などのために用いられます。

 Laravelはartisan serveコマンドによって組み込みサーバを動かせます。このサーバはPHPのビルトインウェブサーバの仕組みを利用しています。
PHP: ビルトインウェブサーバー – Manual
 実装コードは次です。

/** vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php */
    /**
     * Execute the console command.
     *
     * @return int
     *
     * @throws \Exception
     */
    public function handle()
    {
        chdir(public_path());

        $this->line("<info>Laravel development server started:</info> <http://{$this->host()}:{$this->port()}>");

        passthru($this->serverCommand(), $status);

        return $status;
    }
    
    /**
     * Get the full server command.
     *
     * @return string
     */
    protected function serverCommand()
    {
        return sprintf('%s -S %s:%s %s',
            ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false)),
            $this->host(),
            $this->port(),
            ProcessUtils::escapeArgument(base_path('server.php'))
        );
    }

 要は

$ cd ~/public_html
$ php -S [host]:[port] /hoge/fuga/project_root/server.php

をartisanで実行しやすくしているだけです。呼び出しているserver.phpが次です。

/** server.php */
<?php

/**
 * Laravel - A PHP Framework For Web Artisans.
 * @author   Taylor Otwell <taylor@laravel.com>
 */
$uri = urldecode(
    parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
);

// This file allows us to emulate Apache's "mod_rewrite" functionality from the
// built-in PHP web server. This provides a convenient way to test a Laravel
// application without having installed a "real" web server software here.
if ('/' !== $uri && file_exists(__DIR__.'/public'.$uri)) {
    return false;
}

require_once __DIR__.'/public/index.php';

 安全にpublic/index.phpを呼び出しているだけです。public/index.phpはHTTPリクエスト全てを受け取る窓口です。つまりLaravelの組み込みサーバはPHPのビルトインウェブサーバのラッピングであり、そのサーバとしての機能はほとんど素のPHPのビルトインウェブサーバの素のままです。
 このためPHPのビルトインウェブサーバの不便な点はLaravelのビルトインサーバの不便な点と言えます。業務用web開発において特に不便な点はHTTPSによるセキュアな通信が出来ない点です。


 セキュアな通信ができないということは、セキュアな通信でなければ渡されない情報を使えない、ということです。その様な情報には冒頭で述べたSecure属性がtrueなCookieが含まれます。またセキュアなcookieであるべきものには盗聴されるとセッションの乗っ取りを容易にする情報であるセッション情報が含まれます。これによりLaravelビルトインサーバ上ではセキュアなプログラムを組んだままログイン情報を持ちまわせません。Laravelビルトインサーバではログインした状態でブラウザでどうこうすることができません。

 書いている途中で気づきましたが、artisan serveにはオプションがあります。

artisan serve --env=notsecure

 このように記述すると、Laravelは.env.notsecureの環境変数を読み込んでビルトインサーバを立ち上げます。.env.notsecureを読んだ時限定でHTTPSとHTTPを同等に扱えるようなオプションとコードを記述すればビルトインサーバでも大過なくテストできそうです。ただCSRFがらみは念入りにセキュアにされてるっぽそうな。
 とはいえ業務を考えた場合、コードには十分に安全なプログラムであることが要求されます。結局のところセキュアなことを確認するためにビルトインサーバを用いることはできないので、マシン本体、仮想マシン、外部サーバなりなんなりにapacheなどの運用状態と同等の環境を用意するのが業務としては安全で確実そうです。

  • この記事いいね! (1)