Skip to content

Commit cb81445

Browse files
author
Norbert Orzechowicz
committedDec 14, 2014
Merge pull request #44 from norzechowicz/text-matcher
Added text matcher
2 parents d761fb6 + 73c009c commit cb81445

File tree

11 files changed

+298
-4
lines changed

11 files changed

+298
-4
lines changed
 

Diff for: ‎src/Coduo/PHPMatcher/AST/Pattern.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function __construct(Type $type)
2424
}
2525

2626
/**
27-
* @return mixed
27+
* @return Type
2828
*/
2929
public function getType()
3030
{
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Coduo\PHPMatcher\Exception;
4+
5+
class UnknownTypeException extends Exception
6+
{
7+
}

Diff for: ‎src/Coduo/PHPMatcher/Factory/SimpleFactory.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ protected function buildMatchers()
2929
$scalarMatchers,
3030
$arrayMatcher,
3131
new Matcher\JsonMatcher($arrayMatcher),
32-
new Matcher\XmlMatcher($arrayMatcher)
32+
new Matcher\XmlMatcher($arrayMatcher),
33+
new Matcher\TextMatcher($scalarMatchers, $this->buildParser())
3334
));
3435
}
3536

Diff for: ‎src/Coduo/PHPMatcher/Matcher/Pattern/Expander/GreaterThan.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public function __construct($boundary)
3535
*/
3636
public function match($value)
3737
{
38-
if (!is_float($value) && !is_integer($value) && !is_double($value)) {
38+
if (!is_float($value) && !is_integer($value) && !is_double($value) && !is_numeric($value)) {
3939
$this->error = sprintf("Value \"%s\" is not a valid number.", new String($value));
4040
return false;
4141
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Coduo\PHPMatcher\Matcher\Pattern;
4+
5+
use Coduo\PHPMatcher\Exception\UnknownTypeException;
6+
7+
class RegexConverter
8+
{
9+
public function toRegex(TypePattern $type)
10+
{
11+
switch ($type->getType()) {
12+
case 'string':
13+
case 'wildcard':
14+
case '*':
15+
return "(.+)";
16+
case 'number':
17+
return "(\\-?[0-9]*[\\.|\\,]?[0-9]*)";
18+
case 'integer':
19+
return "(\\-?[0-9]*)";
20+
case 'double':
21+
return "(\\-?[0-9]*[\\.|\\,][0-9]*)";
22+
default:
23+
throw new UnknownTypeException();
24+
}
25+
}
26+
}

Diff for: ‎src/Coduo/PHPMatcher/Matcher/Pattern/TypePattern.php

+8
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ public function is($type)
3737
return strtolower($this->type) === strtolower($type);
3838
}
3939

40+
/**
41+
* @return string
42+
*/
43+
public function getType()
44+
{
45+
return strtolower($this->type);
46+
}
47+
4048
/**
4149
* {@inheritdoc}
4250
*/

Diff for: ‎src/Coduo/PHPMatcher/Matcher/TextMatcher.php

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
<?php
2+
3+
namespace Coduo\PHPMatcher\Matcher;
4+
5+
use Coduo\PHPMatcher\Matcher\Pattern\TypePattern;
6+
use Coduo\PHPMatcher\Parser;
7+
use Coduo\PHPMatcher\Matcher\Pattern\RegexConverter;
8+
use Coduo\ToString\String;
9+
10+
class TextMatcher extends Matcher
11+
{
12+
const PATTERN_REGEXP = "/@[a-zA-Z\\.]+@(\\.[a-zA-Z0-9_]+\\([a-zA-Z0-9{},:@\\.\"'\\(\\)]*\\))*/";
13+
14+
const PATTERN_REGEXP_PLACEHOLDER_TEMPLATE = "__PLACEHOLDER%d__";
15+
16+
/**
17+
* @var Parser
18+
*/
19+
private $parser;
20+
21+
/**
22+
* @var ValueMatcher
23+
*/
24+
private $matcher;
25+
26+
/**
27+
* @param ValueMatcher $matcher
28+
* @param Parser $parser
29+
*/
30+
public function __construct(ValueMatcher $matcher, Parser $parser)
31+
{
32+
$this->parser = $parser;
33+
$this->matcher = $matcher;
34+
}
35+
36+
/**
37+
* {@inheritDoc}
38+
*/
39+
public function match($value, $pattern)
40+
{
41+
if (!is_string($value)) {
42+
$this->error = sprintf("%s \"%s\" is not a valid string.", gettype($value), new String($value));
43+
return false;
44+
}
45+
46+
$patternRegex = $pattern;
47+
$patternsReplacedWithRegex = $this->replaceTypePatternsWithPlaceholders($patternRegex);
48+
$patternRegex = $this->prepareRegex($patternRegex);
49+
$patternRegex = $this->replacePlaceholderWithPatternRegexes($patternRegex, $patternsReplacedWithRegex);
50+
51+
if (!preg_match($patternRegex, $value, $matchedValues)) {
52+
$this->error = sprintf("\"%s\" does not match \"%s\" pattern", $value, $pattern);
53+
return false;
54+
}
55+
56+
array_shift($matchedValues); // remove matched string
57+
58+
if (count($patternsReplacedWithRegex) !== count($matchedValues)) {
59+
$this->error = "Unexpected TextMatcher error.";
60+
return false;
61+
}
62+
63+
foreach ($patternsReplacedWithRegex as $index => $typePattern) {
64+
if (!$typePattern->matchExpanders($matchedValues[$index])) {
65+
$this->error = $typePattern->getError();
66+
return false;
67+
}
68+
}
69+
70+
return true;
71+
}
72+
73+
/**
74+
* {@inheritDoc}
75+
*/
76+
public function canMatch($pattern)
77+
{
78+
if (!is_string($pattern)) {
79+
return false;
80+
}
81+
82+
return true;
83+
}
84+
85+
/**
86+
* Reaplce each type pattern (@string@.startsWith("lorem")) with placeholder, in order
87+
* to use preg_quote without destroying pattern & expanders.
88+
*
89+
* before replacement: "/users/@integer@.greaterThan(200)/active"
90+
* after replacement: "/users/__PLACEHOLDER0__/active"
91+
*
92+
* @param string $patternRegex
93+
* @return TypePattern[]|array
94+
*/
95+
private function replaceTypePatternsWithPlaceholders(&$patternRegex)
96+
{
97+
$patternsReplacedWithRegex = array();
98+
preg_match_all(self::PATTERN_REGEXP, $patternRegex, $matches);
99+
100+
foreach ($matches[0] as $index => $typePatternString) {
101+
$typePattern = $this->parser->parse($typePatternString);
102+
$patternsReplacedWithRegex[] = $typePattern;
103+
$patternRegex = str_replace(
104+
$typePatternString,
105+
sprintf(self::PATTERN_REGEXP_PLACEHOLDER_TEMPLATE, $index),
106+
$patternRegex
107+
);
108+
}
109+
110+
return $patternsReplacedWithRegex;
111+
}
112+
113+
114+
/**
115+
* Replace placeholders with type pattern regular expressions
116+
* before replacement: "/users/__PLACEHOLDER0__/active"
117+
* after replacement: "/^\/users\/(\-?[0-9]*)\/active$/"
118+
*
119+
* @param $patternRegex
120+
* @return string
121+
* @throws \Coduo\PHPMatcher\Exception\UnknownTypeException
122+
*/
123+
private function replacePlaceholderWithPatternRegexes($patternRegex, array $patternsReplacedWithRegex)
124+
{
125+
$regexConverter = new RegexConverter();
126+
foreach ($patternsReplacedWithRegex as $index => $typePattern) {
127+
$patternRegex = str_replace(
128+
sprintf(self::PATTERN_REGEXP_PLACEHOLDER_TEMPLATE, $index),
129+
$regexConverter->toRegex($typePattern),
130+
$patternRegex
131+
);
132+
}
133+
134+
return $patternRegex;
135+
}
136+
137+
/**
138+
* Prepare regular expression
139+
*
140+
* @param string $patternRegex
141+
* @return string
142+
*/
143+
private function prepareRegex($patternRegex)
144+
{
145+
return "/^" . preg_quote($patternRegex, '/') . "$/";
146+
}
147+
}

Diff for: ‎tests/Coduo/PHPMatcher/Matcher/Pattern/Expander/GreaterThanTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public static function examplesProvider()
2323
array(-20, -10.5, true),
2424
array(10, 1, false),
2525
array(1, 1, false),
26+
array(10, "20", true)
2627
);
2728
}
2829

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Coduo\PHPMatcher\Tests\Matcher\Pattern;
4+
5+
use Coduo\PHPMatcher\Matcher\Pattern\TypePattern;
6+
use Coduo\PHPMatcher\Matcher\Pattern\RegexConverter;
7+
8+
class RegexConverterTest extends \PHPUnit_Framework_TestCase
9+
{
10+
/**
11+
* @var RegexConverter
12+
*/
13+
private $converter;
14+
15+
public function setUp()
16+
{
17+
$this->converter = new RegexConverter();
18+
}
19+
20+
/**
21+
* @expectedException \Coduo\PHPMatcher\Exception\UnknownTypeException
22+
*/
23+
public function test_convert_unknown_type()
24+
{
25+
$this->converter->toRegex(new TypePattern("not_a_type"));
26+
}
27+
}

Diff for: ‎tests/Coduo/PHPMatcher/Matcher/TextMatcherTest.php

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace Coduo\PHPMatcher\Tests\Matcher;
4+
5+
use Coduo\PHPMatcher\Lexer;
6+
use Coduo\PHPMatcher\Matcher;
7+
use Coduo\PHPMatcher\Parser;
8+
9+
class TextMatcherTest extends \PHPUnit_Framework_TestCase
10+
{
11+
/**
12+
* @var Matcher\TextMatcher
13+
*/
14+
private $matcher;
15+
16+
public function setUp()
17+
{
18+
$parser = new Parser(new Lexer(), new Parser\ExpanderInitializer());
19+
$scalarMatchers = new Matcher\ChainMatcher(array(
20+
new Matcher\CallbackMatcher(),
21+
new Matcher\ExpressionMatcher(),
22+
new Matcher\NullMatcher(),
23+
new Matcher\StringMatcher($parser),
24+
new Matcher\IntegerMatcher($parser),
25+
new Matcher\BooleanMatcher(),
26+
new Matcher\DoubleMatcher($parser),
27+
new Matcher\NumberMatcher(),
28+
new Matcher\ScalarMatcher(),
29+
new Matcher\WildcardMatcher(),
30+
));
31+
$this->matcher = new Matcher\TextMatcher(
32+
new Matcher\ChainMatcher(array(
33+
$scalarMatchers,
34+
new Matcher\ArrayMatcher($scalarMatchers, $parser)
35+
)),
36+
$parser
37+
);
38+
}
39+
40+
/**
41+
* @dataProvider matchingData
42+
*/
43+
public function test_positive_matches($value, $pattern, $expectedResult)
44+
{
45+
$this->assertEquals($expectedResult, $this->matcher->match($value, $pattern));
46+
}
47+
48+
public function matchingData()
49+
{
50+
return array(
51+
array(
52+
"lorem ipsum lol lorem 24 dolorem",
53+
"lorem ipsum @string@.startsWith(\"lo\") lorem @number@ dolorem",
54+
true
55+
),
56+
array(
57+
"lorem ipsum 24 dolorem",
58+
"lorem ipsum @integer@",
59+
false
60+
),
61+
array(
62+
"/users/12345/active",
63+
"/users/@integer@.greaterThan(0)/active",
64+
true
65+
)
66+
);
67+
}
68+
}

Diff for: ‎tests/Coduo/PHPMatcher/MatcherTest.php

+10-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ public function setUp()
4141
$scalarMatchers,
4242
$arrayMatcher,
4343
new Matcher\JsonMatcher($arrayMatcher),
44-
new Matcher\XmlMatcher($arrayMatcher)
44+
new Matcher\XmlMatcher($arrayMatcher),
45+
new Matcher\TextMatcher($scalarMatchers, $parser)
4546
)));
4647
}
4748

@@ -194,6 +195,14 @@ public function test_matcher_with_xml()
194195
$this->assertTrue(match($xml, $xmlPattern));
195196
}
196197

198+
public function test_text_matcher()
199+
{
200+
$value = "lorem ipsum 1234 random text";
201+
$pattern = "@string@.startsWith('lo') ipsum @number@.greaterThan(10) random text";
202+
$this->assertTrue($this->matcher->match($value, $pattern));
203+
$this->assertTrue(match($value, $pattern));
204+
}
205+
197206
public function test_matcher_with_captures()
198207
{
199208
$this->assertTrue($this->matcher->match(

0 commit comments

Comments
 (0)