diff --git a/src/Coduo/PHPMatcher/AST/Pattern.php b/src/Coduo/PHPMatcher/AST/Pattern.php index 3dc16d5d..9d804f85 100644 --- a/src/Coduo/PHPMatcher/AST/Pattern.php +++ b/src/Coduo/PHPMatcher/AST/Pattern.php @@ -24,7 +24,7 @@ public function __construct(Type $type) } /** - * @return mixed + * @return Type */ public function getType() { diff --git a/src/Coduo/PHPMatcher/Exception/UnknownTypeException.php b/src/Coduo/PHPMatcher/Exception/UnknownTypeException.php new file mode 100644 index 00000000..b6a0f832 --- /dev/null +++ b/src/Coduo/PHPMatcher/Exception/UnknownTypeException.php @@ -0,0 +1,7 @@ +buildParser()) )); } diff --git a/src/Coduo/PHPMatcher/Matcher/Pattern/Expander/GreaterThan.php b/src/Coduo/PHPMatcher/Matcher/Pattern/Expander/GreaterThan.php index 120ae5d5..e7153a17 100644 --- a/src/Coduo/PHPMatcher/Matcher/Pattern/Expander/GreaterThan.php +++ b/src/Coduo/PHPMatcher/Matcher/Pattern/Expander/GreaterThan.php @@ -35,7 +35,7 @@ public function __construct($boundary) */ public function match($value) { - if (!is_float($value) && !is_integer($value) && !is_double($value)) { + if (!is_float($value) && !is_integer($value) && !is_double($value) && !is_numeric($value)) { $this->error = sprintf("Value \"%s\" is not a valid number.", new String($value)); return false; } diff --git a/src/Coduo/PHPMatcher/Matcher/Pattern/RegexConverter.php b/src/Coduo/PHPMatcher/Matcher/Pattern/RegexConverter.php new file mode 100644 index 00000000..554bcd38 --- /dev/null +++ b/src/Coduo/PHPMatcher/Matcher/Pattern/RegexConverter.php @@ -0,0 +1,26 @@ +getType()) { + case 'string': + case 'wildcard': + case '*': + return "(.+)"; + case 'number': + return "(\\-?[0-9]*[\\.|\\,]?[0-9]*)"; + case 'integer': + return "(\\-?[0-9]*)"; + case 'double': + return "(\\-?[0-9]*[\\.|\\,][0-9]*)"; + default: + throw new UnknownTypeException(); + } + } +} diff --git a/src/Coduo/PHPMatcher/Matcher/Pattern/TypePattern.php b/src/Coduo/PHPMatcher/Matcher/Pattern/TypePattern.php index ab059f6a..a0298da9 100644 --- a/src/Coduo/PHPMatcher/Matcher/Pattern/TypePattern.php +++ b/src/Coduo/PHPMatcher/Matcher/Pattern/TypePattern.php @@ -37,6 +37,14 @@ public function is($type) return strtolower($this->type) === strtolower($type); } + /** + * @return string + */ + public function getType() + { + return strtolower($this->type); + } + /** * {@inheritdoc} */ diff --git a/src/Coduo/PHPMatcher/Matcher/TextMatcher.php b/src/Coduo/PHPMatcher/Matcher/TextMatcher.php new file mode 100644 index 00000000..4a5d0de6 --- /dev/null +++ b/src/Coduo/PHPMatcher/Matcher/TextMatcher.php @@ -0,0 +1,147 @@ +parser = $parser; + $this->matcher = $matcher; + } + + /** + * {@inheritDoc} + */ + public function match($value, $pattern) + { + if (!is_string($value)) { + $this->error = sprintf("%s \"%s\" is not a valid string.", gettype($value), new String($value)); + return false; + } + + $patternRegex = $pattern; + $patternsReplacedWithRegex = $this->replaceTypePatternsWithPlaceholders($patternRegex); + $patternRegex = $this->prepareRegex($patternRegex); + $patternRegex = $this->replacePlaceholderWithPatternRegexes($patternRegex, $patternsReplacedWithRegex); + + if (!preg_match($patternRegex, $value, $matchedValues)) { + $this->error = sprintf("\"%s\" does not match \"%s\" pattern", $value, $pattern); + return false; + } + + array_shift($matchedValues); // remove matched string + + if (count($patternsReplacedWithRegex) !== count($matchedValues)) { + $this->error = "Unexpected TextMatcher error."; + return false; + } + + foreach ($patternsReplacedWithRegex as $index => $typePattern) { + if (!$typePattern->matchExpanders($matchedValues[$index])) { + $this->error = $typePattern->getError(); + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function canMatch($pattern) + { + if (!is_string($pattern)) { + return false; + } + + return true; + } + + /** + * Reaplce each type pattern (@string@.startsWith("lorem")) with placeholder, in order + * to use preg_quote without destroying pattern & expanders. + * + * before replacement: "/users/@integer@.greaterThan(200)/active" + * after replacement: "/users/__PLACEHOLDER0__/active" + * + * @param string $patternRegex + * @return TypePattern[]|array + */ + private function replaceTypePatternsWithPlaceholders(&$patternRegex) + { + $patternsReplacedWithRegex = array(); + preg_match_all(self::PATTERN_REGEXP, $patternRegex, $matches); + + foreach ($matches[0] as $index => $typePatternString) { + $typePattern = $this->parser->parse($typePatternString); + $patternsReplacedWithRegex[] = $typePattern; + $patternRegex = str_replace( + $typePatternString, + sprintf(self::PATTERN_REGEXP_PLACEHOLDER_TEMPLATE, $index), + $patternRegex + ); + } + + return $patternsReplacedWithRegex; + } + + + /** + * Replace placeholders with type pattern regular expressions + * before replacement: "/users/__PLACEHOLDER0__/active" + * after replacement: "/^\/users\/(\-?[0-9]*)\/active$/" + * + * @param $patternRegex + * @return string + * @throws \Coduo\PHPMatcher\Exception\UnknownTypeException + */ + private function replacePlaceholderWithPatternRegexes($patternRegex, array $patternsReplacedWithRegex) + { + $regexConverter = new RegexConverter(); + foreach ($patternsReplacedWithRegex as $index => $typePattern) { + $patternRegex = str_replace( + sprintf(self::PATTERN_REGEXP_PLACEHOLDER_TEMPLATE, $index), + $regexConverter->toRegex($typePattern), + $patternRegex + ); + } + + return $patternRegex; + } + + /** + * Prepare regular expression + * + * @param string $patternRegex + * @return string + */ + private function prepareRegex($patternRegex) + { + return "/^" . preg_quote($patternRegex, '/') . "$/"; + } +} diff --git a/tests/Coduo/PHPMatcher/Matcher/Pattern/Expander/GreaterThanTest.php b/tests/Coduo/PHPMatcher/Matcher/Pattern/Expander/GreaterThanTest.php index 68c23294..b143fd52 100644 --- a/tests/Coduo/PHPMatcher/Matcher/Pattern/Expander/GreaterThanTest.php +++ b/tests/Coduo/PHPMatcher/Matcher/Pattern/Expander/GreaterThanTest.php @@ -23,6 +23,7 @@ public static function examplesProvider() array(-20, -10.5, true), array(10, 1, false), array(1, 1, false), + array(10, "20", true) ); } diff --git a/tests/Coduo/PHPMatcher/Matcher/Pattern/RegexConverterTest.php b/tests/Coduo/PHPMatcher/Matcher/Pattern/RegexConverterTest.php new file mode 100644 index 00000000..8f8a740c --- /dev/null +++ b/tests/Coduo/PHPMatcher/Matcher/Pattern/RegexConverterTest.php @@ -0,0 +1,27 @@ +converter = new RegexConverter(); + } + + /** + * @expectedException \Coduo\PHPMatcher\Exception\UnknownTypeException + */ + public function test_convert_unknown_type() + { + $this->converter->toRegex(new TypePattern("not_a_type")); + } +} diff --git a/tests/Coduo/PHPMatcher/Matcher/TextMatcherTest.php b/tests/Coduo/PHPMatcher/Matcher/TextMatcherTest.php new file mode 100644 index 00000000..79f2800f --- /dev/null +++ b/tests/Coduo/PHPMatcher/Matcher/TextMatcherTest.php @@ -0,0 +1,68 @@ +matcher = new Matcher\TextMatcher( + new Matcher\ChainMatcher(array( + $scalarMatchers, + new Matcher\ArrayMatcher($scalarMatchers, $parser) + )), + $parser + ); + } + + /** + * @dataProvider matchingData + */ + public function test_positive_matches($value, $pattern, $expectedResult) + { + $this->assertEquals($expectedResult, $this->matcher->match($value, $pattern)); + } + + public function matchingData() + { + return array( + array( + "lorem ipsum lol lorem 24 dolorem", + "lorem ipsum @string@.startsWith(\"lo\") lorem @number@ dolorem", + true + ), + array( + "lorem ipsum 24 dolorem", + "lorem ipsum @integer@", + false + ), + array( + "/users/12345/active", + "/users/@integer@.greaterThan(0)/active", + true + ) + ); + } +} diff --git a/tests/Coduo/PHPMatcher/MatcherTest.php b/tests/Coduo/PHPMatcher/MatcherTest.php index a7a72892..58df8b1b 100644 --- a/tests/Coduo/PHPMatcher/MatcherTest.php +++ b/tests/Coduo/PHPMatcher/MatcherTest.php @@ -41,7 +41,8 @@ public function setUp() $scalarMatchers, $arrayMatcher, new Matcher\JsonMatcher($arrayMatcher), - new Matcher\XmlMatcher($arrayMatcher) + new Matcher\XmlMatcher($arrayMatcher), + new Matcher\TextMatcher($scalarMatchers, $parser) ))); } @@ -194,6 +195,14 @@ public function test_matcher_with_xml() $this->assertTrue(match($xml, $xmlPattern)); } + public function test_text_matcher() + { + $value = "lorem ipsum 1234 random text"; + $pattern = "@string@.startsWith('lo') ipsum @number@.greaterThan(10) random text"; + $this->assertTrue($this->matcher->match($value, $pattern)); + $this->assertTrue(match($value, $pattern)); + } + public function test_matcher_with_captures() { $this->assertTrue($this->matcher->match(