PHPackages                             loupe/matcher - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. loupe/matcher

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

loupe/matcher
=============

Highlight and crop around search terms

0.2.1(4mo ago)10123.6k↓38.2%1[2 PRs](https://github.com/loupe-php/matcher/pulls)1MITPHPPHP ^8.1

Since May 8Pushed 3mo ago1 watchersCompare

[ Source](https://github.com/loupe-php/matcher)[ Packagist](https://packagist.org/packages/loupe/matcher)[ GitHub Sponsors](https://github.com/Toflar)[ RSS](/packages/loupe-matcher/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (4)Dependencies (4)Versions (7)Used By (1)

Loupe Matcher
=============

[](#loupe-matcher)

A PHP library for search term highlighting and text snippet generation. Transform search results into user-friendly formatted text with highlighted matches and contextual cropping.

> Lorem ipsum dolor sit amet, consetetur \[...\] no sea takimata sanctus est lorem est ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur \[...\] dolore te feugait nulla facilisi lorem ipsum dolor sit amet, consectetuer \[...\]

Caution

Work in progress. Expect frequent changes to API and functionality.

Installation
------------

[](#installation)

```
composer require loupe/matcher
```

Quick Start
-----------

[](#quick-start)

Here's a simple example of how to use Loupe Matcher to highlight search terms in a text document and crop around the highlights:

```
use Loupe\Matcher\Tokenizer\Tokenizer;
use Loupe\Matcher\Matcher;
use Loupe\Matcher\Formatter;
use Loupe\Matcher\FormatterOptions;

$tokenizer = new Tokenizer();
$matcher = new Matcher($tokenizer);
$formatter = new Formatter($matcher);

$options = (new FormatterOptions())
    ->withEnableHighlight()
    ->withEnableCrop()
    ->withCropLength(10);

$result = $formatter->format(
    'This is a long document with many words to search through and compare.',
    'search words',
    $options
);

// "...with many words to search through..."
echo $result->getFormattedText();
```

Core Components
---------------

[](#core-components)

### Tokenizer

[](#tokenizer)

**Purpose:** Breaks text into searchable tokens (words, phrases, terms) for accurate matching.

The `Tokenizer` converts strings into `TokenCollection` objects, handling:

- Word boundaries using `ext-intl` rules
- Phrase groups (quoted terms like `"exact phrase"`)
- Negated terms (prefixed with `-`)
- Locale-specific tokenization

```
$tokenizer = new Tokenizer('en_US'); // Optional locale
$tokens = $tokenizer->tokenize('search for "exact phrase" -exclude');

$tokens->all();          // All tokens
$tokens->phraseGroups(); // Quoted phrases only
$tokens->allNegated();   // Terms to exclude
```

### Matcher

[](#matcher)

**Purpose:** Finds which tokens in your text match the search query.

The `Matcher` compares tokenized text against search terms, with support for:

- Stop word filtering (ignore common words like "the", "and")
- Match span calculation (start/end positions)
- Flexible matching between token collections

```
$matcher = new Matcher($tokenizer, ['the', 'and', 'or']); // Stop words
$matches = $matcher->calculateMatches('Text to search', 'search query');

// Get position information for highlighting
$spans = $matcher->calculateMatchSpans('Text to search', 'query', $matches);
foreach ($spans as $span) {
    echo "Match at position {$span->getStartPosition()}-{$span->getEndPosition()}";
}
```

### Formatter

[](#formatter)

**Purpose:** Combines matching and highlighting to create formatted output with context.

The `Formatter` orchestrates the entire process:

- Highlights matched terms with HTML tags
- Crops text to show relevant context around matches
- Configurable through `FormatterOptions`

```
$formatter = new Formatter($matcher);

$options = (new FormatterOptions())
    ->withEnableHighlight()
    ->withHighlightStartTag('')
    ->withHighlightEndTag('')
    ->withEnableCrop()
    ->withCropLength(150)
    ->withCropMarker('...');

$result = $formatter->format($text, $query, $options);
echo $result->getFormattedText();
```

Advanced Usage
--------------

[](#advanced-usage)

### Custom Tokenizer

[](#custom-tokenizer)

Implement `TokenizerInterface` for specialized tokenization:

```
class CustomTokenizer implements TokenizerInterface {
    public function tokenize(string $text): TokenCollection {
        // Your custom tokenization logic
    }

    public function matches(Token $token, TokenCollection $tokens): bool {
        // Your custom logic for checking if a token is a match
    }
}
```

### Pre-highlighted Text Cropping

[](#pre-highlighted-text-cropping)

When you already have highlighted text that needs cropping:

```
$cropper = new \Loupe\Matcher\Formatting\Cropper(
    cropLength: 50,
    cropMarker: '…',
    highlightStartTag: '',
    highlightEndTag: ''
);

// "...text with highlighted terms."
echo $cropper->cropHighlightedText('Long text with highlighted terms.');
```

### Using Pre-calculated Matches

[](#using-pre-calculated-matches)

When you already have a `TokenCollection` of matches (e.g., from a previous search operation or external source), you can format text directly without re-calculating matches. This approach is useful when your search engine already provides match information or you want to cache match results for performance.

```
// Assume you already have matches from somewhere else
$existingMatches = new TokenCollection(/* ... */);

// Set up the tokenizer, matcher, and formatter as usual
$tokenizer = new Tokenizer();
$matcher = new Matcher($tokenizer);
$formatter = new Formatter($matcher);
$options = (new FormatterOptions())
    ->withEnableHighlight()
    ->withEnableCrop()
    ->withCropLength(100);

// Format using the existing matches - no duplicate processing
$result = $formatter->format($text, $query, $options, matches: $existingMatches);
echo $result->getFormattedText();
```

###  Health Score

46

—

FairBetter than 93% of packages

Maintenance79

Regular maintenance activity

Popularity40

Moderate usage in the ecosystem

Community17

Small or concentrated contributor base

Maturity40

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 81.3% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~79 days

Total

4

Last Release

138d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/481937?v=4)[Yanick Witschi](/maintainers/Toflar)[@Toflar](https://github.com/Toflar)

![](https://www.gravatar.com/avatar/31ac2b3787ded290b6bac87b937abf4f267483e4da64731bfb256a942bb669ca?d=identicon)[daun](/maintainers/daun)

---

Top Contributors

[![Toflar](https://avatars.githubusercontent.com/u/481937?v=4)](https://github.com/Toflar "Toflar (13 commits)")[![daun](https://avatars.githubusercontent.com/u/22225348?v=4)](https://github.com/daun "daun (2 commits)")[![lemmon](https://avatars.githubusercontent.com/u/251591?v=4)](https://github.com/lemmon "lemmon (1 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleECS

Type Coverage Yes

### Embed Badge

![Health badge](/badges/loupe-matcher/health.svg)

```
[![Health](https://phpackages.com/badges/loupe-matcher/health.svg)](https://phpackages.com/packages/loupe-matcher)
```

###  Alternatives

[friendsofsymfony/comment-bundle

This Bundle provides threaded comment functionality for Symfony applications

460751.2k5](/packages/friendsofsymfony-comment-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
