PHP-fts is a self-contained full-text search engine written entirely in PHP 8.1+, with no extensions, no external services, and no dependencies beyond the filesystem. It uses trigram indexing, BM25+IDF scoring, and inverted indexes to deliver search over tens of thousands of documents in milliseconds — all from a single Composer package.
What it does
PHP-fts indexes documents (arrays of strings, ints, floats, bools, and string arrays) into a set of flat files on disk. At search time, it breaks the query into trigrams, looks them up in a fixed-size index (~810 KB), and ranks results using the same BM25+IDF algorithm that powers Lucene and Elasticsearch. The engine supports field boosting, combined AND/OR filters, range comparisons, array containment checks, and soft deletes with compaction.
Who it's for
The author explicitly positions php-fts as a replacement for dedicated search services only when those aren't feasible — shared hosting (OVH, Infomaniak, o2switch), small VPS, or any environment where you want zero infrastructure overhead. If you have Elasticsearch, Meilisearch, or Typesense and the infrastructure to run them, use those. PHP-fts is for datasets in the hundreds to tens of thousands of documents, indexed offline or on a schedule.
Performance
Benchmarks on a shared Linux hosting plan (PHP 8.3) show:
- Insertion: 10,000 documents in ~63 seconds (single insert) or ~29 seconds (bulk insert). Bulk insert is consistently ~2× faster because it acquires a single lock for the entire batch.
- Index size: ~21.7 MB for 10,000 documents; ~106 MB for 50,000.
- Search: median 3.2 ms, P95 12.5 ms, P99 22.9 ms on 10,000 documents with 200 queries (including typos and out-of-corpus terms).
How to use it
Install via Composer: composer require ols/php-fts. Then:
use Ols\PhpFts\SearchEngine;
$engine = new SearchEngine();
$engine->open('./search_data');
$docId = $engine->insert([
'title' => 'Brown leather shoe',
'description' => 'Elegant city shoe in soft leather',
'price' => 129.90,
'stock' => 42,
'active' => true,
'category' => 'Shoes',
'brand' => 'Adidas',
'tags' => ['summer', 'luxury', 'city'],
]);
$results = $engine->search('leather shoe', limit: 20, boosts: ['title' => 3.0, 'description' => 1.0]);
foreach ($results as $result) {
echo $result['document']['title'] . ' — score: ' . $result['score'] . PHP_EOL;
}
$engine->close();
Filters support and/or groups with operators =, !=, >, >=, <, <=, in, not in, contains, not contains. Updates are atomic (soft delete + re-insert in a single lock). Compaction rebuilds index files and removes deleted documents.
Tradeoffs
PHP-fts is not suitable for real-time indexing under heavy concurrent write load, datasets in the millions of documents, geo search, or multi-tenant isolation. It also does not support real-time indexing at request time — insertion is an offline operation best run via a scheduled job.
Bottom line
PHP-fts fills a narrow but real gap: full-text search on shared hosting or minimal infrastructure where installing Elasticsearch or Meilisearch is impossible or impractical. It delivers solid relevance ranking, flexible filtering, and predictable performance for datasets up to tens of thousands of documents — all without a single extension or external service.