Library / SDK
coduo/php-matcher avatar
coduo/php-matcher

PHP Matcher: matching JSON, XML and text against readable patterns

The easiest way to match data structures like JSON/PlainText/XML against readable patterns. Sandbox:

838 stars85 forksPHPMIT

At a glance

What is it?
PHP Matcher is a PHP library for testing API responses against patterns instead of exact fixtures, where a value like @string@ stands for any string and expanders add constraints such as startsWith or isUrl. It exists to simplify functional testing of APIs, and it plugs into PHPUnit through a test case class or an assertions trait.
Who is it for?
PHP Matcher is the right tool for a team whose API tests break every time a timestamp or an identifier changes, because it lets a test assert the shape of a response and the parts that matter without freezing the rest, and the expander vocabulary is wide enough to express most real expectations.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 22 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A pattern language for API assertions

PHP Matcher is described as a library created for testing all kinds of JSON, XML, TXT and scalar values against patterns, and the README states the reason it exists: it was built to simplify functional testing of APIs.

The problem it solves is the brittleness of exact fixture comparison. A response contains a request identifier, a timestamp, a duration and a payload. Asserting the whole body means updating the fixture every time the service changes a field, and the test stops telling you anything about behaviour.

The pattern approach inverts that. A value of @string@ matches any string, so the assertion becomes a statement about the type and shape of the response rather than about its exact content.

The public API is three calls.

php
PHPMatcher::match($value = '{"foo": "bar"}', $pattern = '{"foo": "@string@"}') : bool;
PHPMatcher::backtrace() : Backtrace;
PHPMatcher::error() : ?string;

A boolean result, a backtrace describing where the match went wrong, and an error message. The backtrace is what turns a failed pattern into a readable diagnostic, and it is the part that decides whether the library is pleasant to use in a real suite where the failures have to be understood at a glance.

There is a sandbox hosted at php-matcher.norbert.tech, and the README invites you to play with it first, which is the fastest way to understand how a pattern behaves before you commit one to a test.

Installing it as a dev dependency

The library is installed with Composer as a development dependency.

code
composer require --dev "coduo/php-matcher"

The README links to the Packagist package, and the badges cover the latest stable version, the latest unstable version, the total downloads and the licence.

Version support is worth reading before you pin it. The 6.x branch, which is the default branch, supports PHP 8.3 up to 8.5. The older branches are listed with their own ranges: 5.x and 5.0 and 4.0 require PHP 7.2 up to but not including 8.0, 3.2 and 3.1 require PHP 7.0 up to but not including 8.0, and each of those branches has its own README describing the differences.

That table of branches is a decision you make once. If you are on PHP 8.2 today, the 6.x line is not for you, and the older branches exist to keep the library usable rather than to be maintained in parallel with new features.

One optional dependency is named in the pattern list itself: the expression matcher requires symfony/expression-language, and the README pins the accepted versions as 6.4, 7.4 or 8.0. That dependency is optional, so a project that does not use the expression pattern does not need it.

The pattern vocabulary

The available patterns cover the types you meet in a JSON body.

The scalar types are @string@, @integer@, @number@, @double@, @boolean@, @null@, @uuid@, @ulid@ and @json@. The temporal types are @time@, @date@, @datetime@ and @timezone@, with @tz@ as a shorthand for the last one. There is @array@ and @wildcard@, with @*@ as its short form.

Three array patterns are worth understanding, because they are the ones that let a pattern describe a list rather than a single item. @array_previous@ matches the next array element using the pattern from the previous element. @array_previous_repeat@ matches all remaining array elements using the pattern from the previous one. And @...@ is an unbounded array, which, once used, makes the matcher skip any further array elements.

Two patterns go beyond simple typing. The union syntax @string@||@integer@ matches either a string or an integer. And expr(expression) evaluates an expression, which is the one that needs the optional Symfony expression language dependency.

The examples in the README show the vocabulary in use, and the pattern is the second argument to match, with the value first. A date pattern with a constraint, for instance, is written as a datetime pattern followed by the expander, and the constraint style is consistent across types: @[email protected]('lorem').contains('ipsum').endsWith('dolor') is a single chained pattern.

Expanders, the part that carries the real assertions

The expander list is long, and it is where the library earns its keep, because a bare type check rarely expresses what a test actually wants to say.

The string expanders cover the common shapes: startsWith, endsWith, contains and notContains, each taking an optional ignore case flag. The date and time expanders are isDateTime, isInDateFormat with a format argument, before and after with a date argument, and the timezone ones isTzOffset, isTzIdentifier and isTzAbbreviation.

The value and content expanders are isEmail, isUrl, isIp, isEmpty, isNotEmpty, lowerThan and greaterThan with a boundary, and inArray with a value.

Then come the structural ones. hasProperty takes a property name and works on a JSON value. oneOf takes several expanders and matches when any of them does. matchRegex takes a regular expression. match takes a pattern and applies it to a nested value, which is how you assert on a field inside an embedded JSON string. optional marks a value as allowed to be absent, and the README notes that it works only with the array, JSON and XML matchers. count asserts the length of an array, and works only with the array matcher. repeat takes a pattern and a strictness flag, and matches a repeating array element.

The pattern grammar is therefore compositional: a type, optionally followed by a chain of expanders, with the ability to reach inside a nested document.

That last capability deserves a warning. The more permissive a pattern is, the less it tells you when the API breaks, and a pattern that only asserts types will pass for a response with entirely wrong values. The library gives you the vocabulary to be specific; the discipline to use it is yours.

Two ways to use it in PHPUnit

The library ships two integration paths for PHPUnit, and the choice is between extending a base class and using a trait.

The test case approach replaces your base class with PHPMatcherTestCase.

php
<?php

use Coduo\PHPMatcher\PHPUnit\PHPMatcherTestCase;

class MatcherTest extends PHPMatcherTestCase
{
    public function test_matcher_that_value_matches_pattern()
    {
        $this->assertMatchesPattern('{"name": "@string@"}', '{"name": "Norbert"}');
    }
}

The trait approach keeps your existing base class and mixes in the assertions instead.

php
<?php

use Coduo\PHPMatcher\PHPUnit\PHPMatcherAssertions;
use PHPUnit\Framework\TestCase;

class MatcherTest extends TestCase
{
    use PHPMatcherAssertions;

    public function test_matcher_that_value_matches_pattern()
    {
        $this->assertMatchesPattern('{"name": "@string@"}', '{"name": "Norbert"}');
    }
}

Both expose the same assertion, with the pattern as the first argument and the value as the second. The trait is the less intrusive option when a project already has a shared base test case, because it does not force a change to every test class hierarchy.

For code that is not a PHPUnit test, the direct usage constructs the matcher yourself and inspects the error and the backtrace when the match fails, which is the pattern to use in a functional test harness or a script.

Repository signals and maintenance

The repository layout shows a project with a serious quality setup for a library of its size.

There is a .php-cs-fixer configuration for code style, a phpstan.neon for static analysis, phpunit.xml.dist for the test configuration, an infection.json for mutation testing, and phpbench.json with a benchmark directory for performance measurement. The presence of mutation testing is the notable one, because infection checks whether your tests actually fail when the code is broken, which is the sharpest possible critique of a pattern matching library whose value depends on the quality of the assertions people write.

There is also a .nix directory with a shell.nix, which gives a reproducible development environment, and a tools directory.

For users, the practical details are in CHANGELOG.md and UPGRADE.md, the latter being the file to read when you move between major branches, and CONTRIBUTING.md and CODE_OF_CONDUCT.md cover the process. The licence file is LICENCE, and the project is MIT licensed.

On versions, the tags are 6.0.18 from 2025-12-08, 6.0.17 from 2024-12-20 and 6.0.16 from 2024-10-07, and the last push was on 2026-10-01. A patch level numbering with no feature flags means upgrades are small and mostly about PHP version support, and the older branch readmes in the table are where you find out what changed between the major lines.

Editorial conclusion

PHP Matcher is the right tool for a team whose API tests break every time a timestamp or an identifier changes, because it lets a test assert the shape of a response and the parts that matter without freezing the rest, and the expander vocabulary is wide enough to express most real expectations. It is the wrong choice for a codebase that prefers exact fixture comparisons, since a pattern that is too permissive will pass on a response you meant to pin down, and the wrong choice if you need the expression matcher without an extra dependency. Verify first that your PHP version falls inside the range the 6.x branch supports, which is 8.3 to 8.5, and decide in review whether each pattern is specific enough to fail when the API actually breaks.

Frequently asked questions

What is PHP Matcher used for?

It is a library for testing JSON, XML, TXT and scalar values against patterns, built to simplify functional testing of APIs by asserting the shape of a response instead of matching a fixed fixture.

How do I install PHP Matcher?

Run composer require --dev "coduo/php-matcher". The 6.x branch supports PHP 8.3 to 8.5, while the 5.x, 5.0 and 4.0 branches cover PHP 7.2 to 8.0 and the 3.2 and 3.1 branches cover PHP 7.0 to 8.0, each with its own readme.

How do I use PHP Matcher with PHPUnit?

Either extend the PHPMatcherTestCase class or mix the PHPMatcherAssertions trait into your existing test case. Both give you an assertMatchesPattern call taking the pattern first and the value second.

What does the expr() pattern require?

It is optional and requires symfony/expression-language to be installed, with the README listing accepted versions as 6.4, 7.4 and 8.0.

Official sources

  1. coduo/php-matcher on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/coduo-php-matcher.svg)](https://hysenlabs.com/projects/coduo-php-matcher)

Was this useful?