Skip to content

2.3 - Add repeat expander feature #109

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 3 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
147 changes: 147 additions & 0 deletions src/Matcher/Pattern/Expander/Repeat.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?php

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($name)
{
return self::NAME === $name;
}

/**
* @param $value
*/
public function __construct($pattern, $isStrict = true)
{
if (!is_string($pattern)) {
throw new \InvalidArgumentException("Repeat pattern must be a string.");
Copy link
Contributor

Choose a reason for hiding this comment

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

This exception is not covered by tests

Copy link
Author

Choose a reason for hiding this comment

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

Will fix that ASAP.

}

$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)
{
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)
{
foreach ($values as $index => $value) {
$match = $matcher->match($value, $this->pattern);
Copy link
Contributor

Choose a reason for hiding this comment

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

If we only match value, is following pattern failing ?

@[email protected]({"foo": "@string@"})

[
  {
    "foo": "bar" 
  },
  {
    "test": "bar"
  }
]
``

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

I'm not sure to understand what you're talking about here ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Just ignore this comment


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)
Copy link
Contributor

Choose a reason for hiding this comment

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

We should maybe allow this method to be recursive to match nested objects ?

Copy link
Contributor

Choose a reason for hiding this comment

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

{
"offers": "@[email protected]({\"subscription\": \"@[email protected](\"date\": \"@[email protected]()\")\"})"
}

This case will work with actual implementation ?

Copy link
Author

Choose a reason for hiding this comment

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

This case should work with the nested array.repeat syntax 'cause I just make a call to php-matcher factory. But you're right, maybe allowing nested objects would be more clever.

Copy link

Choose a reason for hiding this comment

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

This is not yet supported, right?

Copy link
Author

Choose a reason for hiding this comment

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

Yes I'm sorry, didn't have the time to do it

{
$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 @@ -31,6 +31,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,
];

/**
Expand Down
79 changes: 79 additions & 0 deletions tests/Matcher/Pattern/Expander/RepeatTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

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

use Coduo\PHPMatcher\Matcher\Pattern\Expander\Repeat;

class RepeatTest extends \PHPUnit_Framework_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 = array(
Copy link
Contributor

Choose a reason for hiding this comment

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

You could use "new" PHP short array syntax [] which is less verbose

Copy link
Author

Choose a reason for hiding this comment

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

Yes probably, but the whole project seems to use the older array() syntax. In order to stick with the project's coding convention I'd rather not change that for now.

Copy link
Member

Choose a reason for hiding this comment

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

On master branch we are using [] - 2.3 branch was only created for 5.6 support however I'm not planning to add more new features here without adding them to the master branch first

array("name" => "toto", "activated" => true),
array("name" => "titi", "activated" => false),
array("name" => "tate", "activated" => true)
);

$scalarPattern = "@string@";
$scalarTest = array(
"toto",
"titi",
"tata"
);

$strictTest = array(
array("name" => "toto", "activated" => true, "offset" => "offset")
);

return array(
array($jsonPattern, $jsonTest, true),
array($scalarPattern, $scalarTest, true),
array($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 = array(
array("name" => 1, "activated" => "yes")
);

$keyTest = array(
array("offset" => true, "foe" => "bar")
);

$strictTest = array(
array("name" => 1, "activated" => "yes", "offset" => true)
);

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