Skip to content

3.0 - Add repeat expander feature #110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ $matcher->getError(); // returns null or error message
* ``oneOf(...$expanders)`` - example usage ``"@[email protected](contains('foo'), contains('bar'), contains('baz'))"``
* ``matchRegex($regex)`` - example usage ``"@[email protected]('/^lorem.+/')"``
* ``optional()`` - work's only with ``ArrayMatcher``, ``JsonMatcher`` and ``XmlMatcher``
* ``repeat($pattern, $isStrict = true)`` - example usage ``'@[email protected]({"name": "foe"})'`` or ``"@[email protected]('@string@')"``

## Example usage

Expand Down
149 changes: 149 additions & 0 deletions src/Matcher/Pattern/Expander/Repeat.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<?php

declare(strict_types=1);

namespace Coduo\PHPMatcher\Matcher\Pattern\Expander;

use Coduo\PHPMatcher\Factory\SimpleFactory;
use Coduo\PHPMatcher\Matcher;
use Coduo\PHPMatcher\Matcher\Pattern\PatternExpander;
use Coduo\ToString\StringConverter;

final class Repeat implements PatternExpander
{
const NAME = 'repeat';

/**
* @var null|string
*/
private $error;

/**
* @var string
*/
private $pattern;

/**
* @var bool
*/
private $isStrict;

/**
* @var bool
*/
private $isScalar;

/**
* {@inheritdoc}
*/
public static function is(string $name) : bool
{
return self::NAME === $name;
}

/**
* @param $value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docblock is not matching constructor method signature

*/
public function __construct(string $pattern, bool $isStrict = true)
{
if (!is_string($pattern)) {
throw new \InvalidArgumentException("Repeat pattern must be a string.");
}

$this->pattern = $pattern;
$this->isStrict = $isStrict;
$this->isScalar = true;

$json = json_decode($pattern, true);

if ($json !== null && json_last_error() === JSON_ERROR_NONE) {
$this->pattern = $json;
$this->isScalar = false;
}
}

/**
* @param $values
* @return bool
*/
public function match($values) : bool
{
if (!is_array($values)) {
$this->error = sprintf("Repeat expander require \"array\", got \"%s\".", new StringConverter($values));
return false;
}

$factory = new SimpleFactory();
$matcher = $factory->createMatcher();

if ($this->isScalar) {
return $this->matchScalar($values, $matcher);
}

return $this->matchJson($values, $matcher);
}

/**
* @return string|null
*/
public function getError()
{
return $this->error;
}

/**
* @param array $values
* @param Matcher $matcher
* @return bool
*/
private function matchScalar(array $values, Matcher $matcher) : bool
{
foreach ($values as $index => $value) {
$match = $matcher->match($value, $this->pattern);

if (!$match) {
$this->error = sprintf("Repeat expander, entry n�%d, find error : %s", $index, $matcher->getError());
return false;
}
}

return true;
}

/**
* @param array $values
* @param Matcher $matcher
* @return bool
*/
private function matchJson(array $values, Matcher $matcher) : bool
{
$patternKeys = array_keys($this->pattern);
$patternKeysLength = count($patternKeys);

foreach ($values as $index => $value) {
$valueKeys = array_keys($value);
$valueKeysLength = count($valueKeys);

if ($this->isStrict && $patternKeysLength !== $valueKeysLength) {
$this->error = sprintf("Repeat expander expect to have %d keys in array but get : %d", $patternKeysLength, $valueKeysLength);
return false;
}

foreach ($patternKeys as $key) {
if (!array_key_exists($key, $value)) {
$this->error = sprintf("Repeat expander, entry n�%d, require \"array\" to have key \"%s\".", $index, $key);
return false;
}

$match = $matcher->match($value[$key], $this->pattern[$key]);

if (!$match) {
$this->error = sprintf("Repeat expander, entry n�%d, key \"%s\", find error : %s", $index, $key, $matcher->getError());
return false;
}
}
}

return true;
}
}
1 change: 1 addition & 0 deletions src/Parser/ExpanderInitializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ final class ExpanderInitializer
Expander\OneOf::NAME => Expander\OneOf::class,
Expander\Optional::NAME => Expander\Optional::class,
Expander\StartsWith::NAME => Expander\StartsWith::class,
Expander\Repeat::NAME => Expander\Repeat::class,
];

public function setExpanderDefinition(string $expanderName, string $expanderFQCN)
Expand Down
82 changes: 82 additions & 0 deletions tests/Matcher/Pattern/Expander/RepeatTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

declare(strict_types=1);

namespace Coduo\PHPMatcher\Tests\Matcher\Pattern\Expander;

use Coduo\PHPMatcher\Matcher\Pattern\Expander\Repeat;
use PHPUnit\Framework\TestCase;

class RepeatTest extends TestCase
{
/**
* @dataProvider examplesProvider
*/
public function test_matching_values($needle, $haystack, $expectedResult, $isStrict = true)
{
$expander = new Repeat($needle, $isStrict);
$this->assertEquals($expectedResult, $expander->match($haystack));
}

public static function examplesProvider()
{
$jsonPattern = '{"name": "@string@", "activated": "@boolean@"}';

$jsonTest = [
["name" => "toto", "activated" => true],
["name" => "titi", "activated" => false],
["name" => "tate", "activated" => true]
];

$scalarPattern = "@string@";
$scalarTest = [
"toto",
"titi",
"tata"
];

$strictTest = [
["name" => "toto", "activated" => true, "offset" => "offset"]
];

return [
[$jsonPattern, $jsonTest, true],
[$scalarPattern, $scalarTest, true],
[$jsonPattern, $strictTest, true, false]
];
}

/**
* @dataProvider invalidCasesProvider
*/
public function test_error_when_matching_fail($boundary, $value, $errorMessage)
{
$expander = new Repeat($boundary);
$this->assertFalse($expander->match($value));
$this->assertEquals($errorMessage, $expander->getError());
}

public static function invalidCasesProvider()
{
$pattern = '{"name": "@string@", "activated": "@boolean@"}';

$valueTest = [
["name" => 1, "activated" => "yes"]
];

$keyTest = [
["offset" => true, "foe" => "bar"]
];

$strictTest = [
["name" => 1, "activated" => "yes", "offset" => true]
];

return [
[$pattern, $valueTest, 'Repeat expander, entry n�0, key "name", find error : integer "1" is not a valid string.'],
[$pattern, $keyTest, 'Repeat expander, entry n�0, require "array" to have key "name".'],
[$pattern, $strictTest, 'Repeat expander expect to have 2 keys in array but get : 3'],
[$pattern, "", 'Repeat expander require "array", got "".']
];
}
}