0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

laravel学んで2か月で自サービスを開発した話 Part8

Last updated at Posted at 2022-03-07

こんにちは、ブログの更新で転職活動がなかなか進まないワイです。

今までの開発記録はこちらへ
胡蝶蘭を捨てるくらいならワイが欲しいので、サービス開発する編
公式ドキュメントの言う通り、パッケージをインストールされたら、Inertia.jsが導入されて???になった編
マルチログインを作ってみた編
デザインをtailwindcssに丸投げする編
デザイナーに怒られないために、画像をリサイズする編
AWS×リボ払いで破産へGO編
Google MAPに無人島に飛ばされる編

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f323238313934362f30396630376266302d323930352d643666652d366366632d6138373864653736386438342e706e67.png

今回やること

  • 検索機能の作成(ゲスト画面とユーザーのホーム画面に実装)

検索機能の前に、先に定数の設定をしていきます。
なぜなら、検索機能の条件は、アップデートするたびに条件が変わってしまっては困る。
また、条件を数字で管理するよりも、定数で管理したほうが、メンテナンスもしやすいからね。

さきにapp/Constants/common.phpを作成

common.php
<?php

namespace App\Constants;

class Common
{  
    const ORDER_LATER = '0'; //新しい順
    const ORDER_OLDER = '1'; //古い順
    const ORDER_LIKE = '2';   //いいね順
    const ORDER_SELL = '3';   //取引中(取引中を除くソートを作るために使用)

    const ORDER_LIST = [

        'later' => self::ORDER_LATER,
        'older' => self::ORDER_OLDER,
        'like' => self::ORDER_LIKE,
        'sell' => self::ORDER_SELL
    ];

config.phpにcommon.phpを使えるように登録します

config.php
    'aliases' => [
//略
        'Constant'=>App\Constants\common::class,

    ],

準備はできたので、ProductControllerに新しくviewメゾットを作成して書いていきます

ProductController.php
    public function view(Request $request)
    {
        $categories = PrimaryCategory::with('secondary')->get();
        $productInfo =  Product::with('category')
            ->where('status', 1)
            ->orWhere('status', 2)
            ->orderBy('created_at', 'desc')
            ->orwhere('products.secondary_category_id', $request->category);
            if (!is_null($request->keyword)) {
                 $spaceConvert = mb_convert_kana($keyword, 's'); //全角スペースを半角にする
                 $keywords = preg_split('/[\s]+/', $spaceConvert, -1, PREG_SPLIT_NO_EMPTY);
                 foreach ($keywords as $word) {
                    ->orwhere('products.name', 'like', '%' . $word . '%');
            }
            if ($request->sort === null || $request->sort === \Constant::ORDER_LIST['later']) {
            ->where('status', 1)->orWhere('status', 2)->orderBy('created_at', 'desc');
            }
            if ($request->sort === \Constant::ORDER_LIST['older']) {
            ->where('status', 1)->orWhere('status', 2)->orderBy('created_at', 'asc');
            }
            if ($request->sort === \Constant::ORDER_LIST['sell']) {
            ->where('status', 1)->orderBy('created_at', 'desc');
            }
            ->with('category')->paginate(20); 
        return view('user.dashboard', compact('productInfo', 'categories'));
    }

necchusyou_face_boy4.png

えっ、一つのメゾットですよね?長くないですか?

この長いコードをもう一回書くんですよ?容量どれだけ食うんですか?

私みたいな初心者は、コードが足りないことを恐れてしまうため、余分にコードを書きがちですが、このようにコードが長いコントローラーファイルをファットコントローラーといいます。
himan05_man.png

しかし、プロのエンジニアは太ったコードが嫌いで、できる限り少ないコードで書くようにしています

ただし、恋愛ではぽっちゃりのほうが好きな傾向があります

ということで、分離します上のコードは忘れてください!

ProductController.php
    public function view(Request $request)
    {
        $categories = PrimaryCategory::with('secondary')->get();
        $productInfo = Product::availableItems()
            ->selectCategory($request->category ?? '0')
            ->searchKeyword($request->keyword ?? '')
            ->sortOrder($request->sort)->with('category')->paginate(20);
        return view('user.dashboard', compact('productInfo', 'categories'));
    }

このように短くできます

まあ、何が起きたかというと、検索機能の部分をavailableItemsにして、モデルファイルに分離させました
laravelの公式にも

ローカルスコープを使用すると、アプリケーション全体で簡単に再利用できる、共通のクエリ制約を定義できます。たとえば、「人気がある(popular)」と思われるすべてのユーザーを頻繁に取得する必要があるとしましょう。スコープを定義するには、Eloquentモデルメソッドの前にscopeを付けます。

Laravel 8.x Eloquentの準備 と書いていますので、何度も使う機能は使いまわしにしましょう!

ローカルスコープを書く

ということでモデルファイルに行きます

Product.php

 public function scopeAvailableItems($query)
    {
        Product::with('category')
            ->where('status', 1)
            ->orWhere('status', 2)
            ->orderBy('created_at', 'desc');
        return $query;
    }

    public function scopeSortOrder($query, $sortOrder)
    {
        if ($sortOrder === null || $sortOrder === \Constant::ORDER_LIST['later']) {
            return $query->where('status', 1)
                ->orWhere('status', 2)->orderBy('created_at', 'desc');
        }
        if ($sortOrder === \Constant::ORDER_LIST['older']) {
            return $query->where('status', 1)
                ->orWhere('status', 2)->orderBy('created_at', 'asc');
        }
        if ($sortOrder === \Constant::ORDER_LIST['sell']) {
            return $query->where('status', 1)->orderBy('created_at', 'desc');
        }
        return $query;
    }

    public function scopeSelectCategory($query, $categoryId)
    {
        if ($categoryId !== '0') {
            return $query->where('products.secondary_category_id', $categoryId);
        } else {
            return;
        }
    }

    public function scopeSearchKeyword($query, $keyword)
    {
        if (!is_null($keyword)) {
            $spaceConvert = mb_convert_kana($keyword, 's'); //全角スペースを半角にする
            $keywords = preg_split('/[\s]+/', $spaceConvert, -1, PREG_SPLIT_NO_EMPTY);
            foreach ($keywords as $word) {
                $query->where('products.name', 'like', '%' . $word . '%');
            }
            return $query;
        } else {
            return;
        }
    }

引数として、クエリ、入力されたキーワードなどの情報を保持しています。
そして、戻り値にクエリを返して、コントローラーがクエリを読み込むというシステムです。
関数名の前にscopeも忘れずつけておきましょう

view側の処理

さっそく、検索フォームを作りましょう

dashboard.blade.php
<form action="{{ route('user.dashboard') }}" method="get">
        <div class="md:flex md:justify-around mt-20">
            <div class="md:flex items-center">
                <select name="category" class="my-2 md:mr-4 md:my-4">
                    <option value="0" @if (\Request::get('category') === '0') selected @endif>すべての商品</opition>
                        @foreach ($categories as $category)
                            <optgroup label={{ $category->name }}>
                                @foreach ($category->secondary as $secondary)
                                    <option value="{{ $secondary->id }}"
                                        @if (\Request::get('category') == $secondary->id) selected @endif>
                                        {{ $secondary->name }}
                                    </option>
                                @endforeach
                        @endforeach
                </select>
                <div class="flex space-x-2 items-center">
                    <div><input name="keyword"
                            class="w-full my-2 md:my-4 bg-gray-100 bg-opacity-50 rounded border border-black focus:border-indigo-500 focus:bg-white focus:ring-2 focus:ring-indigo-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out sm:my-4"
                            placeholder="キーワードを入力" value="{{ request()->keyword }}"></div>
                    <div><button type="submit"
                            class="flex mx-auto  text-white bg-indigo-500 border-0 py-2 px-4 md:px-4 focus:outline-none hover:bg-indigo-600 rounded text-lg mx-4">検索する</button>
                    </div>
                </div>
            </div>
            <div class="text-sm my-2 sm:my-4">表示順
                <select name="sort" id="sort" class="ml-4">
                    <option value="{{ \Constant::ORDER_LIST['later'] }}"
                        @if (\Request::get('sort') === \Constant::ORDER_LIST['later']) selected @endif>新しい順
                    </option>
                    <option value="{{ \Constant::ORDER_LIST['older'] }}"
                        @if (\Request::get('sort') === \Constant::ORDER_LIST['older']) selected @endif>古い順
                    </option>
                    <option value="{{ \Constant::ORDER_LIST['sell'] }}"
                        @if (\Request::get('sort') === \Constant::ORDER_LIST['sell']) selected @endif>取引中を除く
                    </option>
                </select>
            </div>
    </form>
//略
 {{ $productInfo->appends(request()->query())->links() }}

注意するのが最後のページネーションのところです。
今までの$productInfo->links() では、2ページ目などに移動した際に、検索結果がリセットされてしまいます。
そのため、ページ移動しても、検索結果を保持するためにappendsという関数を使います

結果

FireShot Capture 008 - flower-gift - flower-gift.herokuapp.com.png
ちゃんと、左の検索ボックスと右の並び変えのボックスがついていますね
FireShot Capture 009 - flower-gift - flower-gift.herokuapp.com.png
並び替えも問題がなさそうです!
FireShot Capture 011 - flower-gift - flower-gift.herokuapp.com.png
検索ボックスもちゃんと機能しています。
あとはゲストページにも同じ機能を追加します!

終わりに

このサイトの場合、そこまでクエリ部分を切り分ける必要はないかなと思いますけれど、
大規模なサイトだと複数のクエリを一つにまとめるのはしんどいですね。
ということで、切り分けに挑戦してみてください!

0
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?