Skip to content

Commit 7b2cb77

Browse files
Leaklessnorberttech
Leakless
authored andcommitted
2.3 - Add repeat expander feature (#109)
* RepeatExpander : feature + tests * RepeatExpander: update README * RepeatExpander : Fix readme and new failure test
1 parent 79a27d1 commit 7b2cb77

File tree

4 files changed

+228
-0
lines changed

4 files changed

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

src/Parser/ExpanderInitializer.php

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

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

0 commit comments

Comments
 (0)