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
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
- Building Advanced Meilisearch Queries in Laravel — a practical walkthrough of the package with real-world examples
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 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 chr15k/laravel-meilisearch-advanced-query 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
A Laravel package to retrieve data from Google Search Console
Indexed Search Autocomplete - Extends the TYPO3 Core Extension Indexed_Search searchform with an autocomplete feature.
Abstraction Layer to index and search entities
Pimcore 10.x Website Indexer (powered by Zend Search Lucene)
Symfony bundle for Elasticsearch integration with round-robin load balancing
A universal search system for App-UI
统计信息
- 总下载量: 52.97k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 6
- 点击次数: 20
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2024-11-08