Skip to content

Commit a304ea3

Browse files
committed
Merge remote-tracking branch 'origin/master' into bug/resolve-parent-definition
2 parents c0c960d + 1eb8e61 commit a304ea3

12 files changed

+677
-14
lines changed

src/AbstractKeyValueStore.php

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
use Zend\Stdlib\ArrayUtils;
1212

13-
abstract class AbstractKeyValueStore implements \JsonSerializable, \Countable
13+
abstract class AbstractKeyValueStore implements \JsonSerializable, \Countable, \Iterator
1414
{
1515
/**
1616
* @var array
@@ -27,6 +27,11 @@ public function count(): int
2727
return \count($this->data);
2828
}
2929

30+
public function current()
31+
{
32+
return $this->get($this->key());
33+
}
34+
3035
/**
3136
* Get value for a key.
3237
*
@@ -38,13 +43,13 @@ public function count(): int
3843
*/
3944
public function get($lookup)
4045
{
41-
if (empty($lookup)) {
46+
if (empty($lookup) && $lookup != 0) {
4247
return;
4348
}
4449

4550
$value = $this->data;
4651

47-
foreach (explode('.', $lookup) as $segment) {
52+
foreach (explode('.', (string) $lookup) as $segment) {
4853
if (\is_array($value) && array_key_exists($segment, $value)) {
4954
$value = $value[$segment];
5055
} else {
@@ -97,7 +102,7 @@ public function has($lookup): bool
97102
{
98103
$subArray = $this->data;
99104

100-
foreach (explode('.', $lookup) as $segment) {
105+
foreach (explode('.', (string) $lookup) as $segment) {
101106
if (\is_array($subArray) && array_key_exists($segment, $subArray)) {
102107
$subArray = $subArray[$segment];
103108
} else {
@@ -126,6 +131,21 @@ public function jsonSerialize()
126131
return $this->toArray();
127132
}
128133

134+
public function key()
135+
{
136+
return key($this->data);
137+
}
138+
139+
public function next(): void
140+
{
141+
next($this->data);
142+
}
143+
144+
public function rewind(): void
145+
{
146+
reset($this->data);
147+
}
148+
129149
/**
130150
* Assign a new key in store.
131151
*
@@ -184,4 +204,9 @@ public function toArray(): array
184204
{
185205
return $this->data;
186206
}
207+
208+
public function valid(): bool
209+
{
210+
return $this->key() !== null && $this->has($this->key());
211+
}
187212
}

src/Context.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ class Context extends AbstractKeyValueStore
3232
'hex',
3333
];
3434

35+
/** @var string[] */
36+
private $unsetOnClone = [];
37+
38+
/**
39+
* Do not propagate non-cloneable keys.
40+
*/
41+
public function __clone()
42+
{
43+
foreach ($this->unsetOnClone as $key) {
44+
unset($this->data[$key]);
45+
}
46+
}
47+
3548
/**
3649
* Instantiate a Context from a Zend Http Request.
3750
*/
@@ -100,12 +113,16 @@ public function isStatusCode($value): bool
100113
*
101114
* {@inheritdoc}
102115
*/
103-
public function set(string $lookup, $value): void
116+
public function set(string $lookup, $value, bool $cloneable = true): void
104117
{
105118
if ($this->isBuiltinValue($lookup)) {
106119
throw new \RuntimeException('Cannot override a builtin value.');
107120
}
108121

122+
if (!$cloneable) {
123+
$this->unsetOnClone[] = $lookup;
124+
}
125+
109126
parent::set($lookup, $value);
110127
}
111128

src/DefinitionIterator.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ public function __construct(Definition $rootDefinition, Context $context)
3333
$this->context = $context;
3434
}
3535

36+
/**
37+
* Make a copy of Context in clone.
38+
*/
39+
public function __clone()
40+
{
41+
$this->context = clone $this->context;
42+
}
43+
3644
/**
3745
* Travserse the Definition for a value, using a resolver if necessary.
3846
*
@@ -48,7 +56,7 @@ public function get($lookup, $definition = null)
4856
$updateContext = false;
4957
$originalLookup = '';
5058

51-
if ($this->isContextFullyPopulated($lookup)) {
59+
if ($definition === null && $this->isContextFullyPopulated($lookup)) {
5260
$value = $this->context->get($lookup);
5361

5462
return ($value instanceof Context) ? $value->toArray() : $value;
@@ -117,6 +125,11 @@ public function get($lookup, $definition = null)
117125
return $value;
118126
}
119127

128+
public function getContext(): Context
129+
{
130+
return $this->context;
131+
}
132+
120133
public function getRootDefinition(): Definition
121134
{
122135
return $this->rootDefinition;

src/Resolver/Conditional.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Upward\Resolver;
10+
11+
use Magento\Upward\Definition;
12+
13+
class Conditional extends AbstractResolver
14+
{
15+
/**
16+
* {@inheritdoc}
17+
*/
18+
public function getIndicator(): string
19+
{
20+
return 'when';
21+
}
22+
23+
/**
24+
* {@inheritdoc}
25+
*/
26+
public function isValid(Definition $definition): bool
27+
{
28+
if (!$definition->has($this->getIndicator())) {
29+
return false;
30+
}
31+
32+
if (!$definition->has('default')) {
33+
return false;
34+
}
35+
36+
foreach ($definition->get($this->getIndicator()) as $matcher) {
37+
if (!$matcher instanceof Definition) {
38+
return false;
39+
}
40+
41+
if (!$matcher->has('matches') || !\is_string($matcher->get('matches'))) {
42+
return false;
43+
}
44+
45+
if (!$matcher->has('pattern') || !\is_string($matcher->get('pattern'))) {
46+
return false;
47+
}
48+
49+
if (!$matcher->has('use')) {
50+
return false;
51+
}
52+
}
53+
54+
return true;
55+
}
56+
57+
/**
58+
* {@inheritdoc}
59+
*/
60+
public function resolve($definition)
61+
{
62+
if (!$definition instanceof Definition) {
63+
throw new \InvalidArgumentException('$definition must be an instance of ' . Definition::class);
64+
}
65+
66+
foreach ($definition->get($this->getIndicator()) as $matcher) {
67+
$value = (string) $this->getIterator()->get($matcher->get('matches'));
68+
$pattern = '/' . addcslashes($matcher->get('pattern'), '/') . '/';
69+
70+
if (preg_match($pattern, $value, $matches)) {
71+
// preface each key with '$'
72+
$matches = array_combine(array_map(function ($key) {
73+
return '$' . $key;
74+
}, array_keys($matches)), $matches);
75+
76+
// $matches is a temporary context value, so need a unique clone of Iterator & Context
77+
$iterator = clone $this->getIterator();
78+
$iterator->getContext()->set('$match', $matches, false);
79+
80+
return $iterator->get('use', $matcher);
81+
}
82+
}
83+
84+
return $this->getIterator()->get('default', $definition);
85+
}
86+
}

src/Resolver/Service.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Upward\Resolver;
10+
11+
use Magento\Upward\Definition;
12+
use Zend\Http\Client;
13+
14+
class Service extends AbstractResolver
15+
{
16+
/**
17+
* {@inheritdoc}
18+
*/
19+
public function getIndicator(): string
20+
{
21+
return 'url';
22+
}
23+
24+
/**
25+
* {@inheritdoc}
26+
*/
27+
public function isValid(Definition $definition): bool
28+
{
29+
if (!$definition->has('query')) {
30+
return false;
31+
}
32+
33+
if ($definition->has('method')) {
34+
$method = $this->getIterator()->get('method', $definition);
35+
if (!\in_array($method, ['GET', 'POST'])) {
36+
return false;
37+
}
38+
}
39+
40+
return parent::isValid($definition);
41+
}
42+
43+
/**
44+
* {@inheritdoc}
45+
*/
46+
public function resolve($definition)
47+
{
48+
if (!$definition instanceof Definition) {
49+
throw new \InvalidArgumentException('$definition must be an instance of ' . Definition::class);
50+
}
51+
52+
$url = $this->getIterator()->get('url', $definition);
53+
$query = $this->getIterator()->get('query', $definition);
54+
$method = $definition->has('method') ? $this->getIterator()->get('method', $definition) : 'POST';
55+
$headers = $definition->has('headers') ? $this->getIterator()->get('headers', $definition) : [];
56+
$variables = $definition->has('variables') ? $this->getIterator()->get('variables', $definition) : [];
57+
$requestParams = [
58+
'query' => $query,
59+
'variables' => $variables,
60+
];
61+
62+
$client = new Client($url);
63+
$client->setMethod($method);
64+
$client->setHeaders($headers);
65+
if ($method === 'POST') {
66+
$client->setRawBody(json_encode($requestParams));
67+
} elseif ($method === 'GET') {
68+
$client->setParameterGet($requestParams);
69+
}
70+
71+
$response = $client->send();
72+
73+
return json_decode($response->getBody(), true);
74+
}
75+
}

src/ResolverFactory.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ class ResolverFactory
2222
* @var array map of resolver key to their class implementation
2323
*/
2424
private static $resolverClasses = [
25-
self::RESOLVER_TYPE_DIRECTORY => Resolver\Directory::class,
26-
self::RESOLVER_TYPE_FILE => Resolver\File::class,
27-
self::RESOLVER_TYPE_INLINE => Resolver\Inline::class,
28-
self::RESOLVER_TYPE_PROXY => Resolver\Proxy::class,
29-
self::RESOLVER_TYPE_TEMPLATE => Resolver\Template::class,
25+
self::RESOLVER_TYPE_CONDITIONAL => Resolver\Conditional::class,
26+
self::RESOLVER_TYPE_DIRECTORY => Resolver\Directory::class,
27+
self::RESOLVER_TYPE_FILE => Resolver\File::class,
28+
self::RESOLVER_TYPE_INLINE => Resolver\Inline::class,
29+
self::RESOLVER_TYPE_PROXY => Resolver\Proxy::class,
30+
self::RESOLVER_TYPE_SERVICE => Resolver\Service::class,
31+
self::RESOLVER_TYPE_TEMPLATE => Resolver\Template::class,
3032
];
3133

3234
/**

test/AbstractKeyValueStoreTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ public function testGet(): void
6262

6363
verify($value11)->is()->instanceOf(AbstractKeyValueStore::class);
6464
verify($value11->get('key110'))->is()->sameAs('value 110');
65+
66+
$subject = $this->getSubject(['a', 'b', 'c']);
67+
68+
verify($subject->get(0))->is()->sameAs('a');
69+
verify($subject->get('0'))->is()->sameAs('a');
70+
verify($subject->get(''))->is()->null();
6571
}
6672

6773
public function testGetExistingParentLookup(): void
@@ -123,6 +129,31 @@ public function testHas(): void
123129
verify($subject->has('key1.key11.key112'))->is()->false();
124130
}
125131

132+
public function testIteratorFunction(): void
133+
{
134+
$subject = $this->getSubject(['a', 'b', 'c']);
135+
136+
$subject->rewind();
137+
138+
verify($subject->valid())->is()->true();
139+
verify($subject->current())->is()->sameAs('a');
140+
141+
$subject->next();
142+
verify($subject->valid())->is()->true();
143+
verify($subject->current())->is()->sameAs('b');
144+
145+
$subject->next();
146+
verify($subject->valid())->is()->true();
147+
verify($subject->current())->is()->sameAs('c');
148+
149+
$subject->next();
150+
verify($subject->valid())->is()->false();
151+
152+
$subject->rewind();
153+
verify($subject->valid())->is()->true();
154+
verify($subject->current())->is()->sameAs('a');
155+
}
156+
126157
/**
127158
* @depends testGet
128159
*/

0 commit comments

Comments
 (0)