zendframework/zendsearch
ZendSearch provides full-text search capabilities for PHP apps, offering indexing, querying, and analysis tools inspired by Lucene. Useful for adding fast, flexible search to your project with customizable analyzers, tokenizers, and query parsers.
Installation
Add the package via Composer (though note the package is archived; consider alternatives like elasticsearch/elasticsearch or opensearchphp/client for modern Laravel):
composer require zendframework/zendsearch
Note: If using Laravel, ensure compatibility with PHP 7.4+ via a legacy wrapper or fork.
Basic Usage
Initialize a ZendSearch\Lucene\Search instance:
use ZendSearch\Lucene\Search\QueryParser;
use ZendSearch\Lucene\Search\Query\Query;
$indexPath = storage_path('app/lucene_index');
$search = new \ZendSearch\Lucene\Search($indexPath);
First Use Case: Indexing Add a document to the index:
$doc = new \ZendSearch\Lucene\Document();
$doc->addField(\ZendSearch\Lucene\Field\Text::unchecked('title', 'Laravel Assessment'));
$doc->addField(\ZendSearch\Lucene\Field\Stored('content', 'Full content here...'));
$search->addDocument($doc);
First Query
$query = new QueryParser();
$hits = $search->find($query->parse('laravel'));
foreach ($hits as $hit) {
echo $hit->title; // Access stored fields
}
Indexing Strategy
Artisan::command to bulk-index data (e.g., during php artisan migrate or php artisan queue:work).
public function handle() {
$posts = Post::all();
foreach ($posts as $post) {
$doc = new \ZendSearch\Lucene\Document();
$doc->addField(\ZendSearch\Lucene\Field\Text::unchecked('body', $post->body));
$search->addDocument($doc);
}
}
eloquent.saved events to auto-index models:
Post::saved(function ($post) {
$search->addDocument($this->createDocument($post));
});
Query Patterns
AND, OR, NOT:
$query = new \ZendSearch\Lucene\Search\Query\Boolean();
$query->addSubQuery(new \ZendSearch\Lucene\Search\Query\Term('title', 'Laravel'), 'AND');
$query->addSubQuery(new \ZendSearch\Lucene\Search\Query\Term('content', 'search'), 'AND');
$hits = $search->find($query);
Query::setLimit() and Query::setOffset():
$query->setLimit(10);
$query->setOffset(20);
Integration with Laravel
$this->app->singleton('search', function () {
return new \ZendSearch\Lucene\Search(storage_path('app/lucene_index'));
});
public function scopeLuceneSearch($query, $searchTerm) {
$hits = app('search')->find($searchTerm);
return $query->whereIn('id', array_map(fn($hit) => $hit->id, $hits));
}
Performance
storage/app/lucene_index.Boolean queries) in high-traffic routes. Cache results or use Laravel’s cache()->remember().Field Types
Stored fields are retrievable. Use Field\Stored for data you need post-query:
$doc->addField(\ZendSearch\Lucene\Field\Stored('id', $post->id));
Field\Text for full-text search and Field\Keyword for exact matches.Concurrency
Cache::lock() or Semaphore for concurrent writes:
$lock = Cache::lock('lucene_index_lock', 10);
if ($lock->get()) {
$search->addDocument($doc);
$lock->release();
}
Deprecation
storage/app/lucene_index and re-index. Backup first!var_dump($query->toString()) to inspect Lucene query syntax.$search->getFieldNames(); // List all indexed fields
Custom Analyzers
Override tokenization with a custom ZendSearch\Lucene\Analysis\Analyzer:
$analyzer = new \ZendSearch\Lucene\Analysis\Analyzer\Standard\Standard();
$field = new \ZendSearch\Lucene\Field\Text('custom_field', 'text', $analyzer);
Custom Scoring
Implement ZendSearch\Lucene\Search\Query\Query to modify relevance scoring:
class CustomScoreQuery extends \ZendSearch\Lucene\Search\Query\Query {
public function score($hit) {
return $hit->getFieldValue('boost') * 2; // Custom logic
}
}
Laravel Events Extend with events for indexing lifecycle:
event(new LuceneIndexed($doc, $search));
How can I help you explore Laravel packages today?