From 300c41a774b0d3e395d7d04bbf6c4e42c1fe165c Mon Sep 17 00:00:00 2001 From: Jarek Jakubowski Date: Fri, 8 Feb 2019 09:29:14 +0100 Subject: [PATCH] Array universal key (#143) * Array universal key * Remove unwanted Exception * add test and remove dump * revert invalid array key check * remove not needed code samples * Add more tests * Update Readme with Universal Array Key example --- README.md | 58 ++++++++++++++++++++++++++++++ src/Lexer.php | 2 +- src/Matcher/ArrayMatcher.php | 17 ++++++--- tests/LexerTest.php | 1 + tests/Matcher/ArrayMatcherTest.php | 45 +++++++++++++++++++++++ tests/Matcher/JsonMatcherTest.php | 12 +++++++ 6 files changed, 129 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ec6a6bda..be0ae53e 100644 --- a/README.md +++ b/README.md @@ -334,6 +334,64 @@ $matcher->match( ); ``` +### Json matching with unbounded arrays and objects + +```php +createMatcher(); + +$matcher->match( + '{ + "users":[ + { + "firstName": "Norbert", + "lastName": "Orzechowicz", + "created": "2014-01-01", + "roles":["ROLE_USER", "ROLE_DEVELOPER"]}, + "attributes": { + "isAdmin": false, + "dateOfBirth" null, + "hasEmailVerified": true + } + }, + { + "firstName": "Michał", + "lastName": "Dąbrowski", + "created": "2014-01-01", + "roles":["ROLE_USER", "ROLE_DEVELOPER", "ROLE_ADMIN"]}, + "attributes": { + "isAdmin": true, + "dateOfBirth" null, + "hasEmailVerified": true + } + } + ] + }', + '{ + "users":[ + { + "firstName": @string@, + "lastName": @string@, + "created": "@string@.isDateTime()", + "roles": [ + "ROLE_USER", + @...@ + ], + "attributes": { + "isAdmin": @boolean@, + "@*@": "@*@" + } + } + ], + @...@ + }' +); +``` + ### Xml matching ```php diff --git a/src/Lexer.php b/src/Lexer.php index 5d8877a4..f635f4da 100644 --- a/src/Lexer.php +++ b/src/Lexer.php @@ -32,7 +32,7 @@ protected function getCatchablePatterns() : array '\\-?[0-9]*\\.?[0-9]*', // numbers "'(?:[^']|'')*'", // string between ' character '"(?:[^"]|"")*"', // string between " character, - '@[a-zA-Z0-9\\*]+@', // type pattern + '@[a-zA-Z0-9\\*.]+@', // type pattern ]; } diff --git a/src/Matcher/ArrayMatcher.php b/src/Matcher/ArrayMatcher.php index bbb9fbd4..882906d2 100644 --- a/src/Matcher/ArrayMatcher.php +++ b/src/Matcher/ArrayMatcher.php @@ -15,6 +15,7 @@ final class ArrayMatcher extends Matcher { const PATTERN = 'array'; const UNBOUNDED_PATTERN = '@...@'; + const UNIVERSAL_KEY = '@*@'; private $propertyMatcher; @@ -70,18 +71,20 @@ private function iterateMatch(array $values, array $patterns, string $parentPath foreach ($values as $key => $value) { $path = $this->formatAccessPath($key); - if ($this->shouldSkippValueMatchingFor($pattern)) { + if ($this->shouldSkipValueMatchingFor($pattern)) { continue; } if ($this->valueExist($path, $patterns)) { $pattern = $this->getValueByPath($patterns, $path); + } elseif (isset($patterns[self::UNIVERSAL_KEY])) { + $pattern = $patterns[self::UNIVERSAL_KEY]; } else { $this->setMissingElementInError('pattern', $this->formatFullPath($parentPath, $path)); return false; } - if ($this->shouldSkippValueMatchingFor($pattern)) { + if ($this->shouldSkipValueMatchingFor($pattern)) { continue; } @@ -138,9 +141,13 @@ function ($item) use ($skipPattern) { return true; } - private function findNotExistingKeys(array $pattern, array $values) : array + private function findNotExistingKeys(array $patterns, array $values) : array { - $notExistingKeys = \array_diff_key($pattern, $values); + if (isset($patterns[self::UNIVERSAL_KEY])) { + return []; + } + + $notExistingKeys = \array_diff_key($patterns, $values); return \array_filter($notExistingKeys, function ($pattern) use ($values) { if (\is_array($pattern)) { @@ -230,7 +237,7 @@ private function formatFullPath(string $parentPath, string $path) : string return \sprintf('%s%s', $parentPath, $path); } - private function shouldSkippValueMatchingFor($lastPattern) : bool + private function shouldSkipValueMatchingFor($lastPattern) : bool { return $lastPattern === self::UNBOUNDED_PATTERN; } diff --git a/tests/LexerTest.php b/tests/LexerTest.php index 3fca10d5..db72076c 100644 --- a/tests/LexerTest.php +++ b/tests/LexerTest.php @@ -181,6 +181,7 @@ public static function validMatcherTypePatterns() ['@integer@'], ['@number@'], ['@*@'], + ['@...@'], ['@wildcard@'] ]; } diff --git a/tests/Matcher/ArrayMatcherTest.php b/tests/Matcher/ArrayMatcherTest.php index 2cadbff4..ddeb9dc4 100644 --- a/tests/Matcher/ArrayMatcherTest.php +++ b/tests/Matcher/ArrayMatcherTest.php @@ -158,9 +158,39 @@ public static function positiveMatchData() 6.66 ]; + $simpleArrPatternWithUniversalKey = [ + 'users' => [ + [ + 'firstName' => '@string@', + Matcher\ArrayMatcher::UNIVERSAL_KEY => '@*@' + ], + Matcher\ArrayMatcher::UNBOUNDED_PATTERN + ], + true, + false, + 1, + 6.66 + ]; + + $simpleArrPatternWithUniversalKeyAndStringValue = [ + 'users' => [ + [ + 'firstName' => '@string@', + Matcher\ArrayMatcher::UNIVERSAL_KEY => '@string@' + ], + Matcher\ArrayMatcher::UNBOUNDED_PATTERN + ], + true, + false, + 1, + 6.66 + ]; + return [ [$simpleArr, $simpleArr], [$simpleArr, $simpleArrPattern], + [$simpleArr, $simpleArrPatternWithUniversalKey], + [$simpleArr, $simpleArrPatternWithUniversalKeyAndStringValue], [[], []], [['foo' => null], ['foo' => null]], [['foo' => null], ['foo' => '@null@']], @@ -224,8 +254,23 @@ public static function negativeMatchData() 6.66 ]; + $simpleArrPatternWithUniversalKeyAndIntegerValue = [ + 'users' => [ + [ + 'firstName' => '@string@', + Matcher\ArrayMatcher::UNIVERSAL_KEY => '@integer@' + ], + Matcher\ArrayMatcher::UNBOUNDED_PATTERN + ], + true, + false, + 1, + 6.66 + ]; + return [ [$simpleArr, $simpleDiff], + [$simpleArr, $simpleArrPatternWithUniversalKeyAndIntegerValue], [['status' => 'ok', 'data' => [['foo']]], ['status' => 'ok', 'data' => []]], [[1], []], [['key' => 'val'], ['key' => 'val2']], diff --git a/tests/Matcher/JsonMatcherTest.php b/tests/Matcher/JsonMatcherTest.php index 4a13cc16..d9cd76bc 100644 --- a/tests/Matcher/JsonMatcherTest.php +++ b/tests/Matcher/JsonMatcherTest.php @@ -200,9 +200,21 @@ public static function positiveMatches() '{"users":[{"firstName":"Norbert","lastName":"Orzechowicz","roles":["ROLE_USER", "ROLE_DEVELOPER"]}]}', '{"users":[{"firstName":"Norbert","lastName":"Orzechowicz","roles":"@wildcard@"}]}' ], + [ + '{"users":[{"firstName":"Norbert","lastName":"Orzechowicz","roles":["ROLE_USER", "ROLE_DEVELOPER"]}]}', + '{"users":[{"firstName":"Norbert","@*@":"@*@"}]}' + ], + [ + '{"users":[{"firstName":"Norbert","lastName":"Orzechowicz","roles":["ROLE_USER", "ROLE_DEVELOPER"]},{}]}', + '{"users":[{"firstName":"Norbert","@*@":"@*@"},@...@]}' + ], [ '[{"name": "Norbert"},{"name":"Michał"},{"name":"Bob"},{"name":"Martin"}]', '[{"name": "Norbert"},@...@]' + ], + [ + '[{"name": "Norbert","lastName":"Orzechowicz"},{"name":"Michał"},{"name":"Bob"},{"name":"Martin"}]', + '[{"name": "Norbert","@*@":"@*@"},@...@]' ] ]; }