Appearance
Search
Site search ranked by relevance, on the database or a search engine.
kitloom/search · admins: kitloom/search-filament, kitloom/search-nova
Site search for a kitloom site on Laravel Scout: an index of the posts a visitor may find, kept in step with the content, searched on the site's database by default — or on Meilisearch, Typesense or Algolia by configuration. The site's search page lists what the index finds, best matches first. The admin page is kitloom/search-filament or kitloom/search-nova.
Better than WordPress's search, how
WordPress (site's own search) | The index |
|---|---|
Every word somewhere in the stored HTML: href, class or a shortcode's parameter is a match | Plain text: no markup, no shortcode tags (the text a shortcode encloses stays), no scripts, styles or block comments |
| Title, excerpt and content | Also the names of the post's published terms in public taxonomies |
| Title matches first, then the newest | Each word found adds the weight of where it was found (title 5, terms 3, text 1 by default), the whole phrase adds them once more; then the newest |
The words are read the WordPress way (SearchTerms): words, "quoted phrases", -word to leave out, the site's stopwords dropped; every word must be found.
The index
Table search_documents (the site's prefix in front), model Kitloom\Search\Models\SearchDocument — Scout's Searchable lives here, so the post models are left as they are. One row per post that is published, has no password and is of a searchable post type; post_id is the post's ID, the row's id is the same on a site in one language.
- The content events keep it in step:
PostSaved(publishing, trashing and restoring are saves too) andPostDeleted, read once the transaction is committed, so the termsPostWriterassigns after the save are in the row.TermSaved,TermTrashed,TermRestoredandTermDeletedre-read the term's posts on the queue (SyncTermPosts, after commit). php artisan search:reindexrebuilds it whole (--queue— on the queue); so does "Rebuild index" in the admin (RebuildSearchIndex, queued). A rebuild writes every searchable post and drops the rows of anything else; the old rows answer searches meanwhile.- Until the index has been built once, the site keeps WordPress's search: a half-filled index would hide what the site has. Run
search:reindexafter installing. - Password-protected posts are not in the index at all: the site does not show them to visitors in search either, and the index answers everyone alike.
Languages
On a site in more than one language (kitloom/languages, or any module that binds the site's Localization) a post has a row in every language, lang its slug and the row's id 12-fr: the title, the text and the term names as the site shows them in that language — the published translation, and the original where a field has none. The search page reads the rows of the language it is shown in, so /fr/?s=journal finds a post by its French title, and a post without a translation is found by its original in every language, once. The index reads the texts through the language module (Localization::showingIn()) and knows nothing of how it stores translations; a translation saved or removed saves its post (PostSaved), and the row follows. A site in one language keeps one row per post, lang empty.
The index follows the site's languages by itself (IndexLanguages). A rebuild writes down the languages it was built in (search_languages, the default first); when a search finds the site's languages are no longer those, it puts one catch-up on the queue (CatchUpSearchLanguages) and logs a line at info saying why. The catch-up writes the rows of an added language and drops those of a removed one, leaving the others' rows as they are; a new default language rebuilds the index whole, since the default's rows hold the originals. Until it has run, a search in a language the index does not hold whole is the site's own, which finds translations too; the other languages are searched in the index as before. A cache key per set of languages keeps it to one job however many visitors search meanwhile (a lost or failed job is tried again a day later). On a site without a queue worker (QUEUE_CONNECTION=database and nobody running queue:work) the job waits on the queue, and the search stays the site's own in the new language until it is taken — or run search:reindex. On the sync queue it runs once the response has been sent, so the visitor who searched does not wait for it; a site too large to index a language within PHP's time limit needs a queue worker.
The search page
SearchResults is the site's SearchProvider: on a search page the main query asks it for one page of post IDs, best first, and the total, and runs no search of its own. The page is still the main query's — its address, page size and links, published posts only, no protected ones for guests; a page past the last one is not found, an empty first page is still the search page. While the module is off or the index was never built, the provider says so and the site searches itself.
Engines
kitloom-search.engine (KITLOOM_SEARCH_ENGINE) | What |
|---|---|
database (default) | WeightedDatabaseEngine, Scout's database engine ranked by the weights of Search → Settings; LIKE on MySQL, MariaDB, PostgreSQL (ILIKE) and SQLite. Nothing else to run |
meilisearch, typesense, algolia | Scout's engines; install their client (composer suggest) and configure them in config/scout.php. The module fills scout.meilisearch.index-settings, scout.algolia.index-settings and scout.typesense.model-settings for its model unless the application did (filterable post_type and lang, the fields from the most important); php artisan scout:sync-index-settings --driver=… sends them. They rank by their own rules: the weights apply to database only |
collection | Scout's engine that filters in PHP: for tests and tiny sites |
The engine is the module's own setting: Scout's driver stays the application's for its models. kitloom-search.index names the index in an engine of its own.
Settings
Options, written from Search → Settings through SettingWriter (declared by SearchOptions):
| Option | What |
|---|---|
search_enabled | The search page asks the index (default on) |
search_post_types | JSON list; empty — the public post types not excluded from search |
search_weight_title, search_weight_terms, search_weight_content | 0–10; 0 — not searched (defaults 5, 3, 1) |
search_rebuilt_at, search_rebuild_queued_at, search_languages | Written by the index (protected) |
AI agents
With kitloom/ai installed, an agent whose user has manage_options reads how the index is doing (search_status: on, built, how many posts, which types, the last rebuild) and puts "Rebuild index" on the queue (search_reindex). The settings above it changes with kitloom/ai's update_setting, as any option. kitloom-search.ai.register false — no search tools.
Tests
From the application that installs the package: php vendor/bin/phpunit --testsuite Search.