Skip to content

Commit 145825c

Browse files
Leaklessnorberttech
Leakless
authored andcommitted
3.0 - Add repeat expander feature (#110)
* RepeatExpander : Master branch * RepeatExpander : Add new line at the end of test
1 parent 9623632 commit 145825c

File tree

4 files changed

+233
-0
lines changed

4 files changed

+233
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ $matcher->getError(); // returns null or error message
8484
* ``oneOf(...$expanders)`` - example usage ``"@[email protected](contains('foo'), contains('bar'), contains('baz'))"``
8585
* ``matchRegex($regex)`` - example usage ``"@[email protected]('/^lorem.+/')"``
8686
* ``optional()`` - work's only with ``ArrayMatcher``, ``JsonMatcher`` and ``XmlMatcher``
87+
* ``repeat($pattern, $isStrict = true)`` - example usage ``'@[email protected]({"name": "foe"})'`` or ``"@[email protected]('@string@')"``
8788

8889
## Example usage
8990

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Coduo\PHPMatcher\Matcher\Pattern\Expander;
6+
7+
use Coduo\PHPMatcher\Factory\SimpleFactory;
8+
use Coduo\PHPMatcher\Matcher;
9+
use Coduo\PHPMatcher\Matcher\Pattern\PatternExpander;
10+
use Coduo\ToString\StringConverter;
11+
12+
final class Repeat implements PatternExpander
13+
{
14+
const NAME = 'repeat';
15+
16+
/**
17+
* @var null|string
18+
*/
19+
private $error;
20+
21+
/**
22+
* @var string
23+
*/
24+
private $pattern;
25+
26+
/**
27+
* @var bool
28+
*/
29+
private $isStrict;
30+
31+
/**
32+
* @var bool
33+
*/
34+
private $isScalar;
35+
36+
/**
37+
* {@inheritdoc}
38+
*/
39+
public static function is(string $name) : bool
40+
{
41+
return self::NAME === $name;
42+
}
43+
44+
/**
45+
* @param $value
46+
*/
47+
public function __construct(string $pattern, bool $isStrict = true)
48+
{
49+
if (!is_string($pattern)) {
50+
throw new \InvalidArgumentException("Repeat pattern must be a string.");
51+
}
52+
53+
$this->pattern = $pattern;
54+
$this->isStrict = $isStrict;
55+
$this->isScalar = true;
56+
57+
$json = json_decode($pattern, true);
58+
59+
if ($json !== null && json_last_error() === JSON_ERROR_NONE) {
60+
$this->pattern = $json;
61+
$this->isScalar = false;
62+
}
63+
}
64+
65+
/**
66+
* @param $values
67+
* @return bool
68+
*/
69+
public function match($values) : bool
70+
{
71+
if (!is_array($values)) {
72+
$this->error = sprintf("Repeat expander require \"array\", got \"%s\".", new StringConverter($values));
73+
return false;
74+
}
75+
76+
$factory = new SimpleFactory();
77+
$matcher = $factory->createMatcher();
78+
79+
if ($this->isScalar) {
80+
return $this->matchScalar($values, $matcher);
81+
}
82+
83+
return $this->matchJson($values, $matcher);
84+
}
85+
86+
/**
87+
* @return string|null
88+
*/
89+
public function getError()
90+
{
91+
return $this->error;
92+
}
93+
94+
/**
95+
* @param array $values
96+
* @param Matcher $matcher
97+
* @return bool
98+
*/
99+
private function matchScalar(array $values, Matcher $matcher) : bool
100+
{
101+
foreach ($values as $index => $value) {
102+
$match = $matcher->match($value, $this->pattern);
103+
104+
if (!$match) {
105+
$this->error = sprintf("Repeat expander, entry n°%d, find error : %s", $index, $matcher->getError());
106+
return false;
107+
}
108+
}
109+
110+
return true;
111+
}
112+
113+
/**
114+
* @param array $values
115+
* @param Matcher $matcher
116+
* @return bool
117+
*/
118+
private function matchJson(array $values, Matcher $matcher) : bool
119+
{
120+
$patternKeys = array_keys($this->pattern);
121+
$patternKeysLength = count($patternKeys);
122+
123+
foreach ($values as $index => $value) {
124+
$valueKeys = array_keys($value);
125+
$valueKeysLength = count($valueKeys);
126+
127+
if ($this->isStrict && $patternKeysLength !== $valueKeysLength) {
128+
$this->error = sprintf("Repeat expander expect to have %d keys in array but get : %d", $patternKeysLength, $valueKeysLength);
129+
return false;
130+
}
131+
132+
foreach ($patternKeys as $key) {
133+
if (!array_key_exists($key, $value)) {
134+
$this->error = sprintf("Repeat expander, entry n°%d, require \"array\" to have key \"%s\".", $index, $key);
135+
return false;
136+
}
137+
138+
$match = $matcher->match($value[$key], $this->pattern[$key]);
139+
140+
if (!$match) {
141+
$this->error = sprintf("Repeat expander, entry n°%d, key \"%s\", find error : %s", $index, $key, $matcher->getError());
142+
return false;
143+
}
144+
}
145+
}
146+
147+
return true;
148+
}
149+
}

src/Parser/ExpanderInitializer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ final class ExpanderInitializer
3030
Expander\OneOf::NAME => Expander\OneOf::class,
3131
Expander\Optional::NAME => Expander\Optional::class,
3232
Expander\StartsWith::NAME => Expander\StartsWith::class,
33+
Expander\Repeat::NAME => Expander\Repeat::class,
3334
];
3435

3536
public function setExpanderDefinition(string $expanderName, string $expanderFQCN)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Coduo\PHPMatcher\Tests\Matcher\Pattern\Expander;
6+
7+
use Coduo\PHPMatcher\Matcher\Pattern\Expander\Repeat;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class RepeatTest extends TestCase
11+
{
12+
/**
13+
* @dataProvider examplesProvider
14+
*/
15+
public function test_matching_values($needle, $haystack, $expectedResult, $isStrict = true)
16+
{
17+
$expander = new Repeat($needle, $isStrict);
18+
$this->assertEquals($expectedResult, $expander->match($haystack));
19+
}
20+
21+
public static function examplesProvider()
22+
{
23+
$jsonPattern = '{"name": "@string@", "activated": "@boolean@"}';
24+
25+
$jsonTest = [
26+
["name" => "toto", "activated" => true],
27+
["name" => "titi", "activated" => false],
28+
["name" => "tate", "activated" => true]
29+
];
30+
31+
$scalarPattern = "@string@";
32+
$scalarTest = [
33+
"toto",
34+
"titi",
35+
"tata"
36+
];
37+
38+
$strictTest = [
39+
["name" => "toto", "activated" => true, "offset" => "offset"]
40+
];
41+
42+
return [
43+
[$jsonPattern, $jsonTest, true],
44+
[$scalarPattern, $scalarTest, true],
45+
[$jsonPattern, $strictTest, true, false]
46+
];
47+
}
48+
49+
/**
50+
* @dataProvider invalidCasesProvider
51+
*/
52+
public function test_error_when_matching_fail($boundary, $value, $errorMessage)
53+
{
54+
$expander = new Repeat($boundary);
55+
$this->assertFalse($expander->match($value));
56+
$this->assertEquals($errorMessage, $expander->getError());
57+
}
58+
59+
public static function invalidCasesProvider()
60+
{
61+
$pattern = '{"name": "@string@", "activated": "@boolean@"}';
62+
63+
$valueTest = [
64+
["name" => 1, "activated" => "yes"]
65+
];
66+
67+
$keyTest = [
68+
["offset" => true, "foe" => "bar"]
69+
];
70+
71+
$strictTest = [
72+
["name" => 1, "activated" => "yes", "offset" => true]
73+
];
74+
75+
return [
76+
[$pattern, $valueTest, 'Repeat expander, entry n°0, key "name", find error : integer "1" is not a valid string.'],
77+
[$pattern, $keyTest, 'Repeat expander, entry n°0, require "array" to have key "name".'],
78+
[$pattern, $strictTest, 'Repeat expander expect to have 2 keys in array but get : 3'],
79+
[$pattern, "", 'Repeat expander require "array", got "".']
80+
];
81+
}
82+
}

0 commit comments

Comments
 (0)