ez-php / search
Full-text search module for ez-php — Meilisearch and Elasticsearch drivers with event-driven index synchronisation
Requires
- php: ^8.5
- ext-curl: *
- ez-php/contracts: ^2.7.0
- ez-php/events: ^2.7.0
Requires (Dev)
- ez-php/console: ^2.7.0
- ez-php/docker: ^2.7.0
- ez-php/testing-application: ^2.7.0
- friendsofphp/php-cs-fixer: ^3.94
- phpstan/phpstan: ^2.1
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^13.0
Suggests
- ez-php/console: Needed for the search:import and search:flush commands
Provides
None
Conflicts
None
Replaces
None
- dev-main
- 2.7.1
- 2.7.0
- 2.6.5
- 2.6.4
- 2.6.3
- 2.6.2
- 2.6.1
- 2.6.0
- 2.5.10
- 2.5.9
- 2.5.8
- 2.5.7
- 2.5.6
- 2.5.5
- 2.5.4
- 2.5.3
- 2.5.2
- 2.5.1
- 2.5.0
- 2.4.11
- 2.4.10
- 2.4.9
- 2.4.8
- 2.4.7
- 2.4.6
- 2.4.5
- 2.4.4
- 2.4.3
- 2.4.2
- 2.4.1
- 2.4.0
- 2.3.9
- 2.3.8
- 2.3.7
- 2.3.6
- 2.3.5
- 2.3.4
- 2.3.3
- 2.3.2
- 2.3.1
- 2.3.0
- 2.2.1
- 2.2.0
- 2.1.1
- 2.1.0
- 2.0.1
- 2.0.0
- 1.14.0
- 1.13.1
- 1.13.0
- 1.12.3
- 1.12.2
- 1.12.1
- 1.12.0
- 1.11.2
- 1.11.1
- 1.11.0
- 1.10.0
- 1.9.2
- 1.9.1
- 1.9.0
- 1.8.0
- 1.7.1
- 1.7.0
- 1.6.1
- 1.6.0
- 1.5.1
- 1.5.0
- 1.4.2
- 1.4.1
- 1.4.0
- 1.3.0
- 1.2.0
- 1.1.1
- 1.1.0
- 1.0.1
- 1.0.0
- 0.9.3
- 0.9.2
- 0.9.1
This package is auto-updated.
Last update: 2026-10-30 15:49:22 UTC
README
Full-text search module for ez-php. Provides a driver-based search abstraction with support for Meilisearch, Elasticsearch, and Typesense, event-driven index synchronisation via ez-php/events, and an in-memory NullDriver for testing.
Installation
composer require ez-php/search
Requirements
- PHP 8.5+
ez-php/contractsez-php/events- A running Meilisearch, Elasticsearch, or Typesense instance (or use
NullDriverfor tests)
Configuration
Add config/search.php to your application:
return [ 'driver' => getenv('SEARCH_DRIVER') ?: 'null', // null | meilisearch | elasticsearch | typesense 'meilisearch' => [ 'host' => getenv('MEILISEARCH_HOST') ?: 'http://meilisearch:7700', 'key' => getenv('MEILISEARCH_KEY') ?: '', ], 'elasticsearch' => [ 'host' => getenv('ELASTICSEARCH_HOST') ?: 'http://elasticsearch:9200', 'user' => getenv('ELASTICSEARCH_USER') ?: '', 'password' => getenv('ELASTICSEARCH_PASSWORD') ?: '', ], 'typesense' => [ 'host' => getenv('TYPESENSE_HOST') ?: 'http://typesense:8108', 'key' => getenv('TYPESENSE_KEY') ?: '', ], ];
Register the provider in provider/modules.php:
\EzPhp\Search\SearchServiceProvider::class,
Making Entities Searchable
Implement SearchableInterface and use the Searchable trait:
use EzPhp\Search\Searchable; use EzPhp\Search\SearchableInterface; class Article implements SearchableInterface { use Searchable; public function __construct( public int $id, public string $title, public string $body, ) {} public function getSearchableKey(): string|int { return $this->id; } // Optional overrides: // public function searchableAs(): string { return 'articles'; } // public function toSearchableArray(): array { return ['id' => $this->id, 'title' => $this->title]; } }
Basic Usage
use EzPhp\Search\SearchIndex; use EzPhp\Search\SearchOptions; $search = $app->make(SearchIndex::class); $search->add($article); $search->remove($article); $result = $search->search('article', 'php tutorial'); foreach ($result->hits as $hit) { echo $hit->id . ': ' . $hit->document['title'] . PHP_EOL; } $result = $search->search( 'article', 'php', (new SearchOptions())->withLimit(10)->withOffset(0)->withFilter('status = "published"'), ); $search->flush('article');
Automatic Index Synchronisation via Events
Wire SyncSearchIndex to your ORM model events in a service provider's boot():
use EzPhp\Events\Event; use EzPhp\Search\Listeners\SyncSearchIndex; use EzPhp\Search\SearchIndex; public function boot(): void { $index = $this->app->make(SearchIndex::class); Event::listen(ArticleSaved::class, new SyncSearchIndex($index)); Event::listen(ArticleDeleted::class, new SyncSearchIndex($index, remove: true)); }
ORM events must implement HasSearchableModel:
use EzPhp\Search\Listeners\HasSearchableModel; use EzPhp\Search\SearchableInterface; class ArticleSaved implements EventInterface, HasSearchableModel { public function __construct(private Article $article) {} public function getSearchableModel(): SearchableInterface { return $this->article; } }
Rebuilding an index (search:import / search:flush)
After a mapping change or on a fresh deploy, re-import documents from the console. The module does not know where your entities live, so register a source — a closure yielding SearchableInterface instances — in one of your providers:
use EzPhp\Search\SearchImportSources; public function boot(): void { $this->app->make(SearchImportSources::class) ->register('articles', fn (): iterable => $this->app->make(ArticleRepository::class)->findAll()); }
php ez search:flush article # drop every document of the index (searchableAs()) php ez search:import articles # index everything the source yields
Each document goes through SearchIndex::add(), so DocumentIndexed fires as on a normal save. Use a generator for large tables to keep memory flat.
Events
| Event | Fired when |
|---|---|
DocumentIndexed |
A document was added or replaced |
DocumentRemoved |
A document was removed |
Drivers
| Driver | Class | Notes |
|---|---|---|
null |
NullDriver |
In-memory, for tests and local development — no external service; all-terms substring match, simple field = value / != filters joined by AND, field:asc|desc sort; not relevance-ranked |
meilisearch |
MeilisearchDriver |
Meilisearch v1.x REST API |
elasticsearch |
ElasticsearchDriver |
Elasticsearch 8.x / OpenSearch REST API |
typesense |
TypesenseDriver |
Typesense REST API; auto-creates collections with wildcard schema |
database |
DatabaseDriver |
The application database — MySQL FULLTEXT (MATCH … AGAINST, boolean mode) or SQLite FTS5; table search.database.table (default search_documents) auto-created; every term required as a prefix, relevance-ordered; filters/sort on stored attributes evaluated in PHP (NullDriver syntax) — for small indexes, no typo tolerance or facets |
Docker
cp .env.example .env
bash start.sh
docker compose exec app composer full