loupe/matcher
Loupe Matcher turns search queries and arbitrary text into precise matches: tokenize phrases and negations, normalize locale-specific spelling variants, decompose compound words, calculate match spans, and format results with highlighting, cropping, and truncation.
<mark> tag — it's word boundaries, diacritics (é vs e), quoted phrases, negated terms (-exclude), stop-word noise, and cropping on token boundaries instead of mid-word. That is several days of fiddly, edge-case-heavy work plus a permanent test-suite liability. Buying it as an MIT dependency is the cheap decision.Formatter accepts pre-calculated matches, highlighting does not force your hand on the engine. You can ship with database LIKE/Scout today, migrate to Meilisearch/Elasticsearch later, and keep one consistent snippet renderer in the UI layer. This de-risks a roadmap decision we would otherwise delay until the search engine is "final."SearchHighlighter service + Blade component used on one high-traffic results page; measure click-through and query refinement rate.cropLength, marker text, and highlight tags per surface via config, not code.ext-intl tokenization and ICU transliteration mean diacritic-insensitive matching comes mostly free in additional locales; validate per market before promising it. Compound-word languages (German, Dutch, Nordic) benefit from the decompounder, whose cost profile improved materially in 0.4.0 (see below).Consider adopting if:
str_ireplace/preg_replace highlighter, which almost certainly mis-highlights on word boundaries and diacritics.Look elsewhere / hold off if:
bench-movies benchmark.ext-intl; confirm it is present in your deployment images before committing. Also confirm PHP-version support against your fleet — check the 0.4.x composer.json rather than assuming continuity from 0.2.x.Adoption gate I would set: pin an exact version in composer.json (0.4.1, not a range), wrap it in a single SnippetFormatter interface, add a snapshot test suite for our top 20 real queries, and re-run our own benchmark against our corpus and locales before scaling — the vendor benchmarks prove the trend, not our latency. Revisit at 1.0. If the API churns, we swap the implementation, not the call sites.
For executives (one paragraph):
Search results currently show raw text with no indication of why a result matched, and descriptions get truncated at arbitrary character counts. This MIT-licensed PHP library gives us Google-style snippets — matched terms highlighted, text cropped to the relevant context, correct across languages and accented characters — as a drop-in component. We can ship it in one to two sprints instead of building and permanently maintaining tokenization and cropping logic ourselves. The trade-off is honest: the library is pre-1.0 and explicitly marked "work in progress," so I recommend we isolate it behind our own interface, pin the version, and run it on one results page first to measure impact before wider rollout. The last two releases were both performance work — roughly 60% cheaper matching and ~17% lower memory in 0.4.0, plus a further speedup for long documents in 0.4.1 — which materially reduces the performance concern we flagged at intake, though we should confirm that on our own data before promising it. No additional licensing cost; the primary investment is engineering time plus a small ongoing upgrade tax.
For engineering (the ask):
loupe/matcheris a three-part pipeline:Tokenizer(ICU word boundaries, quoted phrases, negated terms, locale-aware) →Matcher(stop-word filtering, match spans) →Formatter(configurable highlight tags, crop length, crop/truncation markers, plus a standaloneCropperfor already-highlighted text). Two API details make integration cheap:Formatter::format(..., matches: $existingMatches)lets us skip matching entirely when our search engine already returns hit positions, andFormatterOptions::fromArray()means all tuning (crop length, tags, markers) lives inconfig/rather than code. Proposed work: bind it in a service provider, expose oneSearchHighlightercontract with a null/plain-text fallback implementation, add snapshot tests over real queries, and feature-flag it on a single results page. New in 0.4.1: a performance patch that speeds up match highlighting on long texts (PR #39) — no API change, so it lands behind our existing interface with zero call-site work; bump the pin from 0.4.0 to 0.4.1 and re-run our snapshot suite. Carried over from 0.4.0: the decompounder cache redesign (substring cache → complete-token LRU), reported at 2.45× faster and 14.5 MB lower peak memory on the warmedbench-moviesbenchmark. Risks to track: pre-1.0 API churn (mitigated by the interface + version pin),ext-intlin our images, per-result CPU cost on large result sets (reduced twice now, still nonzero and not uniform across languages — validate with our own dataset; mitigate via snippet caching), and markup safety if source text is HTML.
The one-line decision framing: This is a low-cost, low-risk, high-visible-polish win — 0.4.0 improved the performance baseline and 0.4.1 extends that to the long-text cases we care about most, and neither changes anything about the integration plan — provided we treat it as a replaceable rendering dependency rather than a foundation.
How can I help you explore Laravel packages today?