承接 chr15k/laravel-meilisearch-advanced-query 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

chr15k/laravel-meilisearch-advanced-query

Composer 安装命令:

composer require chr15k/laravel-meilisearch-advanced-query

包简介

A fluent query builder for Meilisearch filter expressions in Laravel.

README 文档

README

Logo for Meilisearch Advanced Query package

GitHub Workflow Status (master) Total Downloads Latest Version License

Meilisearch Advanced Query provides a fluent, expressive API for building Meilisearch filter expressions in Laravel — the same filters you'd otherwise write by hand when customising Scout engine searches.

It handles compound conditions, nested groups, range queries, geo filters, and Meilisearch-specific operators, keeping your code readable as queries grow in complexity.

Warning

v3 contains breaking changes. If you are upgrading from v2, see the upgrade guide.

Requirements

  • PHP 8.3+
  • Laravel 12 or 13
  • Laravel Scout 11+
  • Meilisearch PHP SDK 1.16+

Installation

composer require chr15k/laravel-meilisearch-advanced-query

The service provider is registered automatically via Laravel's package discovery.

Usage

Building a filter string

Use the Query facade (or resolve MeilisearchAdvancedQuery from the container directly) to build a filter string without touching Scout at all:

use Chr15k\MeilisearchAdvancedQuery\Facades\Query;
use Chr15k\MeilisearchAdvancedQuery\Enums\Operator;

$filter = Query::where('status', Operator::EQ, 'active')
    ->whereIn('role', ['admin', 'editor'])
    ->whereBetween('login_count', 10, 500)
    ->compile();

// "status = 'active' AND role IN ['admin', 'editor'] AND login_count 10 TO 500"

Running a Scout search

Chain forModel() on the query builder to hand off to Scout. This returns a Scout Builder instance that you can continue to chain as normal:

use Chr15k\MeilisearchAdvancedQuery\Facades\Query;
use Chr15k\MeilisearchAdvancedQuery\Enums\Operator;
use App\Models\Product;

$results = Query::where('status', Operator::EQ, 'active')
    ->whereIn('category', ['boots', 'shoes'])
    ->forModel(Product::class)
    ->search('leather')
    ->paginate(20);

Sorting

Pass a sort expression (or array of expressions) to search():

Query::where('status', Operator::EQ, 'active')
    ->forModel(Product::class)
    ->search('leather', sort: ['price:asc', 'name:desc']);

For Meilisearch's sort syntax, see the sorting documentation.

The Query Facade

The Query facade proxies to a fresh MeilisearchAdvancedQuery instance on each call, so there is no shared state between requests.

use Chr15k\MeilisearchAdvancedQuery\Facades\Query;

You can also resolve the builder from the container directly if you prefer:

use Chr15k\MeilisearchAdvancedQuery\MeilisearchAdvancedQuery;

$query = app(MeilisearchAdvancedQuery::class);

Builder Methods

where(field, operator, value, boolean)

The primary method. Operator defaults to Operator::EQ. Boolean defaults to AND.

Query::where('name', Operator::EQ, 'Chris')->compile();
// "name = 'Chris'"

Query::where('count', Operator::GTE, 10)->compile();
// "count >= 10"

Query::where('verified', Operator::EQ, true)->compile();
// "verified = true"

orWhere(field, operator, value)

Identical to where() but joins with OR.

Query::where('name', Operator::EQ, 'Chris')
    ->orWhere('name', Operator::EQ, 'Bob')
    ->compile();
// "name = 'Chris' OR name = 'Bob'"

whereNot(field, value) / orWhereNot(field, value)

Negates a field equality check.

Query::whereNot('name', 'Chris')->compile();
// "NOT name = 'Chris'"

Query::where('verified', Operator::EQ, true)
    ->orWhereNot('name', 'Chris')
    ->compile();
// "verified = true OR NOT name = 'Chris'"

whereIn(field, values) / orWhereIn(field, values)

Matches any value in the given array.

Query::whereIn('role', ['admin', 'editor'])->compile();
// "role IN ['admin', 'editor']"

Query::where('verified', Operator::EQ, true)
    ->orWhereIn('role', ['admin', 'editor'])
    ->compile();
// "verified = true OR role IN ['admin', 'editor']"

whereNotIn(field, values) / orWhereNotIn(field, values)

Excludes any value in the given array.

Query::whereNotIn('status', ['banned', 'suspended'])->compile();
// "status NOT IN ['banned', 'suspended']"

whereBetween(field, from, to) / orWhereBetween(field, from, to)

Range filter using Meilisearch's TO operator.

Query::whereBetween('price', 10, 100)->compile();
// "price 10 TO 100"

whereExists(field) / orWhereExists(field)

Matches documents where the field exists.

Query::whereExists('verified_at')->compile();
// "verified_at EXISTS"

whereIsNull(field) / orWhereIsNull(field)

Matches documents where the field is null.

Query::whereIsNull('deleted_at')->compile();
// "deleted_at IS NULL"

whereIsEmpty(field) / orWhereIsEmpty(field)

Matches documents where the field is empty.

Query::whereIsEmpty('tags')->compile();
// "tags IS EMPTY"

whereRaw(query) / orWhereRaw(query)

Passes a raw filter string through the compiler unchanged. Useful for filter expressions the builder does not yet support natively.

Query::whereRaw("name = 'Chris' OR name = 'Bob'")->compile();
// "name = 'Chris' OR name = 'Bob'"

Query::whereRaw("name = 'Chris'")
    ->orWhereRaw("name = 'Bob'")
    ->compile();
// "name = 'Chris' OR name = 'Bob'"

Geo filters

whereGeoRadius(lat, lng, distanceInMeters) / orWhereGeoRadius

Query::where('active', Operator::EQ, true)
    ->whereGeoRadius(48.8566, 2.3522, 1000)
    ->compile();
// "active = true AND _geoRadius(48.8566, 2.3522, 1000)"

whereGeoBoundingBox(lat1, lng1, lat2, lng2) / orWhereGeoBoundingBox

Query::where('active', Operator::EQ, true)
    ->whereGeoBoundingBox(48.8566, 2.3522, 48.9, 2.4)
    ->compile();
// "active = true AND _geoBoundingBox([48.8566, 2.3522], [48.9, 2.4])"

Nested / Grouped Queries

Pass a closure to where() or orWhere() to create a parenthesised group:

Query::where(fn ($q) => $q
    ->where('name', Operator::EQ, 'Chris')
    ->orWhere('name', Operator::EQ, 'Bob')
)
->where('verified', Operator::EQ, true)
->compile();
// "(name = 'Chris' OR name = 'Bob') AND verified = true"

Groups can be nested to any depth:

Query::where(fn ($q) => $q
    ->where('count', Operator::GTE, 10)
    ->where('count', Operator::LTE, 100)
    ->orWhere(fn ($sub) => $sub
        ->where('name', Operator::EQ, 'Chris')
        ->orWhereIsEmpty('name')
        ->orWhereIsNull('email')
    )
)
->orWhere('name', Operator::EQ, 'Bob')
->compile();
// "(count >= 10 AND count <= 100 OR (name = 'Chris' OR name IS EMPTY OR email IS NULL)) OR name = 'Bob'"

Supported Operators

See the Meilisearch filter expression reference for full documentation on each operator.

Enum case Meilisearch syntax
Operator::EQ =
Operator::NEQ !=
Operator::GT >
Operator::GTE >=
Operator::LT <
Operator::LTE <=
Operator::IN IN
Operator::NOT NOT
Operator::BETWEEN TO
Operator::EXISTS EXISTS
Operator::NULL IS NULL
Operator::EMPTY IS EMPTY

Advanced: Using ScoutAdapter Directly

forModel() is a convenience wrapper around ScoutAdapter. If you need more control — for example, to swap in a custom compiler — you can instantiate ScoutAdapter directly:

use Chr15k\MeilisearchAdvancedQuery\Adapters\ScoutAdapter;
use Chr15k\MeilisearchAdvancedQuery\Facades\Query;
use App\Models\Product;

$adapter = ScoutAdapter::for(
    Product::class,
    Query::where('status', Operator::EQ, 'active'),
);

$results = $adapter->search('leather', sort: ['price:asc']);

ScoutAdapter validates at instantiation that the given class exists, is an Eloquent model, and uses the Searchable trait — throwing InvalidArgumentException if any check fails.

Debugging

Call compile() at any point in the chain to get the raw filter string without executing a search:

Query::where(fn ($q) => $q
    ->whereIn('role', ['admin', 'editor'])
    ->orWhereIsEmpty('verified_at')
)
->orWhere('email', Operator::EQ, 'chris@example.com')
->compile();

// "(role IN ['admin', 'editor'] OR verified_at IS EMPTY) OR email = 'chris@example.com'"

Architecture

The package is structured around four concerns:

  • Nodes — immutable, typed value objects representing a single filter clause (ComparisonNode, InNode, NotInNode, BetweenNode, GroupNode, RawNode)
  • Compiler — walks the node tree and produces a Meilisearch filter string (MeilisearchCompiler)
  • Query builder — fluent API that constructs the node tree (MeilisearchAdvancedQuery)
  • Scout adapter — bridges the compiled filter string to a Scout Builder (ScoutAdapter)

The Compiler and Query contracts are independently extensible — you can implement your own compiler (for a different search engine's filter syntax) or your own query builder without touching the rest of the package.

Running Tests

composer test

Individual checks:

composer test:types   # PHPStan static analysis
composer test:lint    # Laravel Pint
composer test:unit    # Pest (with coverage)

Resources

License

MIT — see LICENSE for details.

统计信息

  • 总下载量: 52.97k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 6
  • 点击次数: 18
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 6
  • Watchers: 1
  • Forks: 3
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2024-11-08

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固