coduo/php-matcher

PHP Matcher enables you to match values with patterns

Package info

github.com/coduo/php-matcher

pkg:composer/coduo/php-matcher

Fund package maintenance!

norberttech

flow-php.com/sponsor

Statistics

Installs: 13 112 915

Dependents: 47

Suggesters: 0

Stars: 843

Open Issues: 6

6.0.18 2025-12-08 16:26 UTC

Requires

Requires (Dev)

Suggests

Provides

None

Conflicts

None

Replaces

None

MIT c399b55955ce3186dba356b05571c5ca6cfa02c1

  • Michał Dąbrowski <dabrowski.woop@brillante.pl>
  • Norbert Orzechowicz <norbert.woop@orzechowicz.pl>

jsonmatchertestsMatch

This package is auto-updated.

Last update: 2026-06-02 10:05:52 UTC


README

👁 Type Coverage

Library created for testing all kinds of JSON/XML/TXT/Scalar values against patterns.

API:

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

It was built to simplify API's functional testing.

👁 Latest Stable Version
👁 Total Downloads
👁 Latest Unstable Version
👁 License

Sandbox

Feel free to play first with Sandbox

Installation

Require new dev dependency using composer:

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

Basic usage

Direct PHPMatcher usage

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();
$match = $matcher->match("lorem ipsum dolor", "@string@");

if (!$match) {
 echo "Error: " . $matcher->error();
 echo "Backtrace: \n";
 echo (string) $matcher->backtrace();
}

PHPUnit extending PHPMatcherTestCase

<?php

use Coduo\PHPMatcher\PHPUnit\PHPMatcherTestCase;

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

PHPUnit using PHPMatcherAssertions trait

<?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"}');
 }
}

Available patterns

  • @string@
  • @integer@
  • @number@
  • @double@
  • @boolean@
  • @time@
  • @date@
  • @datetime@
  • @timezone@ || @tz
  • @array@
  • @array_previous@ - match next array element using pattern from previous element
  • @array_previous_repeat@ - match all remaining array elements using pattern from previous element
  • @...@ - unbounded array, once used matcher will skip any further array elements
  • @null@
  • @*@ || @wildcard@
  • expr(expression) - optional, requires symfony/expression-language: ^6.4 || ^7.4 || ^8.0 to be present
  • @uuid@
  • @ulid@
  • @json@
  • @string@||@integer@ - string OR integer

Available pattern expanders

  • startsWith($stringBeginning, $ignoreCase = false)
  • endsWith($stringEnding, $ignoreCase = false)
  • contains($string, $ignoreCase = false)
  • notContains($string, $ignoreCase = false)
  • isDateTime()
  • isInDateFormat($format) - example "@datetime@.isInDateFormat('Y-m-d H:i:s')
  • before(string $date) - example "@string@.isDateTime().before(\"2020-01-01 00:00:00\")"
  • after(string $date) - example "@string@.isDateTime().after(\"2020-01-01 00:00:00\")"
  • isTzOffset()
  • isTzIdentifier()
  • isTzAbbreviation()
  • isEmail()
  • isUrl()
  • isIp()
  • isEmpty()
  • isNotEmpty()
  • lowerThan($boundry)
  • greaterThan($boundry)
  • inArray($value) - example "@array@.inArray(\"ROLE_USER\")"
  • hasProperty($propertyName) - example "@json@.hasProperty(\"property_name\")"
  • oneOf(...$expanders) - example "@string@.oneOf(contains('foo'), contains('bar'), contains('baz'))"
  • matchRegex($regex) - example "@string@.matchRegex('/^lorem.+/')"
  • optional() - work's only with ArrayMatcher, JsonMatcher and XmlMatcher
  • count() - work's only with ArrayMatcher - example "@array@.count(5)"
  • repeat($pattern, $isStrict = true) - example '@array@.repeat({"name": "foe"})' or "@array@.repeat('@string@')"
  • match($pattern) - example {"image":"@json@.match({\"url\":\"@string@.isUrl()\"})"}

Example usage

Scalar matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match(1, 1);
$matcher->match('string', 'string');

String matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match('Norbert', '@string@');
$matcher->match("lorem ipsum dolor", "@string@.startsWith('lorem').contains('ipsum').endsWith('dolor')");

Time matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match('00:00:00', '@time@');
$matcher->match('00:01:00.000000', '@time@');
$matcher->match('00:01:00', '@time@.after("00:00:00")');
$matcher->match('00:00:00', '@time@.before("01:00:00")');

Date matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match('2014-08-19', '@date@');
$matcher->match('2020-01-11', '@date@');
$matcher->match('2014-08-19', '@date@.before("2016-08-19")');
$matcher->match('2014-08-19', '@date@.before("today").after("+ 100year")');

DateTime matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match('2014-08-19', '@datetime@');
$matcher->match('2020-01-11 00:00:00', '@datetime@');
$matcher->match('2014-08-19', '@datetime@.before("2016-08-19")');
$matcher->match('2014-08-19', '@datetime@.before("today").after("+ 100year")');

TimeZone matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match('Europe/Warsaw', '@timezone@');
$matcher->match('Europe/Warsaw', '@tz@');
$matcher->match('GMT', '@tz@');
$matcher->match('01:00', '@tz@');
$matcher->match('01:00', '@tz@.isTzOffset()');
$matcher->match('GMT', '@tz@.isTzAbbreviation()');
$matcher->match('Europe/Warsaw', '@tz@.isTzIdentifier()');

Integer matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match(100, '@integer@');
$matcher->match(100, '@integer@.lowerThan(200).greaterThan(10)');

Number matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match(100, '@number@');
$matcher->match('200', '@number@');
$matcher->match(1.25, '@number@');
$matcher->match('1.25', '@number@');
$matcher->match(0b10100111001, '@number@');

Double matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match(10.1, "@double@");
$matcher->match(10.1, "@double@.lowerThan(50.12).greaterThan(10)");

Boolean matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match(true, "@boolean@");
$matcher->match(false, "@boolean@");

Wildcard matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match("@integer@", "@*@");
$matcher->match("foobar", "@*@");
$matcher->match(true, "@*@");
$matcher->match(6.66, "@*@");
$matcher->match(array("bar"), "@wildcard@");
$matcher->match(new \stdClass, "@wildcard@");

Expression matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match(new \DateTime('2014-04-01'), "expr(value.format('Y-m-d') == '2014-04-01'");
$matcher->match("Norbert", "expr(value === 'Norbert')");

UUID matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match('9f4db639-0e87-4367-9beb-d64e3f42ae18', '@uuid@');

ULID matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match('01BX5ZZKBKACTAV9WEVGEMMVS0', '@ulid@');

Array matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match(
 array(
 'users' => array(
 array(
 'id' => 1,
 'firstName' => 'Norbert',
 'lastName' => 'Orzechowicz',
 'roles' => array('ROLE_USER'),
 'position' => 'Developer',
 ),
 array(
 'id' => 2,
 'firstName' => 'Michał',
 'lastName' => 'Dąbrowski',
 'roles' => array('ROLE_USER')
 ),
 array(
 'id' => 3,
 'firstName' => 'Johnny',
 'lastName' => 'DąbrowsBravoki',
 'roles' => array('ROLE_HANDSOME_GUY')
 )
 ),
 true,
 6.66
 ),
 array(
 'users' => array(
 array(
 'id' => '@integer@.greaterThan(0)',
 'firstName' => '@string@',
 'lastName' => 'Orzechowicz',
 'roles' => '@array@',
 'position' => '@string@.optional()'
 ),
 array(
 'id' => '@integer@',
 'firstName' => '@string@',
 'lastName' => 'Dąbrowski',
 'roles' => '@array@'
 ),
 '@...@'
 ),
 '@boolean@',
 '@double@'
 )
);

Array Previous

@array_previous@ can also be used when matching JSON's and XML's

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match(
 array(
 'users' => array(
 array(
 'id' => 1,
 'firstName' => 'Norbert',
 'lastName' => 'Orzechowicz',
 'roles' => array('ROLE_USER'),
 'position' => 'Developer',
 ),
 array(
 'id' => 2,
 'firstName' => 'Michał',
 'lastName' => 'Dąbrowski',
 'roles' => array('ROLE_USER')
 ),
 array(
 'id' => 3,
 'firstName' => 'Johnny',
 'lastName' => 'DąbrowsBravoki',
 'roles' => array('ROLE_HANDSOME_GUY')
 )
 ),
 true,
 6.66
 ),
 array(
 'users' => array(
 array(
 'id' => '@integer@.greaterThan(0)',
 'firstName' => '@string@',
 'lastName' => 'Orzechowicz',
 'roles' => '@array@',
 'position' => '@string@.optional()'
 ),
 '@array_previous@',
 '@array_previous@'
 ),
 '@boolean@',
 '@double@'
 )
);

Array Previous Repeat

@array_previous_repeat@ can also be used when matching JSON's and XML's

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match(
 array(
 'users' => array(
 array(
 'id' => 1,
 'firstName' => 'Norbert',
 'lastName' => 'Orzechowicz',
 'roles' => array('ROLE_USER'),
 'position' => 'Developer',
 ),
 array(
 'id' => 2,
 'firstName' => 'Michał',
 'lastName' => 'Dąbrowski',
 'roles' => array('ROLE_USER')
 ),
 array(
 'id' => 3,
 'firstName' => 'Johnny',
 'lastName' => 'DąbrowsBravoki',
 'roles' => array('ROLE_HANDSOME_GUY')
 )
 ),
 true,
 6.66
 ),
 array(
 'users' => array(
 array(
 'id' => '@integer@.greaterThan(0)',
 'firstName' => '@string@',
 'lastName' => 'Orzechowicz',
 'roles' => '@array@',
 'position' => '@string@.optional()'
 ),
 '@array_previous_repeat@'
 ),
 '@boolean@',
 '@double@'
 )
);

Json matching

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match(
 '{
 "users":[
 {
 "firstName": "Norbert",
 "lastName": "Orzechowicz",
 "created": "2014-01-01",
 "roles":["ROLE_USER", "ROLE_DEVELOPER"]
 }
 ]
 }',
 '{
 "users":[
 {
 "firstName": "@string@",
 "lastName": "@string@",
 "created": "@string@.isDateTime()",
 "roles": "@array@",
 "position": "@string@.optional()"
 }
 ]
 }'
);

Json matching with unbounded arrays and objects

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match(
 '{
 "users":[
 {
 "firstName": "Norbert",
 "lastName": "Orzechowicz",
 "created": "2014-01-01",
 "roles":["ROLE_USER", "ROLE_DEVELOPER"],
 "attributes": {
 "isAdmin": false,
 "dateOfBirth": null,
 "hasEmailVerified": true
 },
 "avatar": {
 "url": "http://avatar-image.com/avatar.png"
 }
 },
 {
 "firstName": "Michał",
 "lastName": "Dąbrowski",
 "created": "2014-01-01",
 "roles":["ROLE_USER", "ROLE_DEVELOPER", "ROLE_ADMIN"],
 "attributes": {
 "isAdmin": true,
 "dateOfBirth": null,
 "hasEmailVerified": true
 },
 "avatar": null
 }
 ]
 }',
 '{
 "users":[
 {
 "firstName": "@string@",
 "lastName": "@string@",
 "created": "@string@.isDateTime()",
 "roles": [
 "ROLE_USER",
 "@...@"
 ],
 "attributes": {
 "isAdmin": @boolean@,
 "@*@": "@*@"
 },
 "avatar": "@json@.match({\"url\":\"@string@.isUrl()\"})"
 }
 ,
 @...@
 ]
 }'
);

Xml matching

Optional - requires openlss/lib-array2xml: ^1.0 to be present.

<?php

use Coduo\PHPMatcher\PHPMatcher;

$matcher = new PHPMatcher();

$matcher->match(<<<XML
<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">

<soap:Body xmlns:m="http://www.example.org/stock">
 <m:GetStockPrice>
 <m:StockName>IBM</m:StockName>
 <m:StockValue>Any Value</m:StockValue>
 </m:GetStockPrice>
</soap:Body>

</soap:Envelope>
XML
 ,
 <<<XML
<?xml version="1.0"?>
<soap:Envelope
 xmlns:soap="@string@"
 soap:encodingStyle="@string@">

<soap:Body xmlns:m="@string@">
 <m:GetStockPrice>
 <m:StockName>@string@</m:StockName>
 <m:StockValue>@string@</m:StockValue>
 <m:StockQty>@integer@.optional()</m:StockQty>
 </m:GetStockPrice>
</soap:Body>

</soap:Envelope>
XML
 );

Example scenario for api in behat using mongo.

@profile, @user
Feature: Listing user toys

 As a user
 I want to list my toys

 Background:
 Given I send and accept JSON

 Scenario: Listing toys
 Given the following users exist:
 | firstName | lastName |
 | Chuck | Norris |

 And the following toys user "Chuck Norris" exist:
 | name |
 | Barbie |
 | GI Joe |
 | Optimus Prime |

 When I set valid authorization code oauth header for user "Chuck Norris"
 And I send a GET request on "/api/toys"
 Then the response status code should be 200
 And the JSON response should match:
 """
 [
 {
 "id": "@string@",
 "name": "Barbie",
 "_links: "@*@"
 },
 {
 "id": "@string@",
 "name": "GI Joe",
 "_links": "@*@"
 },
 {
 "id": "@string@",
 "name": "Optimus Prime",
 "_links": "@*@"
 }
 ]
 """

PHPUnit integration

The assertMatchesPattern() is a handy assertion that matches values in PHPUnit tests. To use it either include the Coduo\PHPMatcher\PHPUnit\PHPMatcherAssertions trait, or extend the Coduo\PHPMatcher\PHPUnit\PHPMatcherTestCase:

namespace Coduo\PHPMatcher\Tests\PHPUnit;

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

class PHPMatcherAssertionsTest extends TestCase
{
 use PHPMatcherAssertions;

 public function test_it_asserts_if_a_value_matches_the_pattern()
 {
 $this->assertMatchesPattern('@string@', 'foo');
 }
}

The matchesPattern() method can be used in PHPUnit stubs or mocks:

$mock = $this->createMock(Foo::class);
$mock->method('bar')
 ->with($this->matchesPattern('@string@'))
 ->willReturn('foo');

License

This library is distributed under the MIT license. Please see the LICENSE file.

Credits

This lib was inspired by JSON Expressions gem && Behat RestExtension