Loupe is a full-text search engine for PHP apps. It stores its search index in SQLite, so there is no separate search server to run. Yanick Witschi of terminal42 created the package, and it works without a PHP framework.

Loupe sits between basic SQL LIKE searches and a service such as Elasticsearch or Meilisearch. It may fit smaller apps that need better search but do not need a separate server.

The package includes:

  • Typo tolerance so Gucleberry can still find Huckleberry
  • Phrase and negative search using " for exact phrases and - to exclude a keyword
  • SQL-style filters for attributes that you mark as filterable
  • Geo distance filtering and sorting
  • Facets with distribution counts and min/max stats
  • Relevance ranking based on matching terms, typos, proximity, word counts, and exact matches
  • Stemming and language auto-detection, plus compound word splitting for English and German, so brush matches toothbrush

Configure the Search Index

The configuration tells Loupe which fields to search, filter, and sort. You can also choose the primary key.

use Loupe\Loupe\Config\TypoTolerance;
use Loupe\Loupe\Configuration;
use Loupe\Loupe\LoupeFactory;

$configuration = Configuration::create()
    ->withPrimaryKey('uuid')
    ->withSearchableAttributes(['firstname', 'lastname'])
    ->withFilterableAttributes(['departments', 'age'])
    ->withSortableAttributes(['lastname'])
    ->withTypoTolerance(TypoTolerance::create()->withFirstCharTypoCountsDouble(false));

$loupe = (new LoupeFactory())->create('path/to/my_loupe_data_dir', $configuration);

Most settings have defaults. If you leave out withSearchableAttributes(), Loupe indexes every field. Typo tolerance is also on by default.

For tests or short tasks, createInMemory() creates an index that lasts only for the current request.

Documents are plain PHP arrays. Add them to the index with one call:

$loupe->addDocuments([
    ['uuid' => 6, 'firstname' => 'Huckleberry', 'lastname' => 'Finn', 'departments' => ['Backoffice'], 'age' => 18],
]);

Search, Filter, and Sort

Loupe uses a builder for search settings. Its filter strings look much like SQL:

use Loupe\Loupe\SearchParameters;

$searchParameters = SearchParameters::create()
    ->withQuery('Gucleberry')
    ->withAttributesToRetrieve(['uuid', 'firstname'])
    ->withFilter("(departments = 'Backoffice' OR departments = 'Project Management') AND age > 17")
    ->withFacets(['departments', 'age'])
    ->withSort(['lastname:asc']);

$results = $loupe->search($searchParameters);

The result includes the matching records and details about the search. It can also include page totals and facet counts:

[
    'hits' => [
        ['uuid' => 6, 'firstname' => 'Huckleberry'],
    ],
    'query' => 'Gucleberry',
    'processingTimeMs' => 4,
    'hitsPerPage' => 20,
    'page' => 1,
    'totalPages' => 1,
    'totalHits' => 1,
    'facetDistribution' => [
        'departments' => ['Backoffice' => 1],
    ],
    'facetStats' => [
        'age' => ['min' => 18, 'max' => 18],
    ],
]

In this example, the misspelled query Gucleberry still finds Huckleberry. This works because typo tolerance is on by default.

Limits and Performance

Loupe is meant for smaller search indexes. The README gives this warning:

Note that anything above 50k documents is probably not a use case for Loupe.

In the project's benchmark, a typo-tolerant search with relevance ranking takes less than 20 milliseconds. The test uses Meilisearch's set of about 32,000 movies. Results on other systems and data sets may differ.

You can run the tests with composer bench and composer bench-index. The movie data is downloaded the first time a benchmark runs.

Loupe's API is based on Meilisearch. Witschi says this keeps the settings familiar and can make a later move to Meilisearch less difficult if an app outgrows Loupe.

Installation

Loupe needs PHP 8.1 or newer, the pdo_sqlite and mbstring extensions, and SQLite 3.35.0 or newer. SQLite 3.35.0 added the RETURNING feature that Loupe uses for bulk inserts.

composer require loupe/loupe

Loupe uses the MIT license. Visit the loupe-php/loupe repository for the source code, or read the Loupe documentation for more about its schema, ranking, and tokenizer.