Skip to content
This repository was archived by the owner on Feb 6, 2020. It is now read-only.

Commit 6293fb5

Browse files
author
fhein
committed
Fix bc break:
Reintroduced CyclicAliasException::fromAliasMap(). Reintroduced test for fromAliasMap(). Removed $cyclic variable (which was for testing only). Removed CyclicAliasException::getCycle().
1 parent ad4be59 commit 6293fb5

File tree

2 files changed

+287
-15
lines changed

2 files changed

+287
-15
lines changed

src/Exception/CyclicAliasException.php

Lines changed: 131 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,18 @@
77

88
namespace Zend\ServiceManager\Exception;
99

10+
use function array_filter;
11+
use function array_keys;
12+
use function array_map;
13+
use function array_values;
14+
use function implode;
15+
use function reset;
16+
use function serialize;
17+
use function sort;
1018
use function sprintf;
1119

1220
class CyclicAliasException extends InvalidArgumentException
1321
{
14-
public static $cycle;
1522
/**
1623
* @param string conflicting alias key
1724
* @param string[] $aliases map of referenced services, indexed by alias name (string)
@@ -28,16 +35,135 @@ public static function fromCyclicAlias($alias, $aliases)
2835
}
2936
$cycle .= ' -> ' . $alias . "\n";
3037

31-
// for testing
32-
self::$cycle = $cycle;
3338
return new self(sprintf(
3439
"A cycle was detected within the aliases defintions:\n%s",
3540
$cycle
3641
));
3742
}
3843

39-
public function getCycle()
44+
/**
45+
* @param string[] $aliases map of referenced services, indexed by alias name (string)
46+
*
47+
* @return self
48+
*/
49+
public static function fromAliasesMap(array $aliases)
50+
{
51+
$detectedCycles = array_filter(array_map(
52+
function ($alias) use ($aliases) {
53+
return self::getCycleFor($aliases, $alias);
54+
},
55+
array_keys($aliases)
56+
));
57+
58+
if (! $detectedCycles) {
59+
return new self(sprintf(
60+
"A cycle was detected within the following aliases map:\n\n%s",
61+
self::printReferencesMap($aliases)
62+
));
63+
}
64+
65+
return new self(sprintf(
66+
"Cycles were detected within the provided aliases:\n\n%s\n\n"
67+
. "The cycle was detected in the following alias map:\n\n%s",
68+
self::printCycles(self::deDuplicateDetectedCycles($detectedCycles)),
69+
self::printReferencesMap($aliases)
70+
));
71+
}
72+
73+
/**
74+
* Retrieves the cycle detected for the given $alias, or `null` if no cycle was detected
75+
*
76+
* @param string[] $aliases
77+
* @param string $alias
78+
*
79+
* @return array|null
80+
*/
81+
private static function getCycleFor(array $aliases, $alias)
82+
{
83+
$cycleCandidate = [];
84+
$targetName = $alias;
85+
86+
while (isset($aliases[$targetName])) {
87+
if (isset($cycleCandidate[$targetName])) {
88+
return $cycleCandidate;
89+
}
90+
91+
$cycleCandidate[$targetName] = true;
92+
93+
$targetName = $aliases[$targetName];
94+
}
95+
96+
return null;
97+
}
98+
99+
/**
100+
* @param string[] $aliases
101+
*
102+
* @return string
103+
*/
104+
private static function printReferencesMap(array $aliases)
40105
{
41-
return self::$cycle;
106+
$map = [];
107+
108+
foreach ($aliases as $alias => $reference) {
109+
$map[] = '"' . $alias . '" => "' . $reference . '"';
110+
}
111+
112+
return "[\n" . implode("\n", $map) . "\n]";
113+
}
114+
115+
/**
116+
* @param string[][] $detectedCycles
117+
*
118+
* @return string
119+
*/
120+
private static function printCycles(array $detectedCycles)
121+
{
122+
return "[\n" . implode("\n", array_map([__CLASS__, 'printCycle'], $detectedCycles)) . "\n]";
123+
}
124+
125+
/**
126+
* @param string[] $detectedCycle
127+
*
128+
* @return string
129+
*/
130+
private static function printCycle(array $detectedCycle)
131+
{
132+
$fullCycle = array_keys($detectedCycle);
133+
$fullCycle[] = reset($fullCycle);
134+
135+
return implode(
136+
' => ',
137+
array_map(
138+
function ($cycle) {
139+
return '"' . $cycle . '"';
140+
},
141+
$fullCycle
142+
)
143+
);
144+
}
145+
146+
/**
147+
* @param bool[][] $detectedCycles
148+
*
149+
* @return bool[][] de-duplicated
150+
*/
151+
private static function deDuplicateDetectedCycles(array $detectedCycles)
152+
{
153+
$detectedCyclesByHash = [];
154+
155+
foreach ($detectedCycles as $detectedCycle) {
156+
$cycleAliases = array_keys($detectedCycle);
157+
158+
sort($cycleAliases);
159+
160+
$hash = serialize(array_values($cycleAliases));
161+
162+
$detectedCyclesByHash[$hash] = isset($detectedCyclesByHash[$hash])
163+
? $detectedCyclesByHash[$hash]
164+
: $detectedCycle;
165+
}
166+
167+
return array_values($detectedCyclesByHash);
42168
}
43169
}

test/Exception/CyclicAliasExceptionTest.php

Lines changed: 156 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
*/
1616
class CyclicAliasExceptionTest extends TestCase
1717
{
18+
1819
/**
19-
* @dataProvider aliasesProvider
20+
* @dataProvider cyclicAliasProvider
2021
*
2122
* @param string conflicting alias key
2223
* @param string[] $aliases
@@ -29,15 +30,15 @@ public function testFromCyclicAlias($alias, array $aliases, $expectedMessage)
2930
$exception = CyclicAliasException::fromCyclicAlias($alias, $aliases);
3031

3132
self::assertInstanceOf(CyclicAliasException::class, $exception);
32-
self::assertSame($expectedMessage, $exception->getCycle());
33+
self::assertSame($expectedMessage, $exception->getMessage());
3334
}
3435

3536
/**
3637
* Test data provider for testFromCyclicAlias
3738
*
3839
* @return string[][]|string[][][]
3940
*/
40-
public function aliasesProvider()
41+
public function cyclicAliasProvider()
4142
{
4243
return
4344
[
@@ -46,31 +47,35 @@ public function aliasesProvider()
4647
[
4748
'a' => 'a',
4849
],
49-
"a -> a\n",
50+
"A cycle was detected within the aliases defintions:\n"
51+
. "a -> a\n",
5052
],
5153
[
5254
'a',
5355
[
5456
'a' => 'b',
5557
'b' => 'a'
5658
],
57-
"a -> b -> a\n",
59+
"A cycle was detected within the aliases defintions:\n"
60+
. "a -> b -> a\n",
5861
],
5962
[
6063
'b',
6164
[
6265
'a' => 'b',
6366
'b' => 'a'
6467
],
65-
"b -> a -> b\n",
68+
"A cycle was detected within the aliases defintions:\n"
69+
. "b -> a -> b\n",
6670
],
6771
[
6872
'b',
6973
[
7074
'a' => 'b',
7175
'b' => 'a',
7276
],
73-
"b -> a -> b\n",
77+
"A cycle was detected within the aliases defintions:\n"
78+
. "b -> a -> b\n",
7479
],
7580
[
7681
'a',
@@ -79,7 +84,8 @@ public function aliasesProvider()
7984
'b' => 'c',
8085
'c' => 'a',
8186
],
82-
"a -> b -> c -> a\n",
87+
"A cycle was detected within the aliases defintions:\n"
88+
. "a -> b -> c -> a\n",
8389
],
8490
[
8591
'b',
@@ -88,7 +94,8 @@ public function aliasesProvider()
8894
'b' => 'c',
8995
'c' => 'a',
9096
],
91-
"b -> c -> a -> b\n",
97+
"A cycle was detected within the aliases defintions:\n"
98+
. "b -> c -> a -> b\n",
9299
],
93100
[
94101
'c',
@@ -97,7 +104,146 @@ public function aliasesProvider()
97104
'b' => 'c',
98105
'c' => 'a',
99106
],
100-
"c -> a -> b -> c\n",
107+
"A cycle was detected within the aliases defintions:\n"
108+
. "c -> a -> b -> c\n",
109+
],
110+
];
111+
}
112+
113+
/**
114+
* @dataProvider aliasesProvider
115+
*
116+
* @param string[] $aliases
117+
* @param string $expectedMessage
118+
*
119+
* @return void
120+
*/
121+
public function testFromAliasesMap(array $aliases, $expectedMessage)
122+
{
123+
$exception = CyclicAliasException::fromAliasesMap($aliases);
124+
125+
self::assertInstanceOf(CyclicAliasException::class, $exception);
126+
self::assertSame($expectedMessage, $exception->getMessage());
127+
}
128+
129+
/**
130+
* @return string[][]|string[][][]
131+
*/
132+
public function aliasesProvider()
133+
{
134+
return [
135+
'empty set' => [
136+
[],
137+
'A cycle was detected within the following aliases map:
138+
139+
[
140+
141+
]'
142+
],
143+
'acyclic set' => [
144+
[
145+
'b' => 'a',
146+
'd' => 'c',
147+
],
148+
'A cycle was detected within the following aliases map:
149+
150+
[
151+
"b" => "a"
152+
"d" => "c"
153+
]'
154+
],
155+
'acyclic self-referencing set' => [
156+
[
157+
'b' => 'a',
158+
'c' => 'b',
159+
'd' => 'c',
160+
],
161+
'A cycle was detected within the following aliases map:
162+
163+
[
164+
"b" => "a"
165+
"c" => "b"
166+
"d" => "c"
167+
]'
168+
],
169+
'cyclic set' => [
170+
[
171+
'b' => 'a',
172+
'a' => 'b',
173+
],
174+
'Cycles were detected within the provided aliases:
175+
176+
[
177+
"b" => "a" => "b"
178+
]
179+
180+
The cycle was detected in the following alias map:
181+
182+
[
183+
"b" => "a"
184+
"a" => "b"
185+
]'
186+
],
187+
'cyclic set (indirect)' => [
188+
[
189+
'b' => 'a',
190+
'c' => 'b',
191+
'a' => 'c',
192+
],
193+
'Cycles were detected within the provided aliases:
194+
195+
[
196+
"b" => "a" => "c" => "b"
197+
]
198+
199+
The cycle was detected in the following alias map:
200+
201+
[
202+
"b" => "a"
203+
"c" => "b"
204+
"a" => "c"
205+
]'
206+
],
207+
'cyclic set + acyclic set' => [
208+
[
209+
'b' => 'a',
210+
'a' => 'b',
211+
'd' => 'c',
212+
],
213+
'Cycles were detected within the provided aliases:
214+
215+
[
216+
"b" => "a" => "b"
217+
]
218+
219+
The cycle was detected in the following alias map:
220+
221+
[
222+
"b" => "a"
223+
"a" => "b"
224+
"d" => "c"
225+
]'
226+
],
227+
'cyclic set + reference to cyclic set' => [
228+
[
229+
'b' => 'a',
230+
'a' => 'b',
231+
'c' => 'a',
232+
],
233+
'Cycles were detected within the provided aliases:
234+
235+
[
236+
"b" => "a" => "b"
237+
"c" => "a" => "b" => "c"
238+
]
239+
240+
The cycle was detected in the following alias map:
241+
242+
[
243+
"b" => "a"
244+
"a" => "b"
245+
"c" => "a"
246+
]'
101247
],
102248
];
103249
}

0 commit comments

Comments
 (0)