PHPackages                             ols/php-fts - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Search &amp; Filtering](/categories/search)
4. /
5. ols/php-fts

ActiveLibrary[Search &amp; Filtering](/categories/search)

ols/php-fts
===========

A self-contained full-text search engine in pure PHP. No extensions, no external services, no dependencies. Trigram indexing, BM25+IDF scoring, filters. Ideal for shared hosting and small VPS.

v1.0.0(1mo ago)131153MITPHPPHP &gt;=8.1

Since May 6Pushed 1w ago1 watchersCompare

[ Source](https://github.com/olivier-ls/php-fts)[ Packagist](https://packagist.org/packages/ols/php-fts)[ RSS](/packages/ols-php-fts/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependenciesVersions (2)Used By (0)

php-fts
=======

[](#php-fts)

[![Packagist Version](https://camo.githubusercontent.com/a48de3957a9ef676d89c8acdfd561269ce885025c13c5cda5b2867d35ec85200/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6f6c732f7068702d667473)](https://camo.githubusercontent.com/a48de3957a9ef676d89c8acdfd561269ce885025c13c5cda5b2867d35ec85200/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6f6c732f7068702d667473)[![PHP Version](https://camo.githubusercontent.com/7c29b02678fae9728b610f4c112af37d73dd36fbffc8a8c17ec8367a95e66492/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f6f6c732f7068702d6674732f706870)](https://camo.githubusercontent.com/7c29b02678fae9728b610f4c112af37d73dd36fbffc8a8c17ec8367a95e66492/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f6f6c732f7068702d6674732f706870)[![License](https://camo.githubusercontent.com/f86fd151dc8241c6097a0e07f4ef464349797ab3e2b698a785f4398f54d73c44/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6f6c69766965722d6c732f7068702d667473)](https://camo.githubusercontent.com/f86fd151dc8241c6097a0e07f4ef464349797ab3e2b698a785f4398f54d73c44/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6f6c69766965722d6c732f7068702d667473)[![Downloads](https://camo.githubusercontent.com/c451c821943241b286494a353c08895f259471bdc23c3d5dbc2d829df760f24f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6f6c732f7068702d667473)](https://camo.githubusercontent.com/c451c821943241b286494a353c08895f259471bdc23c3d5dbc2d829df760f24f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6f6c732f7068702d667473)

A self-contained full-text search engine written in pure PHP.
No extensions. No external services. No dependencies. Just files.

---

Who is this for?
----------------

[](#who-is-this-for)

php-fts is designed for projects where deploying a dedicated search service is not an option — shared hosting, small VPS, or simply situations where you want to keep your stack minimal and portable.

If you have access to Elasticsearch, Meilisearch or Typesense and the infrastructure to run them, use those. They are more powerful and built for high-traffic, large-scale workloads.

If you don't — or if you'd rather not — php-fts gives you solid full-text search with ranked results, filters, and tolerant matching, with nothing to install and nothing to configure beyond a directory path.

**It is a good fit if:**

- You are on shared hosting (OVH, Infomaniak, o2switch, etc.)
- You want zero infrastructure overhead
- Your dataset is in the range of hundreds to tens of thousands of documents
- You index offline or on a schedule, and serve searches at runtime

**It is not a good fit if:**

- You need real-time indexing under heavy concurrent write load
- Your dataset is in the millions of documents
- You need geo search or multi-tenant isolation

---

Features
--------

[](#features)

- **Full-text search** with trigram indexing — tolerant to typos and partial matches
- **BM25 + IDF scoring** — industry-standard relevance ranking (same algorithm as Lucene / Elasticsearch)
- **Per-document score** — exposed in results, usable to build facet counts, sorting, or custom ranking
- **Field boosting** — weight some fields (e.g. title) more than others
- **Filters** — exact match, comparisons, range, `in`, `not in`, `contains` on array fields
- **Combined AND / OR filtering** — flexible condition logic
- **Bulk insertion** — up to 12× faster than individual inserts, single lock for the whole batch, crash-safe
- **Soft delete** with tombstones — fast deletes, cleaned up on compaction
- **Atomic update** — soft delete + re-insert in a single lock
- **Compaction** — rebuilds index files cleanly, removes deleted documents and fragmentation
- **Fragmentation monitoring** — know when to compact
- **Binary file storage** — portable across servers, no rebuild needed
- **O(1) trigram lookup** — fixed-size index (~810 KB), no tree traversal
- **No extensions required** — runs on any standard PHP 8.1+ installation

---

Requirements
------------

[](#requirements)

- PHP **8.1** or higher
- Read/write access to a directory for index files

---

Installation
------------

[](#installation)

**Via Composer**

```
composer require ols/php-fts
```

**Manual install** — if you are not using Composer, copy the `src/` directory into your project and include the autoloader:

```
require '/path/to/php-fts/src/autoload.php';
```

---

Quick start
-----------

[](#quick-start)

```
use Ols\PhpFts\SearchEngine;

$engine = new SearchEngine();
$engine->open('./search_data');

// Insert a document
$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'],
]);

// Search
$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();
```

---

API Reference
-------------

[](#api-reference)

### Open / Close

[](#open--close)

```
$engine->open('./search_data');   // Creates directory and files if they don't exist
$engine->close();                 // Flushes and closes all file handles
```

### Insert

[](#insert)

```
// Single document — returns the doc ID (binary offset, keep it if you need update/delete)
$docId = $engine->insert([
    'title'  => 'My product',
    'price'  => 49.90,
    'active' => true,
    'tags'   => ['new', 'sale'],
]);

// Bulk insert — one lock for the entire batch, significantly faster
$docIds = $engine->insertBulk([
    ['title' => 'Product A', 'price' => 29.90],
    ['title' => 'Product B', 'price' => 59.90],
]);
```

Supported field types: `string`, `int`, `float`, `bool`, `array` of strings.

### Search

[](#search)

```
$results = $engine->search(
    query:         'leather shoe',
    limit:         20,
    maxCandidates: 5000,
    boosts:        ['title' => 3.0, 'description' => 1.0],
    filters:       [...],
);
```

Each result:

```
[
    'docId'    => 942222,   // document identifier
    'score'    => 43.74,    // BM25+IDF relevance score, 0-100
    'document' => [...],    // original document array
]
```

The `score` field is available on every result and can be used to build facet counts, custom sorting, or relevance thresholds.

### Filters

[](#filters)

```
$results = $engine->search('shoe', filters: [

    'and' => [
        ['field' => 'active',   'op' => '=',        'value' => true],
        ['field' => 'stock',    'op' => '>',         'value' => 0],
        ['field' => 'price',    'op' => ' Always prefer `insertBulk()` over `insert()` in production: it acquires a single lock for the entire batch and is consistently faster — up to **12×** on local NVMe, up to **51×** on shared Linux hosting. Both are designed for offline or scheduled use; keep them out of critical request paths on high-traffic setups.

### Index size

[](#index-size)

VolumeIndex size1 0002.32 MB5 0008.14 MB10 00018.97 MB20 00036.58 MB### Search

[](#search-1)

VolumeMedian WinMedian LinuxP95 WinP95 LinuxP99 WinP99 Linux1 0003.51 ms2.06 ms8.21 ms4.41 ms8.66 ms6.64 ms5 0004.52 ms2.99 ms22.79 ms8.7 ms23.62 ms16.89 ms10 0005.92 ms4.02 ms41.89 ms15.23 ms44.37 ms33.67 ms20 0007.62 ms4.76 ms62.88 ms19.76 ms106.67 ms21.39 ms> 200 queries, 10 distinct queries in rotation (including typos and out-of-corpus queries).
> Measured with `hrtime()`.

### Compaction

[](#compaction)

VolumeWindowsLinux1 000571.6 ms449.3 ms5 0001.25 s897.9 ms10 0002.12 s1.63 s20 0004.03 s2.68 s> Compaction rewrites the index from scratch — it is an occasional maintenance operation, not a request-time concern.
> Run it when `fragmentationRate()` exceeds your threshold (e.g. 20%).

---

Example application
-------------------

[](#example-application)

The gif below shows one possible use of php-fts — a product search interface with filters and ranked results, built on top of a fake shoe catalogue.

It is just an illustration. php-fts is an engine, not an interface. You can use it to power a product search, a documentation search, an admin filter, a CLI tool, or anything else that needs full-text matching over a set of documents.

To run it locally:

```
php demo/seed.php
php -S localhost:8000 -t demo
```

[![Demo](docs/demo.gif)](docs/demo.gif)

> No database. No external service. The filters, scores, and result counts are all computed by the engine.

---

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance96

Actively maintained with recent releases

Popularity23

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

34d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/247223033?v=4)[olivier-ls](/maintainers/olivier-ls)[@olivier-ls](https://github.com/olivier-ls)

---

Top Contributors

[![olivier-ls](https://avatars.githubusercontent.com/u/247223033?v=4)](https://github.com/olivier-ls "olivier-ls (9 commits)")

---

Tags

searchno-dependenciesbm25full text searchftsshared hostingtrigraminverted-index

### Embed Badge

![Health badge](/badges/ols-php-fts/health.svg)

```
[![Health](https://phpackages.com/badges/ols-php-fts/health.svg)](https://phpackages.com/packages/ols-php-fts)
```

###  Alternatives

[elasticsearch/elasticsearch

PHP Client for Elasticsearch

5.3k184.2M1.0k](/packages/elasticsearch-elasticsearch)[teamtnt/tntsearch

A fully featured full text search engine written in PHP

3.2k3.1M28](/packages/teamtnt-tntsearch)[ruflin/elastica

Elasticsearch Client

2.3k51.7M217](/packages/ruflin-elastica)[solarium/solarium

PHP Solr client

93433.9M111](/packages/solarium-solarium)[devnoiseconsulting/laravel-scout-postgres-tsvector

PostgreSQL Full Text Search Driver for Laravel Scout

59115.8k](/packages/devnoiseconsulting-laravel-scout-postgres-tsvector)[opensearch-project/opensearch-php

PHP Client for OpenSearch

15227.1M101](/packages/opensearch-project-opensearch-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
