glueful/meilisearch
Composer 安装命令:
composer require glueful/meilisearch
包简介
Meilisearch full-text search integration for Glueful Framework
README 文档
README
Full-text search integration for the Glueful Framework using Meilisearch.
Overview
The Meilisearch extension provides seamless integration between Glueful Framework and Meilisearch, an open-source, lightning-fast search engine. This extension enables models to be searchable with minimal configuration while providing advanced search features like typo tolerance, filtering, faceting, and geo-search.
Features
- Searchable trait: Make any model searchable with a simple trait
- Automatic syncing: Keep search index in sync with database changes via model events
- Transaction-safe indexing: Indexing deferred until after database transactions commit
- Fluent query builder: Intuitive API for building complex search queries
- Filters and facets: Full support for Meilisearch filtering and faceted search
- Geo-search: Location-based search with radius and bounding box filters
- Pagination: Built-in pagination with metadata
- Queue support: Optional async indexing via queue workers
- Batch operations: Efficient bulk indexing with configurable batch sizes
- Index prefixing: Multi-tenant friendly with configurable index prefixes
- CLI commands: Index management and debugging tools
Installation
Installation (Recommended)
Install via Composer
composer require glueful/meilisearch
Composer discovers packages of type glueful-extension, but installing does not auto-enable them — the provider must be in config/extensions.php's enabled allow-list. Enable/disable in development (these commands edit config/extensions.php and recompile the cache):
# Enable the extension (adds the provider FQCN to `enabled`) php glueful extensions:enable meilisearch # Disable the extension (removes it from `enabled`) php glueful extensions:disable meilisearch # Preview changes without writing php glueful extensions:enable meilisearch --dry-run # Create a .bak backup before editing php glueful extensions:enable meilisearch --backup
In production, manage the enabled list in config and run php glueful extensions:cache in your deploy step.
Local Development Installation
To develop the extension locally, register it as a Composer path repository in your app's composer.json, then require and enable it:
// composer.json "repositories": [ { "type": "path", "url": "extensions/meilisearch", "options": { "symlink": true } } ]
composer require glueful/meilisearch:@dev php glueful extensions:enable meilisearch
Verify Installation
Check status and details:
php glueful extensions:list php glueful extensions:info meilisearch
Requirements
- PHP 8.3 or higher
- Glueful Framework 1.27.0 or higher
- Meilisearch server (v1.6+ recommended)
- meilisearch/meilisearch-php ^1.6 (installed automatically)
Installing Meilisearch Server
The extension requires a running Meilisearch server. Choose one of the following installation methods:
Docker (Recommended)
docker run -d -p 7700:7700 \ -v $(pwd)/meili_data:/meili_data \ -e MEILI_MASTER_KEY='your-master-key' \ getmeili/meilisearch:v1.6
Homebrew (macOS)
brew install meilisearch
meilisearch --master-key="your-master-key"
Binary Download
Download the latest binary for your platform from the Meilisearch releases page or follow the official installation guide.
# Example for Linux curl -L https://install.meilisearch.com | sh ./meilisearch --master-key="your-master-key"
Meilisearch Cloud
For production, consider Meilisearch Cloud for a fully managed solution.
Configuration
Set the following environment variables in your .env file:
# Meilisearch connection MEILISEARCH_HOST=http://127.0.0.1:7700 MEILISEARCH_KEY=your-master-key # Index prefix (optional, useful for multi-tenant or staging/production separation) MEILISEARCH_PREFIX=myapp_ # HTTP search routes are deny-by-default. Add index names without prefix. MEILISEARCH_ALLOWED_INDEXES=posts,parps,entities # Indexes that do not need a server-side scope filter, such as public documents MEILISEARCH_PUBLIC_INDEXES=posts # Queue configuration (optional, for async indexing) MEILISEARCH_QUEUE=false MEILISEARCH_QUEUE_CONNECTION=redis MEILISEARCH_QUEUE_NAME=search # Batch configuration MEILISEARCH_BATCH_SIZE=500 MEILISEARCH_BATCH_TIMEOUT=30 # Search defaults MEILISEARCH_DEFAULT_LIMIT=20 MEILISEARCH_SOFT_DELETE=true
Configuration File
The extension configuration is located at config/meilisearch.php:
<?php return [ 'host' => env('MEILISEARCH_HOST', 'http://127.0.0.1:7700'), 'key' => env('MEILISEARCH_KEY', null), 'prefix' => env('MEILISEARCH_PREFIX', ''), 'allowed_indexes' => env('MEILISEARCH_ALLOWED_INDEXES', ''), 'http_search' => [ 'allowed_indexes' => env('MEILISEARCH_ALLOWED_INDEXES', ''), 'public_indexes' => env('MEILISEARCH_PUBLIC_INDEXES', ''), 'require_server_filter' => true, 'server_filters' => [ // 'posts' => 'tenant_uuid = "{claims.tenant_uuid}"', ], 'retrievable_attributes' => [ // 'posts' => ['id', 'title', 'excerpt'], ], ], 'queue' => [ 'enabled' => (bool) env('MEILISEARCH_QUEUE', false), 'connection' => env('MEILISEARCH_QUEUE_CONNECTION', null), 'queue' => env('MEILISEARCH_QUEUE_NAME', 'search'), ], 'batch' => [ 'size' => (int) env('MEILISEARCH_BATCH_SIZE', 500), 'timeout' => (int) env('MEILISEARCH_BATCH_TIMEOUT', 30), ], 'soft_delete' => (bool) env('MEILISEARCH_SOFT_DELETE', true), 'search' => [ 'limit' => (int) env('MEILISEARCH_DEFAULT_LIMIT', 20), 'attributes_to_highlight' => ['*'], 'highlight_pre_tag' => '<em>', 'highlight_post_tag' => '</em>', ], ];
Usage
Making Models Searchable
Add the Searchable trait to any model. Implementing SearchableInterface is recommended for static analysis type checking:
By default, the trait indexes only the primary identifier. Override toSearchableArray() to expose fields intentionally; do not return a full model row unless every field is safe for search indexing and retrieval.
<?php namespace App\Models; use Glueful\Database\ORM\Model; use Glueful\Extensions\Meilisearch\Contracts\SearchableInterface; use Glueful\Extensions\Meilisearch\Model\Searchable; class Post extends Model implements SearchableInterface { use Searchable; protected string $table = 'posts'; /** * Customize the data indexed in Meilisearch. */ public function toSearchableArray(): array { return [ 'id' => $this->uuid, 'title' => $this->title, 'body' => $this->body, 'author_name' => $this->author->name ?? null, 'tags' => $this->tags->pluck('name')->toArray(), 'category' => $this->category?->name, 'status' => $this->status, 'published_at' => $this->published_at?->timestamp, ]; } /** * Define filterable attributes. */ public function getSearchableFilterableAttributes(): array { return ['status', 'category', 'tags', 'author_name', 'published_at']; } /** * Define sortable attributes. */ public function getSearchableSortableAttributes(): array { return ['published_at', 'title']; } /** * Define attributes Meilisearch may return to search callers. */ public function getSearchableDisplayedAttributes(): array { return ['id', 'title', 'body', 'author_name', 'tags', 'category', 'status', 'published_at']; } /** * Only index published posts. */ public function shouldBeSearchable(): bool { return $this->status === 'published'; } }
HTTP Search Routes
The request-facing search routes require:
authmeilisearch.searchpermission- route rate limiting
- an index listed in
MEILISEARCH_ALLOWED_INDEXES
Private indexes also require a configured server-side filter. For example:
'http_search' => [ 'allowed_indexes' => ['posts'], 'server_filters' => [ 'posts' => 'tenant_uuid = "{claims.tenant_uuid}"', ], 'retrievable_attributes' => [ 'posts' => ['id', 'title', 'excerpt'], ], ],
Caller-provided filter values are combined with the server-side filter using AND. attributesToRetrieve is rejected unless every requested field is in the configured retrievable list for that index. If the caller omits attributesToRetrieve, the configured retrievable list is used; if none is configured, only id is returned.
Basic Searching
// $context is an ApplicationContext instance // Simple search $results = Post::search($context, 'laravel tutorial')->get(); // Access results foreach ($results as $post) { echo $post->title; } // Get raw hits without model hydration $rawResults = Post::search($context, 'laravel')->raw();
Filtering
// Single filter $results = Post::search($context, 'php') ->where('status', 'published') ->get(); // Multiple filters $results = Post::search($context, 'api') ->where('status', 'published') ->where('published_at', '>=', strtotime('-30 days')) ->whereIn('category', ['tutorials', 'guides']) ->get(); // Using raw filter syntax $results = Post::search($context, 'docker') ->filter('status = "published" AND category IN ["tutorials", "guides"]') ->get();
Sorting
$results = Post::search($context, 'api design') ->orderBy('published_at', 'desc') ->get(); // Multiple sort criteria $results = Post::search($context, '') ->orderBy('category', 'asc') ->orderBy('published_at', 'desc') ->get();
Pagination
$results = Post::search($context, 'docker') ->where('status', 'published') ->paginate(page: 1, perPage: 15); // Access pagination metadata $meta = $results->paginationMeta(); // ['current_page' => 1, 'per_page' => 15, 'total' => 42, 'total_pages' => 3, 'has_more' => true]
Faceted Search
$results = Post::search($context, '') ->facets(['category', 'tags', 'author_name']) ->where('status', 'published') ->get(); // Access facet distribution $categoryFacets = $results->facets('category'); // ['tutorials' => 45, 'guides' => 23, 'news' => 12] // All facets $allFacets = $results->facets();
Geo-Search
For location-based models, add geo data to your searchable array:
class Store extends Model implements SearchableInterface { use Searchable; public function toSearchableArray(): array { return [ 'id' => $this->uuid, 'name' => $this->name, '_geo' => [ 'lat' => (float) $this->latitude, 'lng' => (float) $this->longitude, ], ]; } public function getSearchableFilterableAttributes(): array { return ['_geo', 'category']; } public function getSearchableSortableAttributes(): array { return ['_geo', 'name']; } }
Search by location:
// Find stores within 5km of a point $results = Store::search($context, 'coffee') ->whereGeoRadius(40.7128, -74.0060, 5000) ->get(); // Find within bounding box $results = Store::search($context, '') ->whereGeoBoundingBox([45.0, -73.0], [40.0, -74.0]) ->get(); // Sort by distance (nearest first) $results = Store::search($context, 'coffee') ->orderByGeo(40.7128, -74.0060, 'asc') ->get();
Highlighting
$results = Post::search($context, 'important topic') ->highlight(['title', 'body']) ->get(); // Access highlighted results in raw hits $rawResults = Post::search($context, 'important')->highlight(['title'])->raw(); foreach ($rawResults['hits'] as $hit) { echo $hit['_formatted']['title']; // Contains <em>important</em> }
Manual Indexing
// Index a single model $post = Post::find($context, $uuid); $post->searchableSync(); // Remove from index $post->searchableRemove(); // Batch indexing via BatchIndexer $indexer = app($context, BatchIndexer::class); $posts = Post::query($context)->where('status', 'published')->get(); $indexer->indexMany($posts);
Index Management
use Glueful\Extensions\Meilisearch\Indexing\IndexManager; $manager = app($context, IndexManager::class); // Create index with settings $manager->createIndex('posts'); // Update index settings $manager->updateSettings('posts', [ 'filterableAttributes' => ['status', 'category'], 'sortableAttributes' => ['published_at', 'title'], 'searchableAttributes' => ['title', 'body', 'tags'], ]); // Sync settings from model $manager->syncSettingsForModel(new Post([], $context)); // Get index statistics $stats = $manager->getStats('posts'); // Delete all documents from index $manager->flush('posts'); // Delete the index entirely $manager->deleteIndex('posts');
CLI Commands
Index Models
# Index all records for a model php glueful search:index --model=App\\Models\\Post # Index specific IDs php glueful search:index --model=App\\Models\\Post --id=uuid1,uuid2,uuid3 # Fresh index (clear before indexing) php glueful search:index --model=App\\Models\\Post --fresh
Check Index Status
# Show all indexes php glueful search:status # Show specific index stats php glueful search:status posts # Output as JSON php glueful search:status --json
Sync Index Settings
# Sync settings from model to Meilisearch php glueful search:sync --model=App\\Models\\Post # Dry run (show settings without applying) php glueful search:sync --model=App\\Models\\Post --dry-run
Flush Index
# Flush specific index php glueful search:flush posts # Flush all indexes php glueful search:flush --all # Skip confirmation php glueful search:flush posts --force
Debug Search
# Search an index php glueful search:search posts "search query" # With filters php glueful search:search posts "query" --filter="status = published" # Limit results php glueful search:search posts "query" --limit=5 # Raw JSON output php glueful search:search posts "query" --raw
API Endpoints
All endpoints are prefixed with /api/search and require authentication.
Search
GET /api/search?index={index}&q={query}- Universal searchGET /api/search/{index}?q={query}- Search specific index
Query parameters:
q- Search query (optional, empty returns all)filter- Filter expressionfacets- Attributes for facet distributionsort- Sort criterialimit- Maximum results (default: 20)offset- Pagination offset
Admin
GET /api/search/admin/status- Get all index status (requires admin middleware)
Transaction-Safe Indexing
The extension automatically defers indexing operations until after database transactions commit:
// Using db() helper with transaction() db($context)->transaction(function () use ($context) { $post = Post::create($context, [ 'title' => 'New Post', 'body' => 'Content here...', ]); // Indexing is deferred, not executed yet }); // After commit, the post is indexed // If transaction rolls back, nothing is indexed try { db($context)->transaction(function () use ($context) { $post = Post::create($context, ['title' => 'Will be rolled back']); throw new \Exception('Rollback!'); }); } catch (\Exception $e) { // Transaction rolled back - post is NOT indexed }
Queue Support
Enable queue-based indexing for better performance in production:
MEILISEARCH_QUEUE=true MEILISEARCH_QUEUE_CONNECTION=redis MEILISEARCH_QUEUE_NAME=search
When enabled, indexing operations are dispatched to the queue after transaction commit, ensuring both data consistency and non-blocking request handling.
Run the queue worker:
php glueful queue:work --queue=search
Primary Key Strategy
The extension uses id as the Meilisearch primary key field name for all indexes. The model's actual key (uuid or id) is mapped to this field:
- Models with
uuidproperty:uuidvalue stored asid - Models without
uuid:idvalue stored asid
This ensures consistent behavior across all searchable models and proper document hydration.
Performance Considerations
- Batch size: Configure
MEILISEARCH_BATCH_SIZEfor bulk operations (default: 500) - Queue indexing: Enable for production to avoid blocking requests
- Selective indexing: Use
shouldBeSearchable()to skip irrelevant records - Attribute selection: Define
getSearchableFilterableAttributes()andgetSearchableSortableAttributes()for optimal index settings
Troubleshooting
Common Issues
-
Models not appearing in search: Ensure
shouldBeSearchable()returns true and the model was saved after adding the trait. -
Filters not working: Verify the attribute is listed in
getSearchableFilterableAttributes()and runphp glueful search:sync. -
Sort not working: Verify the attribute is listed in
getSearchableSortableAttributes()and runphp glueful search:sync. -
Connection errors: Check
MEILISEARCH_HOSTandMEILISEARCH_KEYare correct. Verify Meilisearch is running. -
Index not found: The extension auto-creates indexes on first use. If issues persist, manually create with
search:index --fresh.
Debugging
# Check Meilisearch connection and indexes php glueful search:status # Test search directly php glueful search:search posts "test query" --raw # Verify index settings match model php glueful search:sync --model=App\\Models\\Post --dry-run
License
This extension is licensed under the same license as the Glueful Framework.
Support
For issues, feature requests, or questions:
- Create an issue in the repository
- See Meilisearch Documentation for search engine specifics
glueful/meilisearch 适用场景与选型建议
glueful/meilisearch 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 20 次下载、GitHub Stars 达 1, 最近一次更新时间为 2026 年 02 月 04 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「search」 「indexing」 「full-text」 「meilisearch」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 glueful/meilisearch 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 glueful/meilisearch 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 glueful/meilisearch 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
A Laravel package to retrieve data from Google Search Console
FullTextSearchQueryLike is a PHP class which transforms search strings into clever SQL conditions with the LIKE operator
An elastic search adapter for extensible search
Laravel and Lumen ElasticSearch query builder to build complex queries using an elegant syntax
Indexed Search Autocomplete - Extends the TYPO3 Core Extension Indexed_Search searchform with an autocomplete feature.
A SilverStripe module to optimise the Meta, crawling, indexing, and sharing of your website content (forked from Cyber-Duck/Silverstripe-SEO)
统计信息
- 总下载量: 20
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 14
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-02-04