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.
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/tntsearchRequirements
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 2Updating 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');Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Open Source Tech Hub
Sharing cutting-edge internet technologies and practical AI resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
