How to Implement Powerful Full‑Text Search in PHP with TNTSearch

This guide explains how to install, configure, and use the PHP‑based TNTSearch engine, covering its key features, required dependencies, index creation, various search modes, dynamic updates, custom tokenizers, geo‑search, and text classification with practical code examples.

Open Source Tech Hub
Open Source Tech Hub
Open Source Tech Hub
How to Implement Powerful Full‑Text Search in PHP with TNTSearch

Overview

TNTSearch is a fully PHP‑written full‑text search (FTS) engine that can be added to a project within minutes through simple configuration.

Features

Fuzzy search

Instant search

Geo search

Text classification

Stem extraction

Custom tokenizers

BM25 ranking algorithm

Boolean search

Result highlighting

Dynamic index updates without full re‑indexing

Easy deployment via Packagist.org

The package includes helper functions such as Jaro‑Winkler and cosine similarity for distance calculations and supports stemming for multiple languages, with optional Snowball stemmers and even Chinese support in some branches.

Installation

The simplest way to install TNTSearch is via Composer:

composer require teamtnt/tntsearch

Requirements

Before proceeding, ensure the server meets the following requirements:

PHP >= 7.1

PDO PHP extension

SQLite PHP extension

mbstring PHP extension

Example: Creating an Index

Creating the Index

To perform full‑text queries you must first create an index:

use TeamTNT\TNTSearch\TNTSearch;

$tnt = new TNTSearch;

$tnt->loadConfig([
    'driver'   => 'mysql',
    'host'     => 'localhost',
    'database' => 'dbname',
    'username' => 'user',
    'password' => 'pass',
    'storage'  => '/var/www/tntsearch/examples/',
    'stemmer'  => \TeamTNT\TNTSearch\Stemmer\PorterStemmer::class // optional
]);

$indexer = $tnt->createIndex('name.index');
$indexer->query('SELECT id, article FROM articles;');
// $indexer->setLanguage('german');
$indexer->run();
Important: The storage setting defines the folder where all index files are saved; ensure the folder is writable to avoid a [PDOException] SQLSTATE[HY000] [14] unable to open database file error.
If your primary key is not id , set it explicitly:
$indexer->setPrimaryKey('article_id');

Making the Primary Key Searchable

By default the primary key is not searchable. Enable it with:

$indexer->includePrimaryKey();

Search

Basic keyword search is straightforward:

use TeamTNT\TNTSearch\TNTSearch;

$tnt = new TNTSearch;
$tnt->loadConfig($config);
$tnt->selectIndex("name.index");

$res = $tnt->search("This is a test search", 12);
print_r($res); // returns an array of up to 12 matching document IDs
// To display results, query the application database:
// SELECT * FROM articles WHERE id IN $res ORDER BY FIELD(id, $res);

The ORDER BY FIELD clause is crucial for preserving the relevance order.

Boolean Search

use TeamTNT\TNTSearch\TNTSearch;

$tnt = new TNTSearch;
$tnt->loadConfig($config);
$tnt->selectIndex("name.index");

// Documents containing "romeo" but not "juliet"
$res = $tnt->searchBoolean("romeo -juliet");

// Documents containing "romeo" or "hamlet"
$res = $tnt->searchBoolean("romeo or hamlet");

// Documents matching (romeo AND juliet) OR (prince AND hamlet)
$res = $tnt->searchBoolean("(romeo juliet) or (prince hamlet)");

Fuzzy Search

Adjust fuzziness via the following public members:

public $fuzzy_prefix_length = 2;
public $fuzzy_max_expansions = 50;
public $fuzzy_distance = 2; // Levenshtein distance
use TeamTNT\TNTSearch\TNTSearch;

$tnt = new TNTSearch;
$tnt->loadConfig($config);
$tnt->selectIndex("name.index");
$tnt->fuzziness(true);
$res = $tnt->search("juleit"); // "juleit" matches "juliet" with default distance 2

Updating the Index

After creating an index, you can modify the underlying documents without re‑indexing:

use TeamTNT\TNTSearch\TNTSearch;

$tnt = new TNTSearch;
$tnt->loadConfig($config);
$tnt->selectIndex("name.index");

$index = $tnt->getIndex();

// Insert a new document
$index->insert([
    'id'      => '11',
    'title'   => 'new title',
    'article' => 'new article'
]);

// Update an existing document
$index->update(11, [
    'id'      => '11',
    'title'   => 'updated title',
    'article' => 'updated article'
]);

// Delete a document
$index->delete(12);

Custom Tokenizer

Create a tokenizer class that extends AbstractTokenizer and implements TokenizerInterface:

use TeamTNT\TNTSearch\Support\AbstractTokenizer;
use TeamTNT\TNTSearch\Support\TokenizerInterface;

class SomeTokenizer extends AbstractTokenizer implements TokenizerInterface {
    protected static $pattern = '/[\s,\.]+/';

    public function tokenize($text) {
        return preg_split($this->getPattern(), strtolower($text), -1, PREG_SPLIT_NO_EMPTY);
    }
}

Register the tokenizer via setTokenizer on the indexer:

$someTokenizer = new SomeTokenizer;
$indexer = new TNTIndexer;
$indexer->setTokenizer($someTokenizer);

Or pass it through configuration:

use TeamTNT\TNTSearch\TNTSearch;

$tnt = new TNTSearch;
$tnt->loadConfig([
    'driver'   => 'mysql',
    'host'     => 'localhost',
    'database' => 'dbname',
    'username' => 'user',
    'password' => 'pass',
    'storage'  => '/var/www/tntsearch/examples/',
    'stemmer'  => \TeamTNT\TNTSearch\Stemmer\PorterStemmer::class,
    'tokenizer'=> \TeamTNT\TNTSearch\Support\SomeTokenizer::class
]);
$indexer = $tnt->createIndex('name.index');
$indexer->query('SELECT id, article FROM articles;');
$indexer->run();

Geo Search

Index

$candyShopIndexer = new TNTGeoIndexer;
$candyShopIndexer->loadConfig($config);
$candyShopIndexer->createIndex('candyShops.index');
$candyShopIndexer->query('SELECT id, longitude, latitude FROM candy_shops;');
$candyShopIndexer->run();

Search

$currentLocation = [
    'longitude' => 11.576124,
    'latitude'  => 48.137154
];
$distance = 2; // kilometers
$candyShopIndex = new TNTGeoSearch();
$candyShopIndex->loadConfig($config);
$candyShopIndex->selectIndex('candyShops.index');
$candyShops = $candyShopIndex->findNearest($currentLocation, $distance, 10);

Classification

use TeamTNT\TNTSearch\Classifier\TNTClassifier;

$classifier = new TNTClassifier();
$classifier->learn("A great game", "Sports");
$classifier->learn("The election was over", "Not sports");
$classifier->learn("Very clean match", "Sports");
$classifier->learn("A clean but forgettable game", "Sports");

$guess = $classifier->predict("It was a close election");
var_dump($guess['label']); // returns "Not sports"

Saving and Loading a Classifier

$classifier->save('sports.cls');

$classifier = new TNTClassifier();
$classifier->load('sports.cls');
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

search engineFull‑Text SearchTokenizergeo-searchtntsearch
Open Source Tech Hub
Written by

Open Source Tech Hub

Sharing cutting-edge internet technologies and practical AI resources.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.