2
1

More than 1 year has passed since last update.

はじめての記事投稿

【Laravel】#3 商品検索ページが表示されるまでに起きていることを丁寧に追う

Last updated at Posted at 2023-06-15

していること

ララベルで実装されたECサイトのリーディングをしていきたいと思います。
使用するオープンソースのURLはこちらです。
https://github.com/jsdecena/laracom

見た目を確認しておく

商品を検索してみます。
http://localhost:8000/search?q=accusamus
スクリーンショット 2023-06-12 23.54.26.png
accusamusで検索をすると、URLクエリパラメタqにaccusamusが代入され、accusamusを含む商品のみが表示されます。

見えないところで起きていること

  1. ルーティングの定義
  2. リクエストの送信
  3. ルーティングのマッチング
  4. ミドルウェアの処理(今回は関連づけられていません)
  5. コントローラーのアクションの実行
  6. レスポンスの生成と送信

1つ1つ具体的に掘り下げていきます。

1. ルーティングの定義

これは我々が記述するルーティングのことです。web.phpファイルには、ルーティングを定義します。ルーティングは、特定のURLパスに対してどのコントローラーのアクションを実行するかを指定するものです。実際に、このECサイトの商品検索ページについて、このようなルーティングの記述があります。

web.php
Route::get("search", 'ProductController@search')->name('search.product');

ルートパス「search」というgetリクエストがあったとき、'ProductController'の'search'メソッドを実行するよう定義されています。

2.リクエストの送信

クライアント側から特定のURLに対してHTTPリクエストが送信されます。ブラウザから'search'へのGETリクエストが送信されます。

3. ルーティングのマッチング

Laravelのルーターが、送信されたリクエストのルートパスと定義されたルーティングを照合します。ルートパスと一致するルーティングが見つかった場合、そのルートに関連付けられたアクションが実行される準備が整います。

4 ミドルウェアの処理

今回は割愛します。'auth','web'ミドルウェアが適用されているページがあるため、それはその時に言及します。

5. コントローラーのアクションの実行

ルートに関連付けられたコントローラーのアクションが実行されます。
どのようにしてProductControllerを見つけるかを厳密に追います。
そもそもルーティングの定義で触れたルーティングの記述は、このような記述に囲まれています。

web.php
Route::namespace('Front')->group(function () {
...
Route::get("search", 'ProductController@search')->name('search.product');
...
}

一行目に関して、'Front'という指定した名前空間(namespace)内にあるコントローラーに対して、グループ内のすべてのルートが適用されるようになります。ルーティングのグループ化です。

Laravelの名前空間の指定するには、ファイルの先頭に名前空間を指定する必要があります。実際にProductController.phpの先頭にはこのような記述があり、名前空間を宣言しています。

ProductController.php
<?php

namespace App\Http\Controllers\Front;
//以下Frontという名前空間

しかし、Laravelにおいて、名前空間とディレクトリ構成を一致させる必要はありませんが、一致させることが推奨されます。あくまで推奨であり、Laravel側はディレクトリ構成は参照せずに、名前空間から探します。 私はこのことを知らなかったため、名前空間の概念の理解に時間がかかりました。このサンプルコードは名前空間とディレクトリ構成が一致しているため(私が調べた範囲では)、名前空間=ディレクトリ構成という認識で話を進めていきます。なお、名前空間自体の理解に関しては、以下のサイトを参考にしてください。

コントローラーファイルは、大きく3種類に分かれており、Admin(管理社)、Auth(認証に関するもの)、Front(利用者)の3種類に大きく分かれています。
スクリーンショット 2023-06-13 0.26.46.png
名前空間=ディレクトリですので、これら3種類のディレクトリがあります。
スクリーンショット 2023-06-13 0.39.12.png

Laravelでは、コントローラーのクラス名とファイル名を一致させる必要はありませんが、一致させることが推奨されています。 これも同様に知りませんでした。今回実行するのはProductControllerですので、クラス名とファイル名が一致していることを信じて、ProductController.phpを開きます。

ProductController.php
<?php

namespace App\Http\Controllers\Front;

...

class ProductController extends Controller
{
    ...
        ));
    }
}

確かに、Frontという名前空間にあるProductControllerが見つかりました。呼び出されたProductControllerはインスタンス化されます。インスタンス化された時、まず初めに__constrctが呼び出されます。ProductRepositoryInterfaceの実装クラスのインスタンスが注入されます。コンストラクタ??インターフェース??といった疑問が生まれると思うため、一旦飛ばします。次回に詳しく追いたいと思います。とりあえず、ProductControllerのsearchメソッドが実行されるようです。メソッドによって返されるデータとその後の流れだけ追います。今回の記事では雰囲気だけ掴み取ってください。

ProductController.php
public function search()
    {
        // request()はララベルのヘルパ関数
        // 引数を空にすると「Illuminate\Http\Request」のインスタンスが返ってくる
        $list = $this->productRepo->searchProduct(request()->input('q'));

        // map:配列それぞれに関数を通す
        $products = $list->where('status', 1)->map(function (Product $item) {
            // transformProduct関数
            // 商品それぞれの情報をインスタンスにまとめる関数
            return $this->transformProduct($item);
        });

        // 10個ずつ返す
        return view('front.products.product-search', [
            'products' => $this->productRepo->paginateArrayResults($products->all(), 10)
        ]);
    }

$listに検索された商品が代入されます。$productsに$listを色々こねてから代入します。$listを「status=1(削除された商品でないこと)」で絞り込み、map関数で一つ一つの商品を整形をし、返り値をそのまま$productsに代入(アロー関数) をしています。
front.products.product-searchにビューを返します。フロントで表示されるサイトです。それと同時に、productsに諸々整形したデータ($product)を持たせます。これはビューファイルで$productsとして扱えます。ちなみに、paginateArrayResults()はページネーション(10個ずつ表示など)のために用意されたヘルパ関数のはずです。

6. レスポンスの生成と送信

アクションが実行された後、結果としてレスポンスが生成されます。レスポンスは、クライアントに返されるデータやHTTPステータスコードなどの情報を含みます。例えば、アクションがビューを返す場合、そのビューがレンダリングされてHTMLコンテンツとしてレスポンスに含まれます。最終的なレスポンスが生成されたら、それがHTTPレスポンスとしてクライアントに送信されます。クライアントは、受け取ったレスポンスを処理し、表示または別の操作を行います。
ビューはどんなファイルなのでしょうか。コントローラーが返したビューファイルを見てみます。

product-search.blade.php
@extends('layouts.front.app')

@section('content')
    <div class="container">
        <hr>
        <div class="row">
            <div class="category-top col-md-12">
                <h2>Search Results</h2>
            </div>
        </div>
        <hr>
        <div class="col-md-3">
            @include('front.categories.sidebar-category')
        </div>
        <div class="col-md-9">
            <div class="row">
                @include('front.products.product-list', ['products' => $products])
            </div>
        </div>
    </div>
@endsection

共通部分は別のファイルlayouts.front.appに切り分けてあります。これについてもいつか触れるかもしれません。商品の陳列部分だけ、といいたいところですが、商品がforeachやらで回される部分はさらに他のページに切り分けられていますね。front.products.product-listを参照します。(layouts.front.category-sidebar-subの方は一回触れないでおきます)

product-list.blade.php
@if (!empty($products) && !collect($products)->isEmpty())
    <ul class="row text-center list-unstyled">
        @foreach ($products as $product)
            <li class="col-md-3 col-sm-6 col-xs-12 product-list">
               //(中略)
                <h4>{{ $product->name }}</h4>
                //(中略)
            </li>
        @endforeach
        @if ($products instanceof \Illuminate\Contracts\Pagination\LengthAwarePaginator)
            <div class="row">
                <div class="col-md-12">
                    <div class="pull-left">{{ $products->links() }}</div>
                </div>
            </div>
        @endif
    </ul>
@else
    <p class="alert alert-warning">No products yet.</p>
@endif

しっかりforeachが出てきましたね。省略した部分で$product->nameなどといった形で渡された値がやっと使われます。$productsが空っぽだった場合は条件分岐させることによりNo products yet.が表示されるようにしてあるのもバッチリです。

あとがき

ドラーマーク($)のエスケープがうまくいかず、大文字のドラーマークで代用した部分があります。どういう仕様なのかわかりませんが、詳細わかる方教えてください(;;)

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1