Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/coduo/php-matcher

The easiest way to match data structures like JSON/PlainText/XML against readable patterns. Sandbox:
https://github.com/coduo/php-matcher

Last synced: 6 days ago
JSON representation

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

Awesome Lists containing this project

README

        

# PHP Matcher

[![Type Coverage](https://shepherd.dev/github/coduo/php-matcher/coverage.svg)](https://shepherd.dev/coduo/php-matcher)

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

API:

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

It was built to simplify API's functional testing.

* [![Test Suite](https://github.com/coduo/php-matcher/actions/workflows/test-suite.yml/badge.svg?branch=6.x)](https://github.com/coduo/php-matcher/actions/workflows/test-suite.yml) - [6.x README](https://github.com/coduo/php-matcher/tree/6.x/README.md) PHP >= 8.1 <= 8.3
* [5.x README](https://github.com/coduo/php-matcher/tree/5.x/README.md) PHP >= 7.2 < 8.0
* [5.0 README](https://github.com/coduo/php-matcher/tree/5.0/README.md) PHP >= 7.2 < 8.0
* [4.0.* README](https://github.com/coduo/php-matcher/tree/4.0/README.md) PHP >= 7.2 < 8.0
* [3.2.* README](https://github.com/coduo/php-matcher/tree/3.2/README.md) PHP >= 7.0 < 8.0
* [3.1.* README](https://github.com/coduo/php-matcher/tree/3.1/README.md) PHP >= 7.0 < 8.0

[![Latest Stable Version](https://poser.pugx.org/coduo/php-matcher/v/stable)](https://packagist.org/packages/coduo/php-matcher)
[![Total Downloads](https://poser.pugx.org/coduo/php-matcher/downloads)](https://packagist.org/packages/coduo/php-matcher)
[![Latest Unstable Version](https://poser.pugx.org/coduo/php-matcher/v/unstable)](https://packagist.org/packages/coduo/php-matcher)
[![License](https://poser.pugx.org/coduo/php-matcher/license)](https://packagist.org/packages/coduo/php-matcher)

## We Stand Against Terror



Stand With Ukraine
Stand With Us




Flag of Ukraine
Flag of Israel

> On Feb. 24, 2022, Russia declared an unprovoked war on Ukraine and launched a full-scale invasion. Russia is currently bombing peaceful Ukrainian cities, including schools and hospitals and attacking civilians who are fleeing conflict zones.

> On Oct. 7, 2023, the national holiday of Simchat Torah, Hamas terrorists initiated an attack on Israel in the early hours, targeting civilians. They unleashed violence that resulted in at least 1,400 casualties and abducted at least 200 individuals, not limited to Israelis.

## Sandbox

Feel free to play first with [Sandbox](https://php-matcher.norbert.tech/)

## Installation

Require new dev dependency using composer:

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

## Basic usage

### Direct PHPMatcher usage

```php
match("lorem ipsum dolor", "@string@");

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

### PHPUnit extending PHPMatcherTestCase

```php
assertMatchesPattern('{"name": "@string@"}', '{"name": "Norbert"}');
}
}
```

### PHPUnit using PHPMatcherAssertions trait

```php
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: ^2.3|^3.0|^4.0|^5.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 `"@[email protected]('Y-m-d H:i:s')`
* ``before(string $date)`` - example ``"@[email protected]().before(\"2020-01-01 00:00:00\")"``
* ``after(string $date)`` - example ``"@[email protected]().after(\"2020-01-01 00:00:00\")"``
* ``isTzOffset()``
* ``isTzIdentifier()``
* ``isTzAbbreviation()``
* ``isEmail()``
* ``isUrl()``
* ``isIp()``
* ``isEmpty()``
* ``isNotEmpty()``
* ``lowerThan($boundry)``
* ``greaterThan($boundry)``
* ``inArray($value)`` - example ``"@[email protected](\"ROLE_USER\")"``
* ``hasProperty($propertyName)`` - example ``"@[email protected](\"property_name\")"``
* ``oneOf(...$expanders)`` - example ``"@[email protected](contains('foo'), contains('bar'), contains('baz'))"``
* ``matchRegex($regex)`` - example ``"@[email protected]('/^lorem.+/')"``
* ``optional()`` - work's only with ``ArrayMatcher``, ``JsonMatcher`` and ``XmlMatcher``
* ``count()`` - work's only with ``ArrayMatcher`` - example ``"@[email protected](5)"``
* ``repeat($pattern, $isStrict = true)`` - example ``'@[email protected]({"name": "foe"})'`` or ``"@[email protected]('@string@')"``
* ``match($pattern)`` - example ``{"image":"@[email protected]({\"url\":\"@[email protected]()\"})"}``

## Example usage

### Scalar matching

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

### String matching

```php
match('Norbert', '@string@');
$matcher->match("lorem ipsum dolor", "@[email protected]('lorem').contains('ipsum').endsWith('dolor')");

```

### Time matching

```php
match('00:00:00', '@time@');
$matcher->match('00:01:00.000000', '@time@');
$matcher->match('00:01:00', '@[email protected]("00:00:00")');
$matcher->match('00:00:00', '@[email protected]("01:00:00")');

```

### Date matching

```php
match('2014-08-19', '@date@');
$matcher->match('2020-01-11', '@date@');
$matcher->match('2014-08-19', '@[email protected]("2016-08-19")');
$matcher->match('2014-08-19', '@[email protected]("today").after("+ 100year")');

```

### DateTime matching

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

```

### TimeZone matching

```php
match('Europe/Warsaw', '@timezone@');
$matcher->match('Europe/Warsaw', '@tz@');
$matcher->match('GMT', '@tz@');
$matcher->match('01:00', '@tz@');
$matcher->match('01:00', '@[email protected]()');
$matcher->match('GMT', '@[email protected]()');
$matcher->match('Europe/Warsaw', '@[email protected]()');
```

### Integer matching

```php
match(100, '@integer@');
$matcher->match(100, '@[email protected](200).greaterThan(10)');

```

### Number matching

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

### Double matching

```php
match(10.1, "@double@");
$matcher->match(10.1, "@[email protected](50.12).greaterThan(10)");
```

### Boolean matching

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

### Wildcard matching

```php
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
match(new \DateTime('2014-04-01'), "expr(value.format('Y-m-d') == '2014-04-01'");
$matcher->match("Norbert", "expr(value === 'Norbert')");
```

### UUID matching

```php
match('9f4db639-0e87-4367-9beb-d64e3f42ae18', '@uuid@');
```

### ULID matching

```php
match('01BX5ZZKBKACTAV9WEVGEMMVS0', '@ulid@');
```

### Array matching

```php
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' => '@[email protected](0)',
'firstName' => '@string@',
'lastName' => 'Orzechowicz',
'roles' => '@array@',
'position' => '@[email protected]()'
),
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
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' => '@[email protected](0)',
'firstName' => '@string@',
'lastName' => 'Orzechowicz',
'roles' => '@array@',
'position' => '@[email protected]()'
),
'@array_previous@',
'@array_previous@'
),
'@boolean@',
'@double@'
)
);
```

### Array Previous Repeat

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

```php
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' => '@[email protected](0)',
'firstName' => '@string@',
'lastName' => 'Orzechowicz',
'roles' => '@array@',
'position' => '@[email protected]()'
),
'@array_previous_repeat@'
),
'@boolean@',
'@double@'
)
);
```

### Json matching

```php
match(
'{
"users":[
{
"firstName": "Norbert",
"lastName": "Orzechowicz",
"created": "2014-01-01",
"roles":["ROLE_USER", "ROLE_DEVELOPER"]
}
]
}',
'{
"users":[
{
"firstName": "@string@",
"lastName": "@string@",
"created": "@[email protected]()",
"roles": "@array@",
"position": "@[email protected]()"
}
]
}'
);
```

### Json matching with unbounded arrays and objects

```php
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": "@[email protected]()",
"roles": [
"ROLE_USER",
"@...@"
],
"attributes": {
"isAdmin": @boolean@,
"@*@": "@*@"
},
"avatar": "@[email protected]({\"url\":\"@[email protected]()\"})"
}
,
@...@
]
}'
);
```

### Xml matching

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

```php
match(<<


IBM
Any Value

XML
,
<<


@string@
@string@
@[email protected]()

XML
);
```

Example scenario for api in behat using mongo.
---
``` cucumber
@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`:

```php
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:

```php
$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](https://github.com/chancancode/json_expressions) &&
[Behat RestExtension ](https://github.com/jakzal/RestExtension)