LoginSignup
0
2

More than 1 year has passed since last update.

PHP(Laravel) 空白を含む検索

Posted at

はじめに

空白区切りで複数の検索をかけれるようにしたい。

【例】 "Ruby PHP JavaScript" → Ruby PHP JavaScript

条件

mac OS "11.2.3 Big Sur"
Laravel Framework "6.20.27"
PHP "8.0.7"

最終的なコード

ControllerModelに分かれています。
Controllerで使用している関数をModelの方で定義しています。

Controllers/SearchController.php
class SearchController extends Controller
{

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


    public function index(Request $request)
    {
        $query = Recipe::query();

        // エスケープ処理
        $keyword = $this->escape($request->input('keyword'));

        // 空白区切りで文字列を分割
        $keywords = $this->search->pregSplit($keyword);

        // 検索処理
        $recipes = $this->search->searchQuery($query, $keywords);

        return view('search.index', [
            'recipes' => $recipes,
            'keywords' => $keywords,
            'searchCount' => $this->search->searchCount($recipes),
        ]);
    }


    /**
     * 検索欄に文字が入力された際、str_replaceを実行する
     * @param string $value
     * @return string $value
     */
    private function escape(string $value = null)
    {
        if(!$value) {
            return $value;
        }

        return str_replace(
            ['\\', '%', '_'],
            ['\\\\', '\\%', '\\_'],
            $value
        );
    }
}
Models/Search.php
class Search extends Model
{

    /**
     * 空白区切りで検索欄に入力された場合
     * @param string $keyword
     * @return Array
     */
    public function pregSplit($keyword)
    {
        // 全角空白→半角空白
        $keyword = mb_convert_kana( $keyword, "s" );
        return preg_split('/[\p{Z}\p{Cc}]++/u', $keyword, -1, PREG_SPLIT_NO_EMPTY);
    }


    /**
     * 検索処理
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @var string[]|false $keywords
     * @return \Illuminate\Database\Eloquent\Builder $query
     */
    public function searchQuery($query, $keywords = null)
    {
        if(!empty($keywords)) {
            foreach ($keywords as $keyword) {
                $query->where('title', 'like', "%$keyword%")
                    ->orWhere('cook_time', 'like', "%$keyword%")
                    ->orWhere('ingredients', 'like', "%$keyword%")
                    ->orWhereHas('tags', function($query) use ($keyword) {
                        $query->where('name', 'like', "%$keyword%" );
                    })
                    ->orWhereHas('mealType', function($query) use ($keyword) {
                        $query->where('name', 'like', "%$keyword%" );
                    })
                    ->orWhereHas('mealClass', function($query) use ($keyword) {
                        $query->where('name', 'like', "%$keyword%" );
                    });
            }
        }
        return $query->get()->sortByDesc('created_at')
        ->load('user', 'stocks', 'tags', 'mealType', 'mealClass');
    }


    /**
     * 検索ヒット数カウント
     * @param mixed $recipes
     * @return int
     */
    public function searchCount($recipes)
    {
        return $recipes->count();
    }
}

各コードの流れ

Controllers/SearchController.php
class SearchController extends Controller
{

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


    public function index(Request $request)
    {
        $query = Recipe::query();

        // ① エスケープ処理
        $keyword = $this->escape($request->input('keyword'));

        // ② 空白区切りで文字列を分割
        $keywords = $this->search->pregSplit($keyword);

        // ③ 検索処理
        $recipes = $this->search->searchQuery($query, $keywords);

        return view('search.index', [
            'recipes' => $recipes,
            'keywords' => $keywords,
            'searchCount' => $this->search->searchCount($recipes),
        ]);
    }


    /**
     * 検索欄に文字が入力された際、str_replaceを実行する
     * @param string $value
     * @return string $value
     */
    private function escape(string $value = null)
    {
        if(!$value) {
            return $value;
        }

        return str_replace(
            ['\\', '%', '_'],
            ['\\\\', '\\%', '\\_'],
            $value
        );
    }
}

① エスケープ処理

① のescape()は下の方に定義してあるように、文字の中に怪しい記号が入っていないかを調べています。
もし指定している記号\\, %, _があれば\\\\, \\%, \\_と置き換えてくれています。

② 空白区切りで文字列を分割

$keywords = $this->search->pregSplit($keyword);


// pregSplit()の内容
    /**
     * 空白区切りで検索欄に入力された場合
     * @param string $keyword
     * @return Array
     */
    public function pregSplit($keyword)
    {
        // 全角空白→半角空白
        $keyword = mb_convert_kana( $keyword, "s" );
        return preg_split('/[\p{Z}\p{Cc}]++/u', $keyword, -1, PREG_SPLIT_NO_EMPTY);
    }

まず、空白区切りが全角であれば半角にしてあげる。

$keyword = mb_convert_kana( $keyword, "s" );

全て半角区切りにした次に、空白ごとに文字を分割してその値を返す。

preg_split('/[\p{Z}\p{Cc}]++/u', $keyword, -1, PREG_SPLIT_NO_EMPTY);

③ 検索処理

空白処理を行い、単語のみとなったため最後に検索をかけてヒットしたものを表示。

$recipes = $this->search->searchQuery($query, $keywords);


// 検索処理
    public function searchQuery($query, $keywords = null)
    {
        if(!empty($keywords)) {
            foreach ($keywords as $keyword) {
                $query->where('title', 'like', "%$keyword%")
                    ->orWhere('cook_time', 'like', "%$keyword%")
                    ->orWhere('ingredients', 'like', "%$keyword%")
                    ->orWhereHas('tags', function($query) use ($keyword) {
                        $query->where('name', 'like', "%$keyword%" );
                    })
                    ->orWhereHas('mealType', function($query) use ($keyword) {
                        $query->where('name', 'like', "%$keyword%" );
                    })
                    ->orWhereHas('mealClass', function($query) use ($keyword) {
                        $query->where('name', 'like', "%$keyword%" );
                    });
            }
        }
        return $query->get()->sortByDesc('created_at')
        ->load('user', 'stocks', 'tags', 'mealType', 'mealClass');
    }

おわり

正規表現あたりが今だに慣れないので、少しずつ慣れていきたい。

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