やりたかっとこと
LaravelのEloquent Builderで、LIKE検索を以下のように記述できるクラスを作った。
UserController.php
$words = ['山田', '岐阜市'];
$comulns = ['name', 'company.address'];
User::whereLike($columns, $words)->get(); // 岐阜市で働く山田さんを取得
これをいろんなプロジェクトで使いたいので、パッケージ化してcomposer require
で利用できるようにしたい。
結果
ここにパッケージをリリースして、
以下コマンドで利用可能に。
composer require kazuki/laravel-where-like
パッケージのロジック
Laravel標準機能のMixinを利用して、Illuminate\Database\Eloquent\Builder
クラスにwhereLike
メソッドを追加しています。
※Mixinは\Illuminate\Support\Traits\Macroable.php
トレイトを利用しているクラスに、メソッドを後づけできる機能です。
標準機能とはいえ、既存クラスの仕様を外部パッケージが変更するのはよろしく無いかもしれません。その是非はここでは一旦おいておきます。
全体の流れ
- GitHubリモートレポジトリを作成
- Composerパッケージを作成
- GitHubにリリース
- PackagistにSubmit
すべて以下のサイトに記載されています。この記事では、2.でMixinを有効にできた手順を紹介します。
ディレクトリ構成
composer init
で作成されたディレクトリに、ソースを追加&composer.jsonの変更をしていきます。
/
├─ .git
│ └─ ...
├─ src
│ ├─ BuilderMixin.php // 追加
│ └─ WhereLikeServiceProvider.php // 追加
├─ vendor
│ └─ ...
├─ composer.json // 編集
├─ composer.lock
├─ LICENSE
└─ README.md
src
BuilderMixin.php(メソッドの実装)
BuilderMixin.php
<?php
namespace Kazuki\LaravelWhereLike;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
class BuilderMixin
{
public function whereLike()
{
/**
* あいまい検索
*
* @param array|string $attributes 検索対象とするカラム名。リレーション先のカラム名を指定する場合は、テーブル名とカラム名を`.`で連結する(複数リレーション連結可能)。
* @param array|string $keywords 検索値。配列の場合は、各検索値をor条件で連結する。
* @param int $position `-1` => 前方一致 `1` => 後方一致 `0` => 部分一致(デフォルトは`0`)
* @param string $boolean `and`か`or`を指定。デフォルトは`and`。
* @return \Illuminate\Database\Eloquent\Builder
*/
return function (array|string $attributes, array|string $keywords, int $position = 0, $boolean = 'and') {
// 検索値が配列の場合、再帰呼び出し。
// 検索値同士は、or検索で連結する。
if (is_array($keywords)) {
/** @var Builder $this */
return $this->where(function (Builder $query) use ($attributes, $keywords, $position) {
foreach ($keywords as $keyword) {
$query->whereLike($attributes, $keyword, $position, 'or');
}
}, boolean: $boolean); // and・or 条件
}
// SQLインジェクション対策
$keywords = addcslashes($keywords, '\_%');
// 検索位置
$condition = [
1 => "{$keywords}%",
-1 => "%{$keywords}",
][$position] ?? "%{$keywords}%";
// カラム名が単体(文字列)で与えられた場合、配列に変換
if (! is_array($attributes)) {
$attributes = [$attributes];
}
/** @var Builder $this */
return $this->where(function (Builder $query) use ($attributes, $condition) {
foreach (Arr::wrap($attributes) as $attribute) {
$query->when(
// $attributeが、'.'(リレーション)を含むか確認
! ($attribute instanceof \Illuminate\Contracts\Database\Query\Expression) &&
str_contains((string) $attribute, '.'),
// '.'(リレーション)を含む場合
function (Builder $query) use ($attribute, $condition) {
// $attributeを'.'でリレーションとカラム名に分割
$attrs = explode('.', (string) $attribute);
$relatedAttribute = array_pop($attrs);
$relation = implode('.', $attrs);
$query->orWhereRelation($relation, $relatedAttribute, 'LIKE', $condition);
},
// '.'(リレーション)を含まない場合
function (Builder $query) use ($attribute, $condition) {
$query->orWhere($attribute, 'LIKE', $condition);
}
);
}
}, boolean: $boolean); // and・or 条件
};
}
public function orWhereLike()
{
/**
* あいまい検索(or)
*
* @param array|string $attributes 検索対象とするカラム名。リレーション先のカラム名を指定する場合は、テーブル名とカラム名を`.`で連結する。
* @param array|string $keywords 検索値
* @param int $position `-1` => 前方一致 `1` => 後方一致 `0` => 部分一致(デフォルトは`0`)
* @return \Illuminate\Database\Eloquent\Builder
*/
return function (array|string $attributes, array|string $keywords, int $position = 0) {
/** @var Builder $this */
return $this->whereLike($attributes, $keywords, $position, 'or');
};
}
}
ServiceProviderの継承クラスを作成して、boot
メソッド内でBuilder
クラスにメソッドを追加します。
WhereLikeServiceProvider.php
<?php
namespace Kazuki\LaravelWhereLike;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Eloquent\Builder;
class WhereLikeServiceProvider extends ServiceProvider
{
public function boot()
{
Builder::mixin(new BuilderMixin()); // Builderにメソッドを追加
}
}
Composer.json
上の参考記事で作成したファイルに依存関係とServicePoroviderを追記。
composer.json
{
"name": "kazuki/laravel-where-like",
"description": "Add where like query to laravel eloquent builder.",
"type": "library",
"license": "MIT",
"autoload": {
"psr-4": {
"Kazuki\\LaravelWhereLike\\": "src/"
}
},
"authors": [
{
"name": "oor30",
"email": "kazuki.w.0920@gmail.com"
}
],
"minimum-stability": "stable",
// 以下を追記(※実際にJSONにコメントを記載してはいけません)
"require": {
"php": ">=8.0",
"illuminate/database": "^9.0 || ^10.0"
},
"extra": {
"laravel": {
"providers": [
"Kazuki\\LaravelWhereLike\\WhereLikeServiceProvider"
]
}
}
}