Skip to content

Commit 1f58c14

Browse files
committed
Move finding longest possible parent lookup to AbstractKeyValueStore and remove concept of "Resolvable" parent
Logic fits better here (next to get() and has()) and keeps the concept of Resolvers & ResolverFactory isolated to DefinitionIterator Some cleanup code in DefinitionIterator Unit tests for all of the above
1 parent 68e33d1 commit 1f58c14

File tree

5 files changed

+124
-28
lines changed

5 files changed

+124
-28
lines changed

src/AbstractKeyValueStore.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,31 @@ public function get($lookup)
5555
return \is_array($value) ? new static($value) : $value;
5656
}
5757

58+
/**
59+
* Find the longest portion of $lookup that exists.
60+
*/
61+
public function getExistingParentLookup(string $lookup): string
62+
{
63+
$subArray = $this->data;
64+
65+
$parentSegments = [];
66+
67+
foreach (explode('.', $lookup) as $segment) {
68+
if (\is_array($subArray)) {
69+
if (array_key_exists($segment, $subArray)) {
70+
$subArray = $subArray[$segment];
71+
$parentSegments[] = $segment;
72+
} else {
73+
break;
74+
}
75+
} else {
76+
break;
77+
}
78+
}
79+
80+
return implode('.', $parentSegments);
81+
}
82+
5883
/**
5984
* List keys.
6085
*/

src/Definition.php

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -77,28 +77,6 @@ public function getBasepath(): string
7777
return $this->basepath;
7878
}
7979

80-
public function getResolvableParent(string $lookup): ?string
81-
{
82-
$subArray = $this->data;
83-
84-
$parentSegments = [];
85-
86-
foreach (explode('.', $lookup) as $segment) {
87-
if (\is_array($subArray)) {
88-
if (array_key_exists($segment, $subArray)) {
89-
$subArray = $subArray[$segment];
90-
$parentSegments[] = $segment;
91-
} elseif (ResolverFactory::get(new static($subArray, $this->getBasepath()))) {
92-
return implode('.', $parentSegments);
93-
}
94-
} else {
95-
return null;
96-
}
97-
}
98-
99-
return null;
100-
}
101-
10280
/**
10381
* Get a dot separated address of where this node belongs in the definition tree.
10482
*/

src/DefinitionIterator.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,6 @@ public function get($lookup, $definition = null)
5454
return ($value instanceof Context) ? $value->toArray() : $value;
5555
}
5656

57-
if (\in_array($lookup, $this->lookupStack)) {
58-
throw new \RuntimeException('Definition appears to contain a loop: ' . json_encode($this->lookupStack));
59-
}
60-
6157
if ($definition === null) {
6258
$definition = $this->getRootDefinition();
6359
$updateContext = true;
@@ -67,7 +63,7 @@ public function get($lookup, $definition = null)
6763

6864
if ($definition instanceof Definition) {
6965
if (!$definition->has($lookup)) {
70-
if ($parentLookup = $definition->getResolvableParent($lookup)) {
66+
if ($parentLookup = $definition->getExistingParentLookup($lookup)) {
7167
$originalLookup = $lookup;
7268
$lookup = $parentLookup;
7369
} else {
@@ -86,6 +82,10 @@ public function get($lookup, $definition = null)
8682
$lookup = $definedValue->getTreeAddress();
8783
}
8884

85+
if (\in_array($lookup, $this->lookupStack)) {
86+
throw new \RuntimeException('Definition appears to contain a loop: ' . json_encode($this->lookupStack));
87+
}
88+
8989
$this->lookupStack[] = $lookup;
9090

9191
try {
@@ -162,7 +162,7 @@ private function getFromDefinedValue(string $lookup, $definedValue)
162162
}
163163

164164
if ($definedValue instanceof Definition && $definedValue->isList()) {
165-
return array_map(function ($key) use ($lookup) {
165+
return array_map(function ($key) {
166166
return $this->get($key);
167167
}, $definedValue->toArray());
168168
}
@@ -184,6 +184,7 @@ private function getFromDefinedValue(string $lookup, $definedValue)
184184
private function getFromResolver(string $lookup, $definedValue, Resolver\ResolverInterface $resolver)
185185
{
186186
$resolver->setIterator($this);
187+
187188
if ($definedValue instanceof Definition && !$resolver->isValid($definedValue)) {
188189
throw new \RuntimeException(sprintf(
189190
'Definition %s is not valid for %s.',

test/AbstractKeyValueStoreTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ public function testGet(): void
6464
verify($value11->get('key110'))->is()->sameAs('value 110');
6565
}
6666

67+
public function testGetExistingParentLookup(): void
68+
{
69+
$subject = $this->getSubject([
70+
'key0' => [
71+
'key00' => 'value',
72+
],
73+
]);
74+
75+
verify($subject->getExistingParentLookup('key0.key00'))->is()->sameAs('key0.key00');
76+
verify($subject->getExistingParentLookup('key0.key00.key000'))->is()->sameAs('key0.key00');
77+
verify($subject->getExistingParentLookup('key0.key01'))->is()->sameAs('key0');
78+
verify($subject->getExistingParentLookup('key1'))->is()->sameAs('');
79+
}
80+
6781
public function testGetKeys(): void
6882
{
6983
$subject = $this->getSubject();

test/DefinitionIteratorTest.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Mockery;
1717
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
1818
use PHPUnit\Framework\TestCase;
19+
use Zend\Http\Response;
1920
use function BeBat\Verify\verify;
2021

2122
class DefinitionIteratorTest extends TestCase
@@ -156,6 +157,83 @@ public function testLookupInContext(): void
156157
verify($iterator->get('key'))->is()->sameAs('context value');
157158
}
158159

160+
public function testParentResolver(): void
161+
{
162+
$context = new Context([]);
163+
$definition = new Definition(['key' => 'resolver-definition']);
164+
$childDefinition = new Definition(['child-key' => '200']);
165+
$iterator = new DefinitionIterator($definition, $context);
166+
$resolverFactory = Mockery::mock('alias:' . ResolverFactory::class);
167+
$mockResolver = Mockery::mock(ResolverInterface::class);
168+
169+
$resolverFactory->shouldReceive('get')
170+
->with('resolver-definition')
171+
->andReturn($mockResolver);
172+
173+
$mockResolver->shouldReceive('setIterator')
174+
->with($iterator);
175+
$mockResolver->shouldReceive('isValid')
176+
->with($definition)
177+
->andReturn(true);
178+
$mockResolver->shouldReceive('resolve')
179+
->with('resolver-definition')
180+
->andReturn($childDefinition);
181+
182+
verify($iterator->get('key.child-key'))->is()->sameAs('200');
183+
}
184+
185+
public function testParentResolverIsNotArray(): void
186+
{
187+
$context = new Context([]);
188+
$definition = new Definition(['key' => 'resolver-definition']);
189+
$iterator = new DefinitionIterator($definition, $context);
190+
$resolverFactory = Mockery::mock('alias:' . ResolverFactory::class);
191+
$mockResolver = Mockery::mock(ResolverInterface::class);
192+
193+
$resolverFactory->shouldReceive('get')
194+
->with('resolver-definition')
195+
->andReturn($mockResolver);
196+
197+
$mockResolver->shouldReceive('setIterator')
198+
->with($iterator);
199+
$mockResolver->shouldReceive('isValid')
200+
->with($definition)
201+
->andReturn(true);
202+
$mockResolver->shouldReceive('resolve')
203+
->with('resolver-definition')
204+
->andReturn('200');
205+
206+
$this->expectException(\RuntimeException::class);
207+
$this->expectExceptionMessage('Could not get nested value key.child-key from value of type string');
208+
209+
$iterator->get('key.child-key');
210+
}
211+
212+
public function testParentResolverIsResponse(): void
213+
{
214+
$context = new Context([]);
215+
$definition = new Definition(['key' => 'resolver-definition']);
216+
$response = new Response();
217+
$iterator = new DefinitionIterator($definition, $context);
218+
$resolverFactory = Mockery::mock('alias:' . ResolverFactory::class);
219+
$mockResolver = Mockery::mock(ResolverInterface::class);
220+
221+
$resolverFactory->shouldReceive('get')
222+
->with('resolver-definition')
223+
->andReturn($mockResolver);
224+
225+
$mockResolver->shouldReceive('setIterator')
226+
->with($iterator);
227+
$mockResolver->shouldReceive('isValid')
228+
->with($definition)
229+
->andReturn(true);
230+
$mockResolver->shouldReceive('resolve')
231+
->with('resolver-definition')
232+
->andReturn($response);
233+
234+
verify($iterator->get('key.child-key'))->is()->sameAs($response);
235+
}
236+
159237
public function testResolverValueDefinition(): void
160238
{
161239
$context = new Context([]);

0 commit comments

Comments
 (0)