Laravel 実装(クエリ・Eloquent)

■EloquentのインスタンスはDIしない。

Eloquentモデルは通常、データベースのレコードに対応し、1つのモデルインスタンスは1つのデータベースレコードを表す。
EloquentモデルをDI(Dependency Injection)することは、そのモデルがデータベースレコードに対応することを強調し、クエリの発行を行うために使用されることを明示する手法とは異なる

■DIすると以下のことが分かりにくくなる
1レコードと紐づくインスタンスなのか、
クエリを発行する為に存在するのか、

dbログ デバッグ
DB::enableQueryLog();
DB::getQueryLog();

^ array:1 [
 0 => array:3 [
  "query" => "select `id` from `posts` where exists (select * from `authors` inner join `author_post` on `authors`.`id` = `author_post`.`author_id` where `posts`.`id` = `author_post`.`post_id` and (authors.name COLLATE utf8mb4_unicode_ci LIKE ? or authors.name_ruby COLLATE utf8mb4_unicode_ci LIKE ?))"
  "bindings" => array:2 [
   0 => "%し%"
   1 => "%し%"
  ]
  "time" => 1.44
 ]
]

whereHasの動き

クエリビルダ

$postAuthorsBuilder = Post::query()->with([
    'authors'
]);
$postAuthorsBuilder->whereHas('authors', function (Builder $builder) use ($keywords) {
    $builder->where(function (Builder $innerBuilder) use ($keywords) {
        foreach ($keywords as $keyword) {
            $innerBuilder->orWhere(function (Builder $orBuilder) use ($keyword) {
                $orBuilder->orWhereRaw('authors.name COLLATE utf8mb4_unicode_ci LIKE ?', ["%$keyword%"]);
                $orBuilder->orWhereRaw('authors.name_ruby COLLATE utf8mb4_unicode_ci LIKE ?', ["%$keyword%"]);
            });
        }
    });
});

SQL

select `id` from `posts`
where exists
(
    select * from `authors`
    inner join `author_post` on `authors`.`id` = `author_post`.`author_id`
    where `posts`.`id` = `author_post`.`post_id`
    and
    (
        (authors.name COLLATE utf8mb4_unicode_ci LIKE ? or authors.name_ruby COLLATE utf8mb4_unicode_ci LIKE ?)
        or
        (authors.name COLLATE utf8mb4_unicode_ci LIKE ? or authors.name_ruby COLLATE utf8mb4_unicode_ci LIKE ?)
    )
)

とあるページ内検索

<?php

declare(strict_types=1);

namespace Oms\Repositories;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\JoinClause;
use Oms\Models\SearchTarget;
use Oms\Repositories\Interfaces\SearchTargetRepositoryInterface;
use Oms\UseCase\Front\Search\FrontSearchRequest;
use Oms\Models\Post;
use Oms\Models\Author;
/**
 * Class SearchTargetRepository
 * @package Oms\Repositories
 */
class SearchTargetRepository implements SearchTargetRepositoryInterface
{
    /** @var SearchTarget */
    protected $orm;

    /**
     * @inheritdoc
     */
    public function __construct(SearchTarget $searchTarget)
    {
        $this->orm = $searchTarget;
    }

    /**
     * @param FrontSearchRequest $request
     *
     * @return Builder
     */
    public function search(FrontSearchRequest $request): Builder
    {
        $builder = $this->orm->newQuery()->with([
                'targetable',
                'targetable.medium',
            ]);

        if ($request->getCategory() === 1) {
            // 1: ニュース, 2: コラム
            $builder->whereIn('category', [1, 2]);
        } elseif ($request->getCategory() !== 0) {
            $builder->where('category', $request->getCategory());
        }
        $keywords = $request->getKeywords();

        // 著者(氏名・ふりがな)を条件にpost_idを取得
        $postAuthorsBuilder = Author::query()->join('author_post', function (JoinClause $join) {
            $join->on('authors.id', '=', 'author_post.author_id');
        });
        $postAuthorsBuilder->where(function (Builder $innerBuilder) use ($keywords) {
            foreach ($keywords as $keyword) {
                $innerBuilder->orWhere(function (Builder $orBuilder) use ($keyword) {
                        $orBuilder->orWhereRaw('authors.name COLLATE utf8mb4_unicode_ci LIKE ?', ["%$keyword%"]);
                        $orBuilder->orWhereRaw('authors.name_ruby COLLATE utf8mb4_unicode_ci LIKE ?', ["%$keyword%"]);
                });
            }
        });
        $author_post_ids = $postAuthorsBuilder->pluck('post_id');

        foreach ($keywords as $keyword) {
            // titleとtextはor検索にしたいのでサブクエリとする
            $builder->where(function (Builder $innerBuilder) use ($keyword, $author_post_ids) {
                $innerBuilder->orWhereRaw('title COLLATE utf8mb4_unicode_ci LIKE ?', ["%$keyword%"]);
                $innerBuilder->orWhereRaw('text COLLATE utf8mb4_unicode_ci LIKE ?', ["%$keyword%"]);
                // 著者もor検索
                $innerBuilder->orWhere(function (Builder $innerSubBuilder) use ($author_post_ids) {
                    $innerSubBuilder->whereIn('targetable_id', $author_post_ids);
                    $innerSubBuilder->where('targetable_type', Post::class);
                });
            });
        }

        if ($from = $request->getFrom()) {
            $builder->where('published_at', '>=', $from);
        }
        if ($to = $request->getTo()) {
            $builder->where('published_at', '<=', $to);
        }

        $builder->orderByDesc('published_at');

        return $builder;
    }
}

照合順序(曖昧検索)とエスケープ

$innerBuilder->orWhereRaw(‘title COLLATE utf8mb4_unicode_ci LIKE ?’, [“%$keyword%”]);

LIKE %%について

%%は意味を持っていないので、全件一致となる。

※%%の間に空白が混ざったりすると空白で検索をかけにいくので、入れる値は注意して

orWhereのパフォーマンス

どちらも同じ動作だが1の方がクエリ生成の観点からは少し効率的であり、可読性も向上するから1.を推奨するとのこと(chatGPT先生)
1.

$builder->where(function (Builder $builder) use ($keyword, $author_post_ids) {
    $builder->orWhereRaw('title COLLATE utf8mb4_unicode_ci LIKE "%' . $keyword . '%"');
    $builder->orWhereRaw('text COLLATE utf8mb4_unicode_ci LIKE "%' . $keyword . '%"');
    // 著者もor検索
    $builder->orWhere(function (Builder $builder) use ($author_post_ids) {
        $builder->whereIn('targetable_id', $author_post_ids);
        $builder->where('targetable_type', 'Oms\Models\Post');
    });
});

2.

$builder->where(function (Builder $builder) use ($keyword, $author_post_ids) {
    $builder->whereRaw('title COLLATE utf8mb4_unicode_ci LIKE "%' . $keyword . '%"');
    $builder->orWhereRaw('text COLLATE utf8mb4_unicode_ci LIKE "%' . $keyword . '%"');
    // 著者もor検索
    $builder->orWhere(function (Builder $builder) use ($author_post_ids) {
        $builder->whereIn('targetable_id', $author_post_ids);
        $builder->where('targetable_type', 'Oms\Models\Post');
    });
});

コメント

タイトルとURLをコピーしました