承接 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.

chr15k/laravel-meilisearch-advanced-query 适用场景与选型建议

chr15k/laravel-meilisearch-advanced-query 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 52.97k 次下载、GitHub Stars 达 6, 最近一次更新时间为 2024 年 11 月 08 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「php」 「search」 「laravel」 「scout」 「meilisearch」 「chr15k」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 chr15k/laravel-meilisearch-advanced-query 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 chr15k/laravel-meilisearch-advanced-query 我们能提供哪些服务?
定制开发 / 二次开发

基于 chr15k/laravel-meilisearch-advanced-query 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

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

GitHub 信息

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

其他信息

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