Lesson 4

Laravelのアーキテクチャ

Lesson 4 Chapter 1
Laravelを採用したアプリケーションアーキテクチャ

Lesson4ではLaravelの基本と搭載されている機能について学習していきましょう。

アーキテクチャとは

アーキテクチャとは一言で構成、構造のことを指します。Laravelに限らずですが、プロジェクトにあったアーキテクチャ設計を行うことで開発の効率化やリリース後の運用面での効率化を図ることが可能になります。今回は最も基本的なLaravelが採用しているMVCアーキテクチャのご紹介を致します。

MVCアーキテクチャとは

MVCアーキテクチャとはLesson1の学習でも紹介したようにModel / View / Controllerに処理を分割することです。

MVCのフロー図

・M = Model

MはModel(モデル)の頭文字からきています。データベースとの連携に関連した記述を行う部分として定義されており、例えばユーザーを管理しているusersテーブルのモデルであれば「User.php」のように単数形で命名してあげることが規則となります。そしてModelは以下ディレクトリに作成します。

app > Models > Model

・V = View

VはView(ビュー)の頭文字からきています。これまでのhtmlのような表示部分の処理を記述する部分として定義されており、Viewは「◯○.blade.php」というファイル名になります。そしてviewは以下ディレクトリに作成します。

resources > views > view

・Controller

CはController(コントローラー)の頭文字からきています。処理を記述する部分として定義されており、ページの表示やデータの取得、追加、削除、更新など実行する処理の記述を行う場所です。Controllerは以下ディレクトリに作成します。

app > http > controllers > Controller

上記の図を見るとユーザーが実行したアクションを処理する場合に、これほどのファイルが関連していることがわかります。LaravelではModel / View / Controllerの使用が基本となる為、イメージを備えておきましょう。

Lesson 4 Chapter 2
バリデーション

chapter2ではバリデーションの学習を行なっていきましょう。バリデーションを取り入れておくことで、ユーザーにとっても使いやすいサービスと同時にセキュリティ面でも安心してサービスを利用することが可能となります。

バリデーションとは

バリデーションとは設定したルールにデータが反していないか判定を行う機能のことを指します。会員登録を例に挙げると「パスワードが一致していません」「メールアドレスはメール形式で入力してください」「不適切な文字が含まれています」「必須項目です」など入力内容がルールに沿っていない場合に表示されるメッセージを一度は目にしたことがあるのではないでしょうか。これらがバリデーションによる動作になります。

バリデーションルール

Laravelでは予め使用できるバリデーションルールが用意されており、その数は全部で50種類以上存在します。例えば、「必須化にしたい」「文字数制限を設けたい」など、ルールを設けたい項目に対して、決められたバリデーションルールを設定することで、バリデーションを適用することが可能です。今回はその中でもよく使用するバリデーションルール10パターンをご紹介します。それ以外のバリデーションルールに興味がある方は「Laraveのバリデーションルール」について調べてみてください。

①required

requiredは入力を必須化するバリデーションルールになります。入力が空欄の状態で処理が実行されるとバリデーションが動作します。

②nullable

nullableは入力を必須化にしないバリデーションルールになります。入力の有無関わらず、リクエストを許可します。

③unique

uniqueは同一の値が存在した場合に実行されるバリデーションルールになります。既に登録されている内容と同じ内容が送られてきた場合に動作します。

④min

minは最小値の制限を設けるバリデーションルールになります。名前やパスワードなど、最低入力文字数を定義し、それに満たない場合に動作します。

⑤max

maxは最大値の制限を設けるバリデーションルールになります。名前やパスワードなど、最大入力文字数を定義し、それを超過している場合に動作します。

⑥email

emailはメールアドレス形式かどうか判定するバリデーションルールになります。「@」を基に判定している為、「@」がない場合に動作します。

⑦boolean

booleanはtrueかfalse、もしくは0か1かを判定するバリデーションルールになります。入力内容がbool型ではない場合に動作します。

⑧numeric

numericとは入力値が数字かどうかを判定するバリデーションルールになります。データ型の制限はなくリクエストが数字ではない場合に動作します。

⑨regex

regexとは正義表現を判定するバリデーションルールになります。数字は0-9までの間や半角英数字のみなどのルールを設けることが可能です。

⑩confirmed

confirmedは入力欄に入力された内容との一致を判定するバリデーションルールになります。パスワードやメールアドレスでは確認用の入力欄を設置されていることを目にしたことがあると思いますが、confirmedによって一致しているかどうか判定しています。

バリデーションの実装

Laravelでバリデーションを実装する方法をご紹介します。今回はvalidateメソッド、validatorファサード、FormRequestの3種類についてご紹介します。

使用する入力フォーム

input.blade.php
<form action="/" method="post">
  <input type="text" name="name" placeholder="名前を入力してください">
  <input type="mail" name="email" placeholder="メールアドレスを入力してください">
  <input type="submit" value="登録">
  @csrf
</form>

①validateメソッド

validateメソッドとはLaravelで用意されているメソッドでリクエストを検証することが可能です。コントローラ内でバリデーションを定義し、呼び出すことでバリデーションを実装できます。

TestController
<namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TestController extends Controller
{
    // バリデーション作成
    public function testValidate(Request $request)
    {
        $request->validate(
            // バリデーションルール
            [
                "name" => "required | min:4 | max:10",
                "email" => "required | email"
            ],
            // バリデーションメッセージ
            [
                "name.required" => "名前は必須項目です。",
                "name.min" => "名前は4文字以上で入力してください。",
                "name.max" => "名前は10文字以内で入力してください。",
                "email.required" => "メールは必須項目です。",
                "email.email" => "メール形式で入力してください。"
            ]
        );
    }
}

バリデーションとしてTestController内にtestValidateメソッドを定義しました。「$request」には入力欄で入力した名前とメールアドレスのデータが入っています。 又、nameとmailは入力フォームのname属性と一致している必要があることを覚えておきましょう。

バリデーションルールの定義

名前のバリデーションは「"name" => "required | min:4 | max:10"」を指しており、必須、4文字以上、10文字以内でバリデーションを設定しています。

メールのバリデーションは「"email" => "required | email"」を指しており、必須、メール形式のバリデーションを設定しています。

バリデーションメッセージの定義

バリデーションメッセージは上記でもあるように「name属性.バリデーションルール」とすることで任意のメッセージを出力することが可能です。

以上がvalidateメソッドでのバリデーション実装方法になります。

②Validatorファサード

続いてはValidatorファサード(Validator::)を使用した実装方法になります。クラスを使用する際はクラスのインスタンス(new クラス名)を作成する必要がありますが、ファサードとはインスタンスを作成しなくてもメソッドを実行できる機能のことです。

TestController
<namespace App\Http\Controllers;

use Illuminate\Http\Request;
// Validatorファサードをuse
use Illuminate\Support\Facades\Validator;

class TestController extends Controller
{
  // バリデーションルール
  $rules = [
    "name" => "required | min:4 | max:10",
    "email" => "required | email"
  ];

  // バリデーションメッセージ
  $message = [
    "name.required" => "名前は必須項目です。",
    "name.min" => "名前は4文字以上で入力してください。",
    "name.max" => "名前は10文字以内で入力してください。",
    "email.required" => "メールは必須項目です。",
    "email.email" => "メール形式で入力してください。"
  ];

  $validator = Validator::make($request->all(),[$rules, $message]);
  if ($validator->fails()) {
    // バリデーション時の処理
  }
}

validateメソッドと異なる点はフォームから送られたリクエストに限らず、連想配列の形式であれば検証することが可能です。又、バリデーション発生時の操作をカスタマイズすることが可能なので柔軟性にも優れています。

③FormRequest

FormRequestはこれまでのvalidateメソッドやvalidatorファサードと違い、新たにファイルを作成し、作成したファイルにバリデーションの定義を行います。

FormRequest作成コマンド
php artisan make:request TestRequest

以下がコマンド実行後に作成されるファイルです。

TestRequest
<?php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class TestRequest extends FormRequest
{
  /**
  * Determine if the user is authorized to make this request.
  *
  * @return  bool
  */
  public function authorize()
  {
    return false;
  }

  /**
  * Get the validation rules that apply to the request.
  *
  * @return  array
  */
  public function rules()
  {
    return [
    //
    ];
  }
}

authorizeメソッドはバリデーションを適用するか否かになります。デフォルトではfalseとなっているのでバリデーションを適用させる場合はtrueに変更しましょう。rulesメソッドにはバリデーションルールを追加し、その他のバリデーションメッセージなどは自信で追加していきます。以下が編集後のTestRequestです。

TestRequest
<?php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class TestRequest extends FormRequest
{
  /**
  * Determine if the user is authorized to make this request.
  *
  * @return  bool
  */
  public function authorize()
  {
    // trueに変更
    return true;
  }

  /**
  * Get the validation rules that apply to the request.
  *
  * @return  array
  */
  public function rules()
  {
    // バリデーションルール
    return [
      "name" => "required | min:4 | max:12",
      "mail" => "required|email"
    ];
  }

  // バリデーションメッセージの定義
  public function messages(){
    return [
      "name.required" => "名前は必須項目です。",
      "name.min" => "名前は4文字以上で入力してください。",
      "name.max" => "名前は10文字以内で入力してください。",
      "email.required" => "メールは必須項目です。",
      "email.email" => "メール形式で入力してください。"
    ];
  }
}

続いて作成したTestRequestを使用する為、TestControllerでは以下のように呼び出します。

TestController
<namespace App\Http\Controllers;

use Illuminate\Http\Request;
// TestRequestをuse
use App\Http\Requests\TestRequest;

class TestController extends Controller
{
    public function test(TestRequest $request){
      // 処理
    }
}

testメソッドの引数にTestRequestを代入しバリデーションを実行します。リクエストの段階でバリデーションを実行する為、バリデーションが発生した場合はtestメソッドが実行されずにリダイレクト処理が行われ、入力値に問題がなければそのままtestメソッドが実行されます。

以上がLaravelでのバリデーション実装でした。使い分けについてはプロジェクトによって異なる為、その時の状況に応じて使い分けが行えるよう知識として持っておきましょう。又、この後の項目で実際に使用していくので、より具体的な使用方法についてはこちらのchapterを参考に進めていきます。

Lesson 4 Chapter 3
サービスコンテナ

chapter3ではサービスコンテナについて学習していきます。Laravelではサービスコンテナを使用せずとも開発は可能ですが、使用しているプロジェクトも多くある為、認識として持っておきましょう。

サービスコンテナとは

サービスコンテナのサービスとは機能の部分を指し、コンテナはサービスが入る入れ物のことを指します。

サービスコンテナの中身を確認する

より具体的にイメージを持つために実際にサービスコンテナの中身を確認してみましょう。その前に今回使用していくコントローラを作成していきます。以下コマンドを実行してください。

ターミナル、コマンドプロンプト
php artisan make:controller IndexController

続いて作成したコントローラのルーティングを記述しましょう。

web.php
<?php

// 省略

Route::get("/index", [App\Http\Controllers\IndexController::class, "index"]);

作成したIndexController内にもindexメソッドを定義し、処理にdd(app())と記述しましょう。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  // testメソッド作成
  public function index(){
    // 処理
    dd(app());
  }
}

ヘルパー関数 dd()

Laravelではdd()というヘルパー関数が存在し、デバッグの際に役立ちます。()内に変数や関数などを入れることで詳細を確認することが可能です。

PHPで学習したvar_dump()と類似した関数になります。

ヘルパー関数 app()

app()を実行することでサービスコンテナのインスタンスが返ります。

それでは/indexへアクセスしサービスコンテナを確認してみましょう。以下のような画面が開かれます。

サービスコンテナ確認

こちらがサービスコンテナです。多くの情報が存在するので、触れていく中で学習するスタンスで構いません。

サービスコンテナを操作する

続いては実際にサービスコンテナへサービスを入れたり、入れたサービスを取り出したりと操作を行なっていきましょう。

サービスコンテナへサービスを代入する

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  // testメソッド作成
  public function index(){
    // 追記
    app()->bind("test", function(){
      return "これはtestです。";
    });

    dd(app());
  }
}

代入はbindを使って代入しています。今回はtestという名前をつけてメッセージ「これはtestです。」を代入しました。それではもう一度/indexへアクセスし確認してみましょう。

サービスコンテナ確認

上から2行目の「bindings」の部分を確認してみると、bindする前とした後では数が68→69に変わっています。▶︎をクリックすることでbindingsの中身を確認することができます。クリックしてみましょう。

サービスコンテナ確認

bindingsの最下層に「test」が代入されていることを確認できました。

サービスコンテナからサービスを取得する

それではtestの代入が確認できたので続いては取得してみましょう。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){
    app()->bind("test", function(){
      return "これはtestです。";
    });

    // 追記
    $test = app()->make("test");

    // 修正
    dd($test);
  }
}

取得する際は代入に比べ、記述がシンプルです。make()を使用することで取得することができます。記述を終えたら再度/indexへアクセスしてみましょう。

ブラウザ出力
"これはtestです。"

代入していたサービスのメッセージを確認することができました。

クラスの依存関係を解決する

これまで確認してきたサービスコンテナはチュートリアルです。サービスコンテナを扱う最大のメリット、役割としては単純にサービスを代入したり取り出したりするのではなく、サービスを管理することになります。

依存関係とは

依存関係とはどのような状況かを簡単に説明するとAを実行するために、Bを実行する必要がある状況などであることを指します。実際に触れながらイメージを持っていきましょう。

classAの作成

実際に動作を確認していくのにクラスを作成します。クラスを作成するコマンドは存在しない為、以下ディレクトリに手動でクラスを作成しましょう。

app > Classes(作成) > classA.php(作成)

classA.php
<?php
namespace App\Classes;

class classA
{
  public function hello(){
    return "Hello World";
  }
}

メッセージ「Hello World」を返すクラスを作成しました。

classBの作成

続いてclassAを実行する為のclassBを作成します。

app > Classes > classB.php(作成)

classA.php
<?php
namespace App\Classes;

class classB
{
  public $classA;

  public function __construct(classA $classA){
    $this->classA = $classA;
  }

  public function run(){
    return $this->classA->hello();
  }
}

サービスコンテナを利用しない場合

まずはサービスコンテナを利用しない通常の動きを確認してみます。つまり依存関係である状態が以下になります。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
// 2行追加
use App\Classes\classA;
use App\Classes\classB;

class IndexController extends Controller
{
  public function index(){
    // 下記追加
    $classA = new classA();
    $classB = new classB($classA);

    dd($classB->run());
  }
}
出力結果
Hello World

このように通常であればclassAのインスタンスを作成し、作成したclassAのインスタンスをclassBの引数に渡して処理を実行しています。

サービスコンテナを利用する場合

それではサービスコンテナを利用しない場合で処理を実行してみます。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Classes\classA;
use App\Classes\classB;

class IndexController extends Controller
{
  public function index(){
    // 下記追加
    app()->bind("classB", classB::class);
    $classB = app()->make("classB");

    dd($classB->run());
  }
}
出力結果
Hello World

サービスコンテナでclassBを管理することでclassAとclassBの依存関係が解決されました。ご覧の通りclassAのインスタンスを作成していなくともclasBのrunメソッドが正常に実行され、classAのhelloメソッドが実行されているのが確認できます。依存関係の解決がサービスコンテナを扱うメリットになります。

Lesson 4 Chapter 4
サービスプロバイダー

chapter4ではサービスプロバイダーについて学習していきます。サービスプロバイダーを取り入れておくことで、より効率的に開発を進めることが可能となります。

サービスプロバイダーとは

サービスプロバイダーとはサービスコンテナへの登録を行なってくれる場所と覚えておきましょう。サービスコンテナの学習ではコントローラ内で代入(bind)していましたが、クラスを使用する度にコンテナへ代入しているのでは正直効率が悪いです。サービスプロバイダーでは一度登録してしまえば、その後何度でも呼び出すことが可能となります。

サービスプロバイダーの作成

今回は練習用に一つサービスプロバイダーを作成してみます。以下コマンドを実行してください。

ターミナル、コマンドプロンプト
php artisan make:provider TestServiceProvider

作成したTestServideProviderの中身を確認してみましょう。以下ディレクトリに作成したサービスプロバイダーが存在しています。

app > Providers > TestServiseProvider.php

TestServiseProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class TestServiceProvider extends ServiceProvider
{
  /**
  * Register services.
  */
  public function register(): void
  {
    //
  }

  /**
  * Bootstrap services.
  */
  public function boot(): void
  {
    //
  }
}

簡単に解説していきます。尚、以前解説したnamespace、use、extendsについては省略します。

registerメソッドとbootメソッド

こちらのメソッドにはサービスコンテナへ代入するサービスを記述していきます。それ以外に処理などは記述しないよう注意しましょう。相違点としてはViewComposerと言ってどのページでも共通化しているヘッダーやサイドバーなどで使用する場合はbootメソッドへ記述していきます。今回は部分的なregisterメソッドへ記述します。又、「:」に関しては戻り値を指定しています。:voidは「何もない」ことを指しており、今回ここでは代入のみ行われ、戻り値は何もないので:voidと指定されています。

サービスプロバイダーの登録

サービスプロバイダーを新たに作成したら、使用できるよう登録する必要があります。登録する場所は以下になります。

config > app.php

app.php
<?php

// 省略

/*
 * Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
// 追加
App\Providers\TestServiceProvider::class,

サービスコンテナへの代入

コントローラへ代入する際にしていた記述をregisterメソッドへ記述しましょう。

TestServiseProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
// 追加
use App\Classes\classB;

class TestServiceProvider extends ServiceProvider
{
  /**
  * Register services.
  */
  public function register(): void
  {
    // 追加
    $this->app->bind("classB", classB::class);
  }

  /**
  * Bootstrap services.
  */
  public function boot(): void
  {
    //
  }
}

続いてコントローラを修正します。代入はサービスプロバイダーで完了しているので、取得のみの記述にします。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Classes\classA;
use App\Classes\classB;

class IndexController extends Controller
{
  public function index(){
    $classB = app()->make("classB");

    dd($classB->run());
  }
}
出力結果
Hello World

コントローラで代入せずともしっかりと処理の実行が確認できました。このようにサービスコンテナへの代入はサービスプロバイダで行うことによって、統一感が生まれ無駄な記述を省くことが可能となります。

Lesson 4 Chapter 5
リポジトリ

chapter5ではリポジトリについて学習していきます。プロジェクトによって取り入れている場合もある為、認識として持っておきましょう。

リポジトリを学習する前に

このchapterでの目的は実践を行うことではなくイメージを持ってもらうことになります。その理由として

  • 初学者にとって学習のハードルが高めな点
  • Laravelに標準で提供されていない機能である点
  • 学習範囲外の知識量が多く登場する点

などが挙げられます。しかし、知識としては必要な部分になりますので、覚えられる範囲内での認識はしておきましょう。

リポジトリについて

Laravelでリポジトリについて調べるとリポジトリパターンというデザインパターンについて多く情報が出てきますが、それらと同じ内容という認識で問題ありません。又、ベースは同じであってもプロジェクトによってはリポジトリパターンに多少の相違も存在しますので、これらを踏まえた上で学習を進めていきましょう。

デザインパターンとは

プロジェクトの設計内容を指します。デザインパターンによって保守、運用、開発などの効率をあげることができるメリットが存在します。

リポジトリパターンとは

一言で処理を細分化し、役割に応じてファイルを分割していくデザインパターンになります。

リポジトリパターンを導入するメリット

リポジトリパターンを基に設計することで処理に修正やアップデート等、何かしらの仕様変更が加わった場合、影響範囲を最低限に抑えることができます。後々のことを考えると非常に効率の良いデザインパターンとなります。

リポジトリパターンを導入するデメリット

一方デメリットとしては設計に時間が掛かる点や、扱うファイル数も増えるため、処理の実装に時間が掛かります。

一般的な処理の流れ(リポジトリパターン不採用)

以下はユーザーの取得、登録、更新、削除の実装を例としたコントローラになります。1行ほどで実装できるシンプルな処理もあれば作成や更新のように記述量が多い処理も存在しています。

UsersController.php
<?php
namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;

class UsersController extends Controller
{
    // 全ユーザーの取得処理
    public function getUsers()
    {
        $users = User::get();

        return view("index", compact("users"));
    }

    // 特定のユーザーの取得処理
    public function getUser(int $id)
    {
        $user = User::find($id);

        return view("index", compact("users"));
    }

    // ユーザー作成の処理
    public function createUser(Request $request)
    {
        User::create([
            "username" => $request->username,
            "email" => $request->email,
            "icon" => $request->icon,
        ]);

        return redirect("/index");
    }

    // ユーザーの更新処理
    public function updateUser(Request $request)
    {
        $user = User::find($request->id);
        $user->username = $request->username;
        $user->email = $request->email;
        $user->icon = $request->icon;
        $user->save();

        return redirect("/index");
    }

    // ユーザーの削除処理
    public function deleteUser(int $id)
    {
        $user = User::find($id);
        $user->delete();

        return redirect("/index");
    }
}

処理の流れ(図)

処理の流れを図にして表すと以下のような流れになります。これが一般的なMVCパターンです。

MVCパターンの図

①ユーザーがページにアクセス、又は何らかの処理を実行

②ユーザーの実行内容に紐づく処理をweb.phpからコントローラの呼び出し

③処理に応じてUserモデルにアクセス

④モデルからDBへアクセスし処理の実行

⑤結果をUserモデルへ返す

⑥Userモデルからコントローラへ結果を返す

⑦表示するページをユーザーへ返す

⑧画面上に実行結果が反映される

図だけを見るとMVCパターンはすごくシンプルに見えます。しかし実際のサービスでは現状の処理だけでなく、バリデーションを実装したり、処理に何かしら条件をつけたりなど、今後もコントローラに追加される処理やメソッドの処理内容も増えることでしょう。そうなると1つのControllerやメソッドの記述量も増え、非常に見づらく仕様変更による影響範囲のリスクが増加します。

リポジトリパターンを採用した処理の流れ

UsersController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Services\User\UserActionServiceInterface;

class UsersController extends Controller
{
    private $userActionService;

    public function __construct(
        UserActionServiceInterface $userActionService,

    ) {
        $this->userActionService = $userActionService;
    }

    // 全ユーザーの取得処理
    public function getUsers()
    {
        $users = $this->userActionService->getUsers();

        return view("index", compact("users"));
    }

    // 特定のユーザーの取得処理
    public function getUser(int $id)
    {
        $user = $this->userActionService->getUser($id);

        return view("index", compact("users"));
    }

    // ユーザー作成の処理
    public function createUser(Request $request)
    {
        $this->userActionService->create($request);

        return redirect("/index");
    }

    // ユーザーの更新処理
    public function saveUser(Request $request)
    {
        $this->userActionService->save($request);

        return redirect("/index");
    }

    // ユーザーの削除処理
    public function deleteUser(int $id)
    {
        $this->userActionService->delete($id);

        return redirect("/index");
    }
}

当初と比べかなりコントローラがシンプルで見やすくなりました。createUserメソッドやsaveUserメソッドを見るとわかりやすいです。 当初はコントローラで処理を行っていましたが、リポジトリパターンの採用によって、処理が定義されている場所へデータを渡すだけの役割をしているイメージになります。

処理の流れ(図)

リポジトリパターン図

先ほどのMVCパターンに比べ図で確認すると少しややこしく見えるかもしれません。役割を細分化し、1ファイルあたりの記述量が減った分、ファイル数が増えているイメージになります。

①各メソッド名を定義している場所になります。interfaceでは処理の記載はせず、メソッド名のみを管理します。

②実行したい処理の記述を行います。(Repositoryに定義されている処理を呼び出す)

③ServiceInterfaceと同じように処理は記載せず、各メソッド名を定義している場所になります。

④実行したい処理の記述を行います。(実際に処理を実行する)

リポジトリパターンの導入

リポジトリパターンの導入において、追加したファイルや役割をもう少し具体的に解説していきます。

大まかな導入の流れ

①Interfaceファイルの準備

②Servideファイルの準備

③ServiceProviderへの登録

④RepositoryInterfaceファイルの準備

⑤Repositoryファイルの準備

⑥RepositoryServiceInterfaceへの登録

①Interfaceファイルの準備

Interface内に定義されているメソッドは派生クラスで実装を強制することができます。強制することで実装漏れを防ぎ、実行時のエラーを回避することが可能となります。

以下がinterfaceファイルの例になります。全ユーザーの取得が行いたい場合はコントローラよりgetUsersメソッドを、更新を行いたい場合はコントローラよりsaveUserメソッドを、このように実行したい処理のメソッドを呼び出します。Interfaceの特徴として処理の記載はせず、メソッド名のみ記述します。又「:返り値」を指定してあげることでより厳格な処理とすることができます。

UserInterface.php
<?php
namespace App\Http\Services;

interface UserInterface
{
  // 全ユーザー取得
  public function getUsers(): mixed;

  // 特定のユーザー取得
  public function getUser(int $id): mixed;

  // ユーザー作成
  public function createUser(array $user): void;

  // ユーザー更新
  public function saveUser(array $user): void;

  // ユーザー削除
  public function deleteUser(int $id): void;
}

②Servideファイルの準備

続いてはServiceになります。Serviceとは処理を記載する場所になります。 厳密には処理というより、「何の処理を実行するか」になります。具体的な処理自体はこの後のRepositoryにて記載します。

UserService.php
<?php
namespace App\Http\Services;

use App\Http\Repositories\Interfaces\UserRepositoryInterface;

private $userRepository;

public function __construct(UserRepositoryInterface $userRepository)
{
  $this->userRepository = $userRepository;
}

class UserService implements UserInterface
{
  // 全ユーザー取得
  public function getUsers(): mixed
  {
    return $this->userRepository->getUsers();
  }

  // 特定のユーザー取得
  public function getUser(int $id): mixed
  {
    return $this->userRepository->getUser($id);
  }

  // ユーザー作成
  public function createUser(array $user): void
  {
    $this->userRepository->create($user);
  }

  // ユーザー更新
  public function saveUser(array $user): void
  {
    $this->userRepository->save($user);
  }

  // ユーザー削除
  public function deleteUser(int $id): void
  {
    $this->userRepository->delete($id);
  }
}

$userRepositoryとはuserRepositoryInterfaceのことを指します。ServiceではuserRepositoryに定義されているメソッドを実行する処理を記載しています。

③ServiceProviderへの登録

先ほどの図には記載していませんが、UserServiceとUserInterfaceをプロバイダへ登録する作業が必要になりますので、覚えておきましょう。

ServiceProvider.php
<?php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class ServiceClassServiceProvider extends ServiceProvider
{
// 省略

private function register()
    {
        $this->app->bind(
            \App\Http\Services\UserInterface::class,
            \App\Http\Services\UserService::class
        );
    }
}

④RepositoryInterfaceファイルの準備

先ほどのServiceInterfaceと同じようにRepositoryInterfaceにもRepositoryで使用するメソッド名の定義を行います。

UserRepositoryInterface.php
<?php
namespace App\Http\Repositories\Interfaces;

interface UserRepositoryInterface
{
  // 全ユーザー取得
  public function getUsers(): mixed;

  // 特定のユーザー取得
  public function getUser(int $id): mixed;

  // ユーザー作成
  public function create(array $user): void;

  // ユーザー更新
  public function save(array $user): void;

  // ユーザー削除
  public function delete(int $id): void;

}

⑤Repositoryファイルの準備

Repositoryでは実際の処理を記述していきます。serivceからRepository内に定義されているメソッドへアクセスすることでそれぞれの処理を実行することができます。

UserRepository.php
<?php
namespace App\Http\Repositories\Eloquent;

use App\Models\User;

class UserRepository implements UserRepositoryInterface
{

  private $user;

  public function __construct(User $user) {
    $this->user = $user;
  }

  // 全ユーザー取得
  public function getUsers(): mixed
  {
    return $this->user->get();
  }

  // 特定のユーザー取得
  public function getUser(int $id): mixed
  {
    return $this->user->find($id);
  }

  // ユーザー作成
  public function create(array $user): void
  {
    $this->user->create($user);
  }

  // ユーザー更新
  public function save(array $user): void
  {
    $this->user->save($user);
  }

  // ユーザー削除
  public function delete(int $id): void
  {
    $this->user->delete($id);
  }

}

⑥RepositoryServiceInterfaceへの登録

RepositoryServiceProviderでも先ほどのProviderと同様、コンテナをプロバイダへ登録しておきましょう。

RepositoryServiceProvider.php
<?php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class RepositoryServiceProvider extends ServiceProvider
{

// 省略

private function register()
    {
        $this->app->bind(
            \App\Http\Repositories\Interfaces\UserRepositoryInterface::class,
            \App\Http\Repositories\Eloquent\UserRepository::class
        );
    }
}

以上でリポジトリパターンの導入は完了になります。記述やファイルが増えた分、ややこしく感じる部分もありますが、処理を分割したことにより処理の修正がシンプルになり、影響範囲も最小限に減らすことが可能です。その他にも処理の仕様を変更したい場合にアクセスするRepositoryのメソッドを変更してあげるだけで実現が可能となります。

Lesson 4 Chapter 6
キャッシュ

chapter6ではキャッシュについて学習していきます。キャッシュを使用することでシステムのパフォーマンス向上や少量の記述量で処理を実装することが可能となります。

キャッシュとは

データを一時的に保存する機能のことを指します。データベースから取得したデータをキャッシュとして保存しておくことで、2回目以降の処理はデータベースに処理が到達する前にキャッシュを参照し一時保存したデータの取得が行われます。よってデータベースへ掛かる負荷を抑えると同時に処理速度を上げることも可能となります。

キャッシュでデータを保存する

早速キャッシュを使用してデータを保存してみましょう。保存する方法はキャッシュヘルパーを使用する方法とキャッシュファサードを使用する方法が存在します。又、新規・上書き保存を行うputメソッドと存在しないデータの保存を行うaddメソッドの2つが存在するのでそれぞれの動作を確認していきましょう。

①キャッシュヘルパーを使用して、存在しないデータの保存(add)

②キャッシュファサードを使用して、存在しないデータの保存(add)

③キャッシュヘルパーを使用して、データの新規保存か上書き保存(put)

④キャッシュファサードを使用して、データの新規保存か上書き保存(put)

①キャッシュヘルパーを使用して、存在しないデータの保存(add)

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){
    // キャッシュヘルパーで存在しないデータの保存
    cache()->add("key", "value");
  }
}

cacheヘルパーとは「cache()」の部分を指します。存在しないデータを保存する場合はadd()を使用して保存しましょう。第一引数にキー、第二引数に値を代入します。

②キャッシュファサードを使用して、存在しないデータの保存(add)

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;

class IndexController extends Controller
{
  public function index(){
    // キャッシュファサードで存在しないデータの保存
    Cache::add("key", "value");
  }
}

cacheファサードとは「Cache::」の部分を指します。同じように第一引数にはキー、第二引数には値を代入します。又、ファサードを使用する場合はCacheクラスのuseを忘れないようにしましょう。

③キャッシュヘルパーを使用して、データの新規保存か上書き保存(put)

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){
    // キャッシュヘルパーで新規保存、又は上書き保存
    cache()->put("key", "value");
  }
}

データの新規保存、又は上書き保存を行う場合はputを使用します。

④キャッシュファサードを使用して、データの新規保存か上書き保存(put)

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;

class IndexController extends Controller
{
  public function index(){
    // キャッシュファサードで新規保存、又は上書き保存
    Cache::put("key", $value);
  }
}

以上がキャッシュにデータを保存する方法になります。

キャッシュからデータを取得する方法(add)

続いては先ほど保存したデータを取得してみましょう。

①キャッシュヘルパーでデータの取得

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){
    cache()->add("key", "value");
    $cache = cache("key");
    dd($cache);
  }
}
出力結果
value

取得する際は「cache("取得したい値のキー")」とすることでキャッシュデータを取得することができます。

②キャッシュファサードでデータの取得

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;

class IndexController extends Controller
{
  public function index(){
    Cache::add("key", "value");
    $cache = Cache::get("key");
    dd($cache);
  }
}
出力結果
value

取得する際は「Cache::get("取得したい値のキー")」とすることでキャッシュデータを取得することができます。

キャッシュからデータを取得する方法(put)

方法については特に違いはありませんが、挙動が変わるのでそこも含めて確認していきます。まずはaddの状態のままvalueの部分を変更します。

③キャッシュヘルパーでデータの取得

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){
    cache()->add("key", "val");
    $cache = cache("key");
    dd($cache);
  }
}
出力結果
value

値の部分を変更していますが、出力結果は変わりません。冒頭でもお伝えしましたがaddはデータ(key)が存在しない場合に実行される関数である為、既に存在している場合は処理が実行されません。この場合は上書き保存としてputに書き換えてあげることで変更後の値を取得することができます。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){
    cache()->put("key", "val");
    $cache = cache("key");
    dd($cache);
  }
}
出力結果
val

④キャッシュファサードでデータの取得

ファサードであっても変更後のデータを取得したい場合はputを使用し値の上書きを行ってあげましょう。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;

class IndexController extends Controller
{
  public function index(){
    Cache::put("key", "val");
    $cache = Cache::get("key");
    dd($cache);
  }
}
出力結果
val

キャッシュからデータを削除する方法

最後にキャッシュからデータの削除を行ってみましょう。

キャッシュヘルパーで任意のデータを削除する場合

まずは削除される前のデータを確認しておきます。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){
    $cache = cache()->add("key", "value");

    dd($cache);
  }
}
出力結果
true

trueと表示されているので、データが存在していることが確認できます。続いて、forgetを使用し、保存したデータを削除してから確認してみましょう。

IndexController
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){
    $cache = cache()->add("key", "value");
    // 追加
    cache->forget("key");
    dd($cache);
  }
}
出力結果
false

falseと表示されているのでデータが存在しないことを表しています。このようにforgetを使用することでデータの削除に成功しました。

キャッシュファサードで任意のデータを削除する場合

キャッシュファサードの場合は以下のように削除を実行します。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;

class IndexController extends Controller
{
  public function index(){
    $cache = Cache::put("key", "val");
    Cache::forget("key");
    dd($cache);
  }
}
出力結果
false

全てのキャッシュデータを削除する場合

任意のデータではなくキャッシュデータ全てを削除する場合はforgetの部分を以下に変更します。

キャッシュヘルパーの全削除
cache()->flush()
キャッシュファサードの全削除
Cache::flush()

Lesson 4 Chapter 7
コレクション

chapter7ではコレクションについて学習していきます。Laravelではコレクションも多く登場する為、理解しておくことで開発に対する柔軟性や幅が広がり、スムーズなLaravelの開発を行うことが可能となります。

コレクションとは

コレクションとは配列を操作する機能が拡張されたクラスになります。イメージとしては配列に似ており、Laravel独自の配列として認識していただいて問題ありません。Laravelでコレクションが多く登場する理由として、この先の項目で学習するヘルパ関数の中には返り値がコレクションの形式も存在している為です。コレクションを扱えるようになっておくことで、実装パターンも増え幅広く開発を行うことが可能となります。

この項目で扱うコレクションの準備

今回は2名のユーザー情報が格納された架空のデータを用意しました。こちらを基にコレクションの動作確認を行なっていきます。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){

    $users = collect([

      "0" => collect([
        "username" => "テスト太郎",
        "age" => "20",
        "mail" => "test@test.com"
        ]),

      "1" => collect([
        "username" => "テスト花子",
        "age" => "18",
        "mail" => "hanako@hanako.com"
      ])

    ]);

    dd($users);

  }
}
web.php
<?php

// 省略

Route::get("/index", [App\Http\Controllers\IndexController::class, "index"]);

ヘルパー関数 collect()

collect()を使用することでコレクションを作成することが可能です。

この項目で扱うコレクションの確認

「http://127.0.0.1:8000/index」にアクセスし確認してみましょう。「Illuminate\Support\Collection」の部分がコレクションであることを表しいています。又「items: array:2」の部分には2つのデータが格納されていることを表しています。

コレクションの確認

itemsの部分をクリックすると格納されているデータが確認できます。定義した内容通りのデータであることが確認できました。

コレクションの確認

コレクションの操作

コレクションを扱うメソッドは多数用意されています。今回はその中でも使用頻度の高いメソッドをいくつかピックアップしご紹介致します。

①get

getとはコレクションからデータを取得する場合に使用されるコレクションメソッドになります。作成した$usersのデータをもとに確認してみます。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){

    $users = collect([

      "0" => collect([
        "username" => "テスト太郎",
        "age" => "20",
        "mail" => "test@test.com"
        ]),

      "1" => collect([
        "username" => "テスト花子",
        "age" => "18",
        "mail" => "hanako@hanako.com"
      ])

    ]);

    // ->get(キー、又はインデックス番号)
    dd($users->get("0"));

  }
}
出力結果
Illuminate\Support\Collection {#277 ▼ // app/Http/Controllers/IndexController.php:23
  #items: array:3 [▼
    "username" => "テスト太郎"
    "age" => "20"
    "mail" => "test@test.com"
  ]
  #escapeWhenCastingToString: false
}

getの引数にはインデックス番号、もしくはキーを指定してあげることで特定のデータを取得することが可能です。getに引数を指定したり、絞り込んだデータを取得する際にはgetを使用しましょう。以下は今後学習する内容ですが、データの全権取得についてご紹介します。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){

    // データベースに存在するユーザー情報を全て取得する例
    $users = User::get();

    dd($users);

  }
}

このように全件取得する場合は引数が不要になります。詳細は今後の学習でご紹介しますが、getではこのようなこともできるというイメージは持っておきましょう。

②all

allとはデータの情報を全件取得するコレクションメソッドになります。getと似ていますが、取得したいデータの指定や絞り込みができない為、全データを取得する場合に使用しましょう。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){

    $users = collect([
            "0" => collect([
                "username" => "テスト太郎",
                "age" => "20",
                "mail" => "test@test.com"
            ]),
            "1" => collect([
                "username" => "テスト花子",
                "age" => "18",
                "mail" => "hanako@hanako.com"
            ])
        ]);
        // ->all()
        dd($users->all());

  }
}
出力結果
array:2 [▼ // app/Http/Controllers/IndexController.php:23
  0 => Illuminate\Support\Collection {#277 ▼
    #items: array:3 [▼
      "username" => "テスト太郎"
      "age" => "20"
      "mail" => "test@test.com"
    ]
    #escapeWhenCastingToString: false
  }
  1 => Illuminate\Support\Collection {#266 ▼
    #items: array:3 [▼
      "username" => "テスト花子"
      "age" => "18"
      "mail" => "hanako@hanako.com"
    ]
    #escapeWhenCastingToString: false
  }
]

③toArray

toArrayは配列に変換するコレクションメソッドになります。開発状況によって配列でしか使用できないメソッドや配列の方が都合が良い実装を行う場合に便利なメソッドです。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){

    $users = collect([
            "0" => collect([
                "username" => "テスト太郎",
                "age" => "20",
                "mail" => "test@test.com"
            ]),
            "1" => collect([
                "username" => "テスト花子",
                "age" => "18",
                "mail" => "hanako@hanako.com"
            ])
        ]);
        // ->toArray()
        dd($users->toArray());

  }
}
出力結果
array:2 [▼ // app/Http/Controllers/IndexController.php:23
  0 => array:3 [▼
    "username" => "テスト太郎"
    "age" => "20"
    "mail" => "test@test.com"
  ]
  1 => array:3 [▼
    "username" => "テスト花子"
    "age" => "18"
    "mail" => "hanako@hanako.com"
  ]
]

結果はallの時と似ていますが、allはコレクション、toArrayは配列と覚えておきましょう。

④make

makeはコレクションに変換するコレクションメソッドになります。開発状況によってコレクションでしか使用できないメソッドやコレクションの方が都合が良い実装を行う場合に便利なメソッドです。先ほどのtoArrayで配列にしたデータをmakeを使用してコレクションに変換してみます。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
// コレクションクラスをuse
use Illuminate\Support\Collection;

class IndexController extends Controller
{
  public function index(){

    $users = collect([
            "0" => collect([
                "username" => "テスト太郎",
                "age" => "20",
                "mail" => "test@test.com"
            ]),
            "1" => collect([
                "username" => "テスト花子",
                "age" => "18",
                "mail" => "hanako@hanako.com"
            ])
        ]);

        // 配列のデータを格納
        $arrayUsers = $users->toArray();

        // 配列のデータをコレクションのデータに変換
        $collectUsers = Collection::make($arrayUsers);

        dd($collectUsers);

  }
}
出力結果
Illuminate\Support\Collection {#281 ▼ // app/Http/Controllers/IndexController.php:27
  #items: array:2 [▼
    0 => array:3 [▼
      "username" => "テスト太郎"
      "age" => "20"
      "mail" => "test@test.com"
    ]
    1 => array:3 [▼
      "username" => "テスト花子"
      "age" => "18"
      "mail" => "hanako@hanako.com"
    ]
  ]
  #escapeWhenCastingToString: false
}

このようにCollectionクラスをuseし、Collection::make(データ)とすることでデータをコレクションに変換することが可能です。

⑤pluck

pluckとはデータに含まれる特定の値を取得する際に便利なコレクションメソッドになります。先ほどのtoArrayと一緒に使用し、usersのデータからusernameのみ取得してみます。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){

    $users = collect([
            "0" => collect([
                "username" => "テスト太郎",
                "age" => "20",
                "mail" => "test@test.com"
            ]),
            "1" => collect([
                "username" => "テスト花子",
                "age" => "18",
                "mail" => "hanako@hanako.com"
            ])
        ]);

        // ->pluck(キー)
        $arrayUsers = $users->pluck("username")->toArray();

        dd($arrayUsers);

  }
}
出力結果
array:2 [▼ // app/Http/Controllers/IndexController.php:26
  0 => "テスト太郎"
  1 => "テスト花子"
]

pluckを使用することでデータに含まれる特定の値を取得することが可能となりました。

⑥push

pushは既存のデータに対して新たなデータを追加するコレクションメソッドになります。新に$userデータを作成し既存データにpushしてみます。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){

    $users = collect([
            "0" => collect([
                "username" => "テスト太郎",
                "age" => "20",
                "mail" => "test@test.com"
            ]),
            "1" => collect([
                "username" => "テスト花子",
                "age" => "18",
                "mail" => "hanako@hanako.com"
            ])
        ]);

        // 新たなデータの作成
        $user = collect([
            "username" => "Aさん",
            "age" => "25",
            "mail" => "push@test.com"
        ]);

        // ->push(追加したいデータ)
        $users = $users->push($user);

        dd($users);

  }
}
出力結果
Illuminate\Support\Collection {#278 ▼ // app/Http/Controllers/IndexController.php:31
  #items: array:3 [▼
    0 => Illuminate\Support\Collection {#277 ▼
      #items: array:3 [▼
        "username" => "テスト太郎"
        "age" => "20"
        "mail" => "test@test.com"
      ]
      #escapeWhenCastingToString: false
    }
    1 => Illuminate\Support\Collection {#266 ▼
      #items: array:3 [▼
        "username" => "テスト花子"
        "age" => "18"
        "mail" => "hanako@hanako.com"
      ]
      #escapeWhenCastingToString: false
    }
    2 => Illuminate\Support\Collection {#280 ▼
      #items: array:3 [▼
        "username" => "Aさん"
        "age" => "25"
        "mail" => "push@test.com"
      ]
      #escapeWhenCastingToString: false
    }
  ]
  #escapeWhenCastingToString: false
}

2名だった$usersのデータに作成した$userのデータをpushすることで3名存在する一つのデータを作成することが可能となりました。

⑦put

putは特定のデータに対して値を追加するコレクションメソッドになります。現状一人のユーザーはusername、age、mailと3つの情報を保持していますが、特定のユーザーにpasswordの情報を追加してみます。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){

    $users = collect([
            "0" => collect([
                "username" => "テスト太郎",
                "age" => "20",
                "mail" => "test@test.com"
            ]),
            "1" => collect([
                "username" => "テスト花子",
                "age" => "18",
                "mail" => "hanako@hanako.com"
            ])
        ]);

        // ->put(キー, 値);
        $user = $users[0]->put("password", "put_test");

        dd($user);

  }
}
出力結果
Illuminate\Support\Collection {#277 ▼ // app/Http/Controllers/IndexController.php:33
  #items: array:4 [▼
    "username" => "テスト太郎"
    "age" => "20"
    "mail" => "test@test.com"
    "password" => "put_test"
  ]
  #escapeWhenCastingToString: false
}

putを使用することで4つ目のデータを追加することに成功しました。又、$users[インデックス番号]とすることで特定のユーザーを参照することも可能です。今回であれば0 => テスト太郎、1 => テスト花子となっています。このように参照する方法もあるので知識として把握しておきましょう。

⑧count

countとはデータの数を数えるコレクションメソッドになります。$usersに格納されているデータの数をcountを使用して算出してみます。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){

    $users = collect([
            "0" => collect([
                "username" => "テスト太郎",
                "age" => "20",
                "mail" => "test@test.com"
            ]),
            "1" => collect([
                "username" => "テスト花子",
                "age" => "18",
                "mail" => "hanako@hanako.com"
            ])
        ]);

        // ->count()
        dd($users->count());

  }
}
出力結果
2 // app/Http/Controllers/IndexController.php:33

今回はテスト太郎、テスト花子の2名が$usersに格納されているので、出力結果も2となりました。このようにcountを使用することでデータの数を数えることができます。

⑨search

searchはコレクションに含まれる値を検索し、一致する値があればキーを、一致する値がなければfalseを返すコレクションメソッドになります。テスト太郎の情報を基に、searchを使用してみます。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){

    $users = collect([
            "0" => collect([
                "username" => "テスト太郎",
                "age" => "20",
                "mail" => "test@test.com"
            ]),
            "1" => collect([
                "username" => "テスト花子",
                "age" => "18",
                "mail" => "hanako@hanako.com"
            ])
        ]);

        // ->search(値, true)
        dd($users[0]->search("20", true));

  }
}
出力結果
"age" // app/Http/Controllers/IndexController.php:33

searchの第一引数には検索したい値を代入します。第二引数にはtrueを入れておくことで型のチェックも同時に行ってくれます。値によっては想定外の結果が返ることもあるので、trueを指定しておくことを推奨します。テスト太郎の持つデータの中から20を含むデータを検索し、該当する値が存在したのでキーのageが返ってきていることが確認できます。仮に21など存在しないデータを検索した場合はfalseが返ります。

⑩each

eachはコレクションのループ処理を行う場合に使用するコレクションメソッドになります。$usersに格納されているデータを1件ずつ$arrayに格納していきます。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){

    $users = collect([
            "0" => collect([
                "username" => "テスト太郎",
                "age" => "20",
                "mail" => "test@test.com"
            ]),
            "1" => collect([
                "username" => "テスト花子",
                "age" => "18",
                "mail" => "hanako@hanako.com"
            ])
        ]);

        // 空の配列を定義
        $array = [];

        $users->each(function ($user) use (&$array) {
            // 空の配列に$q($users)を1件ずつ代入
            $array[] = $user;
        });

        dd($array);

  }
}
出力結果
array:2 [▼ // app/Http/Controllers/IndexController.php:33
  0 => Illuminate\Support\Collection {#277 ▼
    #items: array:3 [▼
      "username" => "テスト太郎"
      "age" => "20"
      "mail" => "test@test.com"
    ]
    #escapeWhenCastingToString: false
  }
  1 => Illuminate\Support\Collection {#266 ▼
    #items: array:3 [▼
      "username" => "テスト花子"
      "age" => "18"
      "mail" => "hanako@hanako.com"
    ]
    #escapeWhenCastingToString: false
  }
]

まずは「ループしたいコレクション->each」とし、function内の$userには複数のデータを保持している$usersの単一の値が代入されます。今回の例で言うと1回目のループ処理で$userにテスト太郎、2回目のループ処理で$userにテスト花子、このようなイメージです。$userの命名は任意ですが、単数名にしてあげましょう。ループ処理内部で外部に定義している変数を利用したい場合は「use(使用したい変数)」としてあげます。又、useした変数に格納されているデータを外部でも引き継ぎたい場合は参照渡しと言って変数の前に「&」を付与する必要がありますので注意が必要です。$array[] = $userとすることで$arrayへ次々に$userを格納することが可能となります。

Lesson 4 Chapter 8
ヘルパー

chapter8ではヘルパーについて学習していきます。ヘルパーはヘルパー関数とも呼ばれます。ヘルパーを理解しておくことでLaravelでは開発の柔軟性や幅を広げることが可能となります。

ヘルパー関数とは

ヘルパー関数とはLaravel独自の関数のことを指します。Laravel独自なので、開発者が定義せずとも予め用意されており、使用できる関数になります。このchapterでは利用場面の多いヘルパー関数の紹介とヘルパー関数の作成を行っていきましょう。

よく使うヘルパー関数

①dd

これまでも多く登場しましたdd関数です。ddはヘルパー関数の中でも非常に多くの場面で使用されていて、開発効率を向上してくれるデバッグ関数になります。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){
    $user = collect([
            "username" => "テスト太郎",
            "age" => "20",
            "mail" => "test@test.com"
        ]);

        dd($user);
  }
}
出力結果
Illuminate\Support\Collection {#277 ▼ // app/Http/Controllers/IndexController.php:19
  #items: array:3 [▼
    "username" => "テスト太郎"
    "age" => "20"
    "mail" => "test@test.com"
  ]
  #escapeWhenCastingToString: false
}

dd関数の特徴としては、使用された時点で処理が停止します。その為、dd以降に記述されている処理は実行されません。上記のようにddに確認したいデータを渡すことで$userはコレクションであり、格納されているデータの詳細を目視で確認することができます。実際に開発を行う場面でddを使用する場合も同じようにデータをチェックしてあげることで、データそのものに問題があるかどうかを確認しバグやエラー発生時も原因を調査しやすくなります。確認が終えたらdd関数は消すかコメントアウトしプログラムとして実行されないようにしておきましょう。

②view

viewは登場頻度の多い関数になります。Lesson3でも使用しましたが、ページを表示させる処理としてメソッドの最下部に使用するケースがほとんどです。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){
    return view("hello");
  }
}
出力結果
Hello World

viewの中には表示させたいbladeファイルを指定してあげます。その際blade.phpは不要です。今回はhello.blade.phpファイルをブラウザに出力する処理としてview("hello")としました。

③redirect

redirectも登場頻度の多い関数になります。処理の一番最後に実行させることが多い関数で、redirect(URL)としてあげることで任意のページでリダイレクトさせることが可能です。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  return redirect("/");
}

ウェルカムページ

このように/indexへアクセスした時の処理として/へリダイレクトさせるよう記述しました。 つまりIndexControllerのindexメソッドが実行されるとLaravelのウェルカムページへリダイレクトされます。

④collect

前回のchapterでも登場してきました、データをコレクションに変換させる関数になります。collect(データ)とすることでコレクション型に変換することができます。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){
    $user = [
      "username" => "テスト太郎",
      "old" => "20",
      "email" => "test@test.com"
    ];

    dd(collect($user));
  }
}
出力結果
Illuminate\Support\Collection {#277 ▼ // app/Http/Controllers/IndexController.php:20
  #items: array:3 [▼
    "username" => "テスト太郎"
    "old" => "20"
    "email" => "test@test.com"
  ]
  #escapeWhenCastingToString: false
}

作成した段階では通常の配列です。確認したい場合はcollectを外してdd($user)としてみてください。上記のようにcollect($user)とすることで出力結果からも分かるように$userが配列になったことを確認できました。コレクションでしか使用できない関数を使用したい状況で便利な関数です。

⑤today

today関数は本日の日付を取得する関数になります。使用方法は非常にシンプルですが、今日の日付を表示するサービスはよくあるので、利用頻度も高いヘルパ関数となります。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){
    dd(today());
  }
}
出力結果
Illuminate\Support\Carbon @1677628800 {#277 ▼ // app/Http/Controllers/IndexController.php:13
  #endOfTime: false
  #startOfTime: false
  #constructedObjectId: "0000000074eebff30000000072ba0dd8"
  #localMonthsOverflow: null
  #localYearsOverflow: null
  #localStrictModeEnabled: null
  #localHumanDiffOptions: null
  #localToStringFormat: null
  #localSerializer: null
  #localMacros: null
  #localGenericMacros: null
  #localFormatFunction: null
  #localTranslator: null
  #dumpProperties: array:3 [▶]
  #dumpLocale: null
  #dumpDateProperties: null
  date: 2023-03-01 00:00:00.0 UTC (+00:00)
}

注目すべき箇所は一番最下部に位置している「date: 2023-03-01 00:00:00.0 UTC (+00:00)」の部分です。ただこれではまだ実践的ではないのでtodayに続いて記述を加えてあげます。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){
    dd(today()->format("Y年m月d日"));
  }
}
出力結果
"2023年03月01日" // app/Http/Controllers/IndexController.php:13

このようにformatを使用することで出力のカスタマイズが可能です。Y=年、m=月、d=日になります。today関数はシンプルで利用場面も多いヘルパ関数なので覚えておきましょう。

ヘルパー関数は全部で200種類以上存在しているので、今回は使用頻度が多く、現状のレベル感でも使いやすいヘルパー関数を一部ご紹介しました。気になる方はLaravelのヘルパー関数について色々調べてみましょう。

ヘルパー関数の自作

ヘルパー関数は開発者が定義し、使用することも可能です。ヘルパー関数を定義しておくことで、定義したヘルパー関数を様々な場所から自由に呼び出すことができるメリットがあります。又、使用頻度が高い関数であれば一度定義しておくことで利便性も増すことでしょう。しかしチームで開発を行う場合はかえって混乱を招いてしまう可能性もある為、プロジェクトのルールに沿ってヘルパー関数の定義を行っていきましょう。

ヘルパー関数を定義するファイルを作成する

ヘルパー関数はAppフォルダ内に作成します。今後、複数作成されることを想定しHelperフォルダを準備し、その中にファイルを作成していきます。

app > Helper > HelloMessageHelper.php

HelloMessageHelper.php
<?php
if (!function_exists('helloMessage')) {

  function helloMessage(): string
  {
    return "Hello World";
  }
}

if (!function_exists('helloMessage'))こちらは他の場所でhelloMessageメソッドが定義されていないかチェックしています。helloMessage関数はHello Worldを出力する処理です。

作成したヘルパー関数の使用準備を行う

作成した関数を使用する前に準備を行う必要があります。以下ファイルを開きましょう。

composer.json
"autoload": {
        "psr-4": {
            "App\\": "app/",
            "Database\\Factories\\": "database/factories/",
            "Database\\Seeders\\": "database/seeders/"
        },
        // 追加
        "files": [
            "app/Helpers/HelloMessageHelper.php"
        ]
    },

「files」の部分を追加してください。追加が完了したらターミナル、又はコマンドプロンプトにて以下コマンドを実行しましょう。

ターミナル、コマンドプロンプト
composer dump-autoload

作成したヘルパー関数を実行する

これで準備は完了です。作成したヘルパー関数を実行してみましょう。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){
    return helloMessage();
  }
}
出力結果
Hello World

以上がヘルパー関数の作成から実行までの流れでした。冒頭でもお伝えしましたが使用頻度が多い処理の場合は関数として定義しておくことで利便性の向上につながるメリットも存在しますが、チームでの開発ではプロジェクトルールに従うか、予め相談した上でのヘルパー関数の作成を推奨しますので、個人の判断で実装は控えるようにしておきましょう。

Lesson 4 Chapter 9
ファイルストレージ

chapter9ではファイルストレージについて学習していきます。ファイルストレージを理解しておくことで開発の幅を広げることが可能となります。

ファイルストレージとは

ファイルストレージとは一言でファイルを保存する場所になります。テキストファイルや画像ファイルなどを保存したり、保存されているファイルを操作したりが可能になります。

ファイルシステム設定ファイルの確認

Laravelにはファイルを保存する為の設定がされているファイルが存在します。以下ファイルを参照し確認してみましょう。

config > filesystems.php

filesystems.php
<?php
return [

    'default' => env('FILESYSTEM_DISK', 'local'),

    'disks' => [

        'local' => [
            'driver' => 'local',
            'root' => storage_path('app'),
            'throw' => false,
        ],

        'public' => [
            'driver' => 'local',
            'root' => storage_path('app/public'),
            'url' => env('APP_URL') . '/storage',
            'visibility' => 'public',
            'throw' => false,
        ],

        's3' => [
            'driver' => 's3',
            'key' => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
            'region' => env('AWS_DEFAULT_REGION'),
            'bucket' => env('AWS_BUCKET'),
            'url' => env('AWS_URL'),
            'endpoint' => env('AWS_ENDPOINT'),
            'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
            'throw' => false,
        ],

    ],

    'links' => [
        public_path('storage') => storage_path('app/public'),
    ],

];

disks

保存先のストレージが3種類記載されています。デフォルトではlocalが設定されています。

local

localドライバーと言います。自分の環境(ローカル)でのみ参照可能なファイルの保存先情報が記載されています。その為、実際に運用されているサービスの多くはlocalドライバー以外を使用していきましょう。

public

publicドライバーと言います。自分だけでなく外部からの参照も可能なファイルの保存先情報が記載されています。基本的に運用されているサービスでは使用される場所になります。

s3

s3ドライバーと言います。AWS(Amazon Web Services)が提供しているオンラインストレージの情報を記載する場所になります。大量のファイルを扱う場合に適しています。

この学習でfilesystems.phpの記述の変更は行いませんが、今後必要に応じて変更する可能性はありますので認識しておきましょう。

実践の準備

今回は動作確認も含めて画像ファイルの保存を行っていきましょう。その為の準備をしていきます。

画像保存用のフォームを作成

resources > views > index.blade.php

index.blade.php

<!-- 画像を保存するフォーム -- -->
<form action="/index" method="post" enctype="multipart/form-data">
  <input type="file" name="file">
  <input type="submit" value="登録">
  @csrf
</form>

Laravelでformタグを使用しpost送信する場合は「@csrf(トークン)」の記述がない場合、エラーとなる為、記述忘れがないように注意しましょう。

app > Http > Controllers > IndexController

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
  public function index(){
    return view("index");
  }
}

routes > web.php

web.php
<?php
// 省略

Route::get("/index", [App\Http\Controllers\IndexController::class, "index"]);

「http://127.0.0.1:8000/index」へアクセスし、以下のようになっていればフォームは完成です。

画像保存フォーム

画像保存用の処理を記述

画像を保存する処理を追加しました。post処理の詳細については今後の項目で学習していくので、今回は簡潔にお伝えしてます。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
// use
use Illuminate\Support\Facades\Storage;

class IndexController extends Controller
{
  public function index(){
    return view("index");
  }

  // 追加
  public function fileSave(Request $request)
    {
      // 保存する画像のファイル名を取得
      $filename = $request->file("file")->getClientOriginalName();

      // Storageクラスを使用し、画像を保存する処理
      Storage::putFileAs('public', $request->file("file"), $filename);

      return redirect("/index");
    }
}

fileSaveメソッドは登録ボタンを押すことで動作するメソッドになります。送信した画像ファイルを取得し、Storageクラスを使用することでファイルの参照、登録、削除などの操作が可能になります。そのうちの一つであるputFileAsメソッドを使用して任意の場所に保存しています。第一引数のpublicは保存先を指定、第二引数には画像ファイルのインスタンス、第三引数に画像名を指定します。処理が完了したらredirectで「/index」へリダイレクトするように指定しています。

web.php
<?php
// 省略

Route::get("/index", [App\Http\Controllers\IndexController::class, "index"]);
// 追加
Route::post("/index", [App\Http\Controllers\IndexController::class, "fileSave"]);

fileSaveメソッドを実行する為のルーティングも忘れずに設定しましょう。

シンボリックリンクの設定

通常、画像ファイルはstorage/app/publicに保存されます。このファイルをweb上からアクセスできるようにする為にpublic/storageからシンボリックリンクを作成します。例えばSNSのサービスでは各ユーザーが設定したプロフィール画像などweb上に公開されている画像が存在しています。このシンボリックリンクの設定がされていない場合は画像へのアクセスができず、表示がされないので注意しましょう。シンボリックリンクの作成はコマンド一つで完了します。以下コマンドをターミナル、又はコマンドプロンプトにて実行しましょう。

ターミナル、コマンドプロンプト
php artisan storage:link
実行結果
The [public/storage] link has been connected to [storage/app/public]

このように表示されればシンボリックリンクの作成は完了です。

画像の保存と表示

画像の保存・登録

準備は完了しました。早速画像ファイルを保存してみましょう。ブラウザに戻り「ファイルを選択」で適当なpngやjpgなどの画像ファイルを選択し登録してみましょう。

画像保存フォーム

以下ディレクトリに登録した画像が保存されています。

public > storage > 画像

Storageクラスを参照することで確認することも可能です。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class IndexController extends Controller
{
  public function index(){

    // 追加
    $file = Storage::disk("public")->files();
    dd($file);

    return view("index");
  }

  public function fileSave(Request $request)
    {
      $filename = $request->file("file")->getClientOriginalName();

      Storage::putFileAs('public', $request->file("file"), $filename);

      return redirect("/index");
    }
}
ブラウザ
array:2 [▼ // app/Http/Controllers/IndexController.php:15
  0 => ".gitignore"
  1 => "google.png"
]

今回はpublicドライバーへの保存先に指定しているためdisk("public")とし、files()でpublic/storage配下に存在するファイルを参照することができます。

画像の表示

続いては画像の表示を行う為、以下ファイルの修正を行いましょう。

index.blade.php
{{-- 画像を保存するフォーム --}}
<form action="/index" method="post" enctype="multipart/form-data">
  <input type="file" name="file">
  <input type="submit" value="登録">
  @csrf
</form>

{{-- 画像を表示する部分 --}}
<img src="{{ asset('storage/' ."$file")}}" width="50%">

assetとはLaravelのヘルパー関数の一つです。asset関数を使用することでpublicフォルダのパスを返してくれるのでパスの位置関係をあまり気にせず画像の表示が可能となります。Laravelで画像を扱う際は使用する認識でいましょう。$fileはこれからコントローラへ定義する変数です。

IndexController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class IndexController extends Controller
{
  public function index(){

    // 修正
    $file = Storage::disk("public")->files()[1];

    return view("index", compact("file"));
  }

  public function fileSave(Request $request)
    {
      $filename = $request->file("file")->getClientOriginalName();

      Storage::putFileAs('public', $request->file("file"), $filename);

      return redirect("/index");
    }
}

$fileの定義を行います。先ほどddで確認した部分を再確認してみましょう。

ブラウザ
array:2 [▼ // app/Http/Controllers/IndexController.php:15
  0 => ".gitignore"
  1 => "google.png"
]

今回表示させたい画像ファイルのインデックス番号が1なのでfiles()[1]としました。変数をbladeファイルで使用する場合はcompact()を使用して渡してあげます。$fileをindex.blade.phpへ渡す場合は「compact("file")」になります。

ブラウザ確認

それでは再度「http://127.0.0.1:8000/index」へアクセスし確認してみましょう。

保存した画像の表示

画像の表示に成功しました。

今回はチュートリアルとしてStorageクラスを使用したファイルの取得と保存を行いましたが、これはあくまで方法の一つです。現段階ではこのようなやり方があり、このようなこともLaravelでは可能だということを認識しておきましょう。冒頭でもお伝えしましたが、今後の学習で更に実践的な部分の学習に入っていくので、そこでpost処理の詳細な内容をお伝えしていきます。

Lesson 4 Chapter 10
レート制限

chapter10ではレート制限について学習を行っていきます。レート制限を理解しておくことで安全性も高く、ユーザーにとっても利用しやすいサービスを構築することが可能になります。

レート制限とは

レート制限とはDos攻撃対策として導入しておくべき制限になります。Laravelには一定時間内に一定以上のアクセスがあった場合、アクセスを制限する機能が存在します。それがレート制限です。

Dos攻撃とは

Dos(Denial of Service Attack)攻撃とはアクセスを集中させサーバーに負荷を掛けるサイバー攻撃のことを指します。短時間でブラウザの連続更新などが当てはまります。

レート制限の実装方法

レート制限の実装については大きく分けてサーバー側で制限を設けるか、アプリケーション側から制限を設けるかの2通り存在します。今回は後者のアプリケーション側からアクセス制限を設けていきいます。

アプリケーション側でアクセス制限を設けるメリットは以下になります。

  • 特定のユーザーにだけアクセス制限を設けることができる。
  • 特定のアクションにだけアクセス制限を設けることができる。

このように条件に合わせてアクセス制限を設けることが可能であるため、柔軟性に優れています。

レート制限 動作確認の準備

今回は特定のページにレート制限を設け、一定回数以上のアクセスがあった場合、どのような動作になるのかを確認していきましょう。

実装準備①

RouteServiceProvider.phpにてレート制限の定義を行います。

app > Providers > RouteServiceProvider.php

RouteServiceProvider.php
<?php

// 省略

/**
     * Configure the rate limiters for the application.
     *
     * @return void
     */
    protected function configureRateLimiting()
    {
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });

        // 追加
        RateLimiter::for('test', function (Request $request) {
            return Limit::perMinute(5);
        });
    }

configureRateLimitingメソッド内に追加します。forの第一引数の命名は任意で、ここではtestとしておきます。perMinuteの引数にはアクセス回数を記述してください。今回のperMinute(5)だと1分間に5回までのアクセスとしています。

実装準備②

web.phpにてレート制限を適用させるルーティングを選択します。

routes > web.php

web.php
<?php

// 省略

Route::middleware(['throttle:test'])->group(function () {
  // ルーティング
  Route::get("/index", [App\Http\Controllers\IndexController::class, "index"]);
});

上記のmiddleware内にレート制限(5回/1分)を適用させたいルーティングの追加していきます。['throttle:test']の「test」は先ほどRouteServiceProvider.phpで記載したforの第一引数を指定しましょう。

これにてレート制限の準備は完了です。

ViewとController

bladeとcontrollerの処理も記載しておきます。レート制限には関係ないので参考までに修正してください。

IndexController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
    public function index()
    {
        return view("index");
    }
}
index.blade.php
<p>Hello World</p>

レート制限の動作確認

/indexへアクセスし現状の確認を行っておきましょう。

ハローワールド

続いてブラウザ更新を5回以上実行してみます。

アクセス制限画面

一定時間内に一定回数アクセス(今回は1分間に5アクセス)が発生した場合にエラー画面の表示ができました。このようにLaravelではDos攻撃対策としてレート制限を実装することが可能です。覚えておきましょう。

Lesson 4 Chapter 11
バッチ処理

chapter11ではバッチ処理について学習していきます。バッチ処理を理解しておくことで、サービスの効率性や開発の幅を広げることが可能となります。

バッチ処理とは

バッチ処理とは一言で処理の自動化になります。例えば毎日決まった時間にメールを送るなどバッチ処理を実装することで処理の自動化の実現が可能になります。今回は一定時間に簡単なメッセージを出力するバッチ処理を定義し、動作確認を行っていきましょう。

artisanコマンドの作成

一定の時間で処理を実行するロジックとしては一定時間に定義されたコマンドを実行し、そのコマンドによって処理が実行されるイメージになります。その為、まず初めにメッセージを出力する為のコマンドを作成しましょう。以下コマンドを実行しファイルを作成します。

コマンドプロンプト、ターミナル
php artisan make:command BatchTest

BatchTestの部分はファイル名になります。ファイル名は任意ですが、今回はBatchTestとしました。作成したBatchTest.phpを確認しましょう。

app > Console > Commands > BatchTest.php

BatchTest.php
<?php
namespace App\Console\Commands;

use Illuminate\Console\Command;

class BatchTest extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:name';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        return Command::SUCCESS;
    }
}

こちらは作成してすぐの状態です。3ヶ所修正していきます。

BatchTest.php
<?php
namespace App\Console\Commands;

use Illuminate\Console\Command;

class BatchTest extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'batch_test';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'バッチテスト実行';

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        logger('バッチテスト実行');
    }
}

1. protected $signature

実行する際のコマンドをここで定義します。今回はbatch_testと定義したので、「php artisan batch_test」コマンドが使用可能になります。

2. protected $description

実行コマンドの説明文を記載するプロパティになります。

3. public function handle()

コマンドの処理内容を記述するメソッドになります。「php artisan batch_test」を実行するとLaravelLogに「バッチテスト実行」とメッセージが出力されます。

作成したコマンドの登録

上記だけではまだコマンドを作成しただけなので実行する為にコマンドの登録を行います。ファイルを開きましょう。デフォルトは以下の通りです。

app > Console > Kernel.png

Kernel.php
<?php
namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        // $schedule->command('inspire')->hourly();
    }

    /**
     * Register the commands for the application.
     *
     * @return void
     */
    protected function commands()
    {
        $this->load(__DIR__.'/Commands');

        require base_path('routes/console.php');
    }
}

Kernel.phpを編集していきます。

Kernel.php
<?php
namespace App\Console;

use App\Console\Commands\BatchTest;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    protected $commands = [BatchTest::class];

    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        // $schedule->command('inspire')->hourly();
        $schedule->command("batch_test")->everyMinute();
    }

    /**
     * Register the commands for the application.
     *
     * @return void
     */
    protected function commands()
    {
        $this->load(__DIR__ . '/Commands');

        require base_path('routes/console.php');
    }
}

1. use App\Console\Commands\BatchTest

作成したコマンドを登録するので、コマンドを定義しているBatchTestクラスをuseします。

2. protected $commands

$comanndsプロパティに実行するコマンドを定義します。

3. protected function schedule

scheduleメソッドにはコマンド実行のタイミングを定義します。今回は1分おきにコマンドを実行するためeveryMinute()としました。他にも5分や1時間、1日、1週間、1ヶ月、1年など複数のメソッドが用意されています。興味のある方はLaravelのタスクスケジュールについて調べてみてください。

以上でKernel.phpにてコマンドの登録は完了になります。

作成・登録したコマンドの確認

ターミナル、又はコマンドプロンプトで以下コマンドを実行することで作成したコマンドが登録できているか確認することができます。

ターミナル、コマンドプロンプト
php artisan list
実行結果

...
Available commands:
  about                  Display basic information about your application
  batch_test             バッチテスト実行
  clear-compiled         Remove the compiled class file
  completion             Dump the shell completion script
  db                     Start a new database CLI session
  docs                   Access the Laravel documentation
  down                   Put the application into maintenance / demo mode
...

Available commands内にbatch_testコマンドの存在を確認できました。作成したコマンドの登録ができているか確認は以上になります。

コマンドの実行と確認

それでは作成したコマンドを実行してみましょう。

ターミナル、コマンドプロンプト
php artisan batch_test

storage > logs > laravel.log

laravel.log
[実行した日付 実行した時間] local.DEBUG: バッチテスト実行

定義していたメッセージがログに出力されていることが確認できました。

コマンドの実行を自動化

先ほどは手動でコマンドを実行した場合にのみメッセージがログに出力されている状態でしたので、続いては作成したコマンドを自動で実行されるようにしていきます。尚、毎分実行する定義はKernel.phpにて完了しているので、一つコマンドを実行するのみです。

ターミナル、コマンドプロンプト
php artisan schedule:work

コマンドを実行したらlaravel.logをしばらく放置しておきましょう。

laravel.log
[05:29:00] local.DEBUG: バッチテスト実行
[05:30:00] local.DEBUG: バッチテスト実行
[05:31:01] local.DEBUG: バッチテスト実行

1分毎にコマンドが実行され、処理である「バッチテスト実行」のメッセージ出力が確認できました。

Laravelではこのように決められた時間にコマンドを実行する方法があり、それをバッチ処理と言います。重要な処理になるので覚えておきましょう。

Lesson 4 Chapter 12
ジョブとキュー

chapter12ではジョブとキューについて学習していきます。

はじめに

この項目ではジョブとキューについて実践ではなく主に紹介を行っていきます。その理由として、非動機処理の要素が含まれており、Laravelで本来学習する内容と少し離れてしまう点である為です。とはいえLaravelに含まれる機能の一つになりますので、認識としては抑えておきましょう。

非同期処理とは

非同期処理とはローディングを行わずに、処理の実行を行うことを指します。PHPで開発されたシステムは主に同期処理となっており、例えばユーザーを作成した後、ローディングが行われることでユーザーリストに作成したユーザーが反映されます。対して非同期処理はローディングを行うことなく反映が行われます。PHPで非同期処理を行う場合は、JavaScriptなど他の言語を用いて開発を行うことで可能となります。

ジョブ(Job)とは

ジョブとは処理のことを指します。ジョブはキューに登録し、キューで管理しているジョブが実行されます。例えば、登録や更新、削除の処理などのことを指します。

キュー(Queue)とは

キューとはジョブを非同期処理で実行する仕組みのことを指します。キューで管理されているジョブを実行することで非同期処理の実現が可能となります。

ジョブとキューの使い所

一言で非同期処理であると使いやすい部分に使用することでジョブとキューが活躍します。例えば、メールを受信した際にメールリストに新着メールが追加されるシーンを思い浮かべるとイメージが湧きやすいかもしれません。

ジョブとキューの準備

続いてはジョブとキューを実際に使用するまでの流れをお伝えしていきます。

1. キューテーブルの作成

ジョブが登録されるキューテーブルを作成します。以下コマンドを実行することで2つのテーブルが作成されます。

ターミナル、コマンドプロンプト
php artisan queue:table

1つ目にジョブが登録されるjobsテーブルが作成されます。

database > migrations > ◯◯年_◯◯月_◯◯日_create_jobs_table

◯◯年_◯◯月_◯◯日_create_jobs_table
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('jobs', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('queue')->index();
            $table->longText('payload');
            $table->unsignedTinyInteger('attempts');
            $table->unsignedInteger('reserved_at')->nullable();
            $table->unsignedInteger('available_at');
            $table->unsignedInteger('created_at');
        });
    }

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

2つ目に実行失敗時に使用されるfield_jobsテーブルが作成されます。

database > migrations > ◯◯年_◯◯月_◯◯日_field_jobs_table

◯◯年_◯◯月_◯◯日_field_jobs_table
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('failed_jobs', function (Blueprint $table) {
            $table->id();
            $table->string('uuid')->unique();
            $table->text('connection');
            $table->text('queue');
            $table->longText('payload');
            $table->longText('exception');
            $table->timestamp('failed_at')->useCurrent();
        });
    }

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

コマンドを実行し作成したテーブルをデータベースに反映させてテーブル作成は完了となります。以下がデータベース反映のコマンドになります。

ターミナル、コマンドプロンプト
php artisan migrate

2. ジョブクラスの作成

続いては実行したい処理を定義するジョブクラスを作成します。以下コマンドを実行することでクラスが作成されます。

ターミナル、コマンドプロンプト
php artisan make:job TestJob

app > Jobs > TestJob.php

TestJob.php
<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class TestJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        //
    }
}

例えば、登録や削除、更新など非同期で実行したい処理をhandleメソッド内に定義してあげます。

3. .envファイルの修正

.env内に記述が既にあれば修正し、なければ追記しましょう。

.env
QUEUE_CONNECTION=database
QUEUE_DIRVER=database

.envファイルを修正した際に、反映がうまく行われない場合があります。以下コマンドを実行しキャッシュクリアを行っておきましょう。

ターミナル、コマンドプロンプト
php artisan cache:clear
ターミナル、コマンドプロンプト
php artisan config:cache

ジョブとキューについて準備はこれで完了になります。この先の項目で含まれている要素や、含まれていない要素も多く登場している為、理解が難しい箇所があるかもしれませんが、機能の一つとして存在は認識しておきましょう。Laravelの開発に慣れてきた際に、こちらの記事を参考に是非チャレンジしてみてください。

Lesson 4 Chapter 13
ブロードキャスト

chapter13ではブロードキャストについて学習していきます。

はじめに

この項目でもブロードキャストについて実践ではなく主に紹介を行っていきます。理由は先ほどと同様、非動機処理やJavaScriptへの依存も強く、chapter12で学習したキューの要素も含まれている為です。しかし同じようにLaravelの機能の一つとして存在していますので、こちらも認識だけは抑えておきましょう。

ブロードキャストとは

ブロードキャストも非同期での処理が行うことが可能となった機能です。チャット機能を思い浮かべるとイメージが湧きやすいかもしれません。チャット機能はブラウザを更新することなく利用することができますし、仮に更新をしないと新着メッセージが表示されないチャット機能はとても使いづらいことでしょう。ブロードキャストの機能を利用することでチャット機能などの非同期処理を用いた開発を行うことが可能となっています。

ブロードキャストの準備

ブロードキャストの導入についてはPusherとRedisでの導入方法が用意されています。Pusherは外部サービスを利用し場合によっては料金が発生してしまう可能性がある為、今回はRedisでの導入手順を簡単に解説していきます。

1. ライブラリ「Predis」のインストール

Redisでインストールを進める為、composerコマンドでライブラリをインストールします。

ターミナル、コマンドプロンプト
composer require predis/predis

2. ライブラリ「Socket.io」のインストール

非同期通信を目的として準備されているnode.jsライブラリとJavaScriptライブラリのセットになります。

ターミナル、コマンドプロンプト
npm install --save socket.io-client

3. ライブラリ「laravel-echo」のインストール

こちらもJavaScriptライブラリになります。

ターミナル、コマンドプロンプト
npm install --save socket.io-client

4. インストール完了の確認

ここで一度、3つのライブラリのインストールが完了しているか確認を行っていきましょう。

composer.jsonのrequire内に「Predis」が存在するか確認します。

composer.json
"require": {
  // 省略
  "predis/predis": "^2.1"
},

package.jsonのdependencie内に「laravel-echo」と「socket.io」が存在するか確認します。

package.json
"dependencies": {
  "laravel-echo": "^1.15.0",
  "socket.io-client": "^4.6.1"
}

3つのライブラリが無事インストールされていることを確認できました。

5. パッケージ「larval-echo-server」のインストール

larval-echo-serverを準備しておくことで、本来記述する必要のあるJavaScriptのコードを準備することができます。

ターミナル、コマンドプロンプト
sudo npm install -g laravel-echo-server

6. app.phpの修正

BroadcastServiceProviderがデフォルトではコメントアウトされている状態である為、コメントアウトを外します。

config > app.php

app.php
// 省略
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
                      

コメントアウトの解除

app.php
// 省略
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
                      

7. .envの修正

.envファイル内に既に存在していれば「redis」に変更し、存在していなければ下記2行の追加を行いましょう。

.env

BROADCAST_DRIVER=redis
QUEUE_CONNECTION=redis
                      

.envファイルを修正したのでキャッシュクリアを実行します。

ターミナル、コマンドプロンプト
php artisan cache:clear
ターミナル、コマンドプロンプト
php artisan config:cache

8. database.phpの修正

ファイルを開き、prefixの第二引数を空白にします。

config > database.php

database.phpv
'options' => [
  'cluster' => env('REDIS_CLUSTER', 'redis'),
  // 以下修正
  'prefix' => env('REDIS_PREFIX', ''),
],

9. larval-echo-serverの初期化

コマンドを実行し、初期化を実行します。いくつか質問されますが、Enterで進めていくとstart run serverと表示されます。

ターミナル、コマンドプロンプト
laravel-echo-server init
実行結果
appId:
key:
Configuration file saved. Run laravel-echo-server start to run server.

10. larval-echo-serverの起動

laravel-echo-server起動コマンドを実行します。「Server ready!」と表示されれば問題なく起動完了です。

ターミナル、コマンドプロンプト
laravel-echo-server start
実行結果
Server ready!

11. Eventの作成

ブロードキャストで使用するイベントファイルの作成を行います。以下がファイルの作成コマンドになります。

ターミナル、コマンドプロンプト
php artisan make:event TestEvent

TestEvent.phpファイルが作成されていることを確認しておきましょう。

app > Events > TestEvent.php

TestEvent.php
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class TestEvent
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

12. Eventの編集

作成したTestEvent.phpで3ヶ所修正を行います。ShouldBroadcastの継承とチャンネル名の変更、broadcastWithメソッドの追加を行いましょう。

TestEvent.php
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

// ShouldBroadcastの継承
class TestEvent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        // チャンネル名の変更
        return new PrivateChannel('test-channel');
    }

    // broadcastWithメソッドの追加
    public function broadcastWith()
    {
        return [
            'data' => 'testデータです。'
        ];
    }
}

13. ルーティングの設定

イベントをブロードキャストするルーティングの設定を行います。/testにアクセスがあった場合にブロードキャストが実行されます。

TestEvent.php
Route::get('/test', function(){
  event(new \App\Events\TestEvent());
  return 'TestEvent';
});

14. bootstrap.jsの修正

bootstrap.jsでインストールしたライブラリの読み込みを行う為、以下の記述を追加します。

resources > js > bootstrap.js

bootstrap.js

// 省略

import Echo from 'laravel-echo';
import io from 'socket.io-client';
window.io = io;
window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001'
});

以上がブロードキャストを動作するまでの準備となります。

ブロードキャスト実行準備

ブロードキャストの環境が整ったところで続いては実行前準備になります。

1. コンパイルの実行

app.jsを使用する為、コンパイルの実行を行います。

ターミナル、コマンドプロンプト
npm install
ターミナル、コマンドプロンプト
npm run dev

2. laravel-echo-serverの起動

ブロードキャストを動作する前にlaravel-echo-serverを起動します。

ターミナル、コマンドプロンプト
laravel-echo-server start

3. キューの実行

キュー実行のコマンドを入力し、処理を実行します。

ターミナル、コマンドプロンプト
php artisan queue:work

以上がブロードキャスト実行までの準備でした。これまでの項目に比べ準備するライブラリやファイルの記述が多い上に、JavaScriptの要素も多く含んだ項目だった為、イメージの湧きづらい部分でもありますが、プロジェクトによってはブロードキャストを導入するのか、JavaScriptでも開発を行い非同期処理を実現させるのか様々です。Laravelの機能の一つにブロードキャストという機能があるということだけでも認識しておくと良いでしょう。