TreeCompiler   F
last analyzed

Complexity

Total Complexity 115

Size/Duplication

Total Lines 840
Duplicated Lines 15.6 %

Coupling/Cohesion

Components 1
Dependencies 19

Test Coverage

Coverage 50.82%

Importance

Changes 0
Metric Value
wmc 115
lcom 1
cbo 19
dl 131
loc 840
ccs 279
cts 549
cp 0.5082
rs 1.263
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 37 2
A getXrefs() 0 4 1
A getIncludeKey() 0 4 1
A setIncludeKey() 0 5 1
B recursiveRemoveData() 0 12 6
B recursiveAddData() 0 17 6
C getXrefKeysToBeResolved() 0 51 8
C validateXrefTokenResolverOptions() 26 71 9
C parseXrefInfo() 15 65 12
F parseXrefTokenResolverDefinitions() 0 150 25
F recursiveCompileXref() 90 236 39
A compileXref() 0 6 1
A compileLocalFile() 0 5 1
A compileUrl() 0 5 1
A save() 0 15 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like TreeCompiler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TreeCompiler, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace ConfigToken;
4
5
use ConfigToken\Exception\NotRegisteredException;
6
use ConfigToken\TokenResolver\TokenResolverFactory;
7
use ConfigToken\TokenResolver\Types\RegisteredTokenResolver;
8
use ConfigToken\TokenResolver\Types\ScopeTokenResolver;
9
use ConfigToken\TreeCompiler\Exception\TokenResolverDefinitionException;
10
use ConfigToken\TreeCompiler\Exception\TreeCompilerFormatException;
11
use ConfigToken\TreeCompiler\Exception\CircularReferenceException;
12
use ConfigToken\TreeCompiler\XrefResolver\Exception\UnknownXrefException;
13
use ConfigToken\TreeCompiler\XrefResolver\Exception\XrefResolverFormatException;
14
use ConfigToken\TreeCompiler\XrefResolver\Types\InlineXrefResolver;
15
use ConfigToken\TreeCompiler\XrefResolver\Types\UrlXrefResolver;
16
use ConfigToken\TreeCompiler\XrefResolver\Types\LocalFileXrefResolver;
17
use ConfigToken\TreeCompiler\Xref;
18
use ConfigToken\TreeCompiler\XrefCollection;
19
use ConfigToken\TreeCompiler\XrefTokenResolver;
20
use ConfigToken\TreeCompiler\XrefTokenResolverCollection;
21
use ConfigToken\TreeSerializer\TreeSerializerFactory;
22
23
24
class TreeCompiler
25
{
26
    /** @var string */
27
    protected $includeKey = 'include';
28
    /** @var string */
29
    protected $includeXrefKey = 'xref';
30
    /** @var string */
31
    protected $includeXrefTypeKey = 'type';
32
    /** @var string */
33
    protected $includeXrefLocationKey = 'src';
34
    /** @var string */
35
    protected $includeXrefDataKey = 'data';
36
    /** @var string */
37
    protected $includeXrefResolversKey = 'resolve';
38
    /** @var string */
39
    protected $includeMainKey = 'main';
40
    /** @var string */
41
    protected $xrefTokenResolverTypeKey = 'type';
42
    /** @var string */
43
    protected $xrefTokenResolverOptionsKey = 'options';
44
    /** @var string */
45
    protected $xrefTokenResolverValuesKey = 'values';
46
    /** @var string */
47
    protected $xrefTokenResolverValuesXrefKey = 'values-xref';
48
    /** @var string */
49
    protected $xrefTokenResolverOptionIgnoreUnknownTokensKey = 'ignore-unknown-tokens';
50
    /** @var string */
51
    protected $xrefTokenResolverOptionIgnoreUnknownFiltersKey = 'ignore-unknown-filters';
52
    /** @var string */
53
    protected $xrefTokenResolverOptionTokenRegexKey = 'token-regex';
54
    /** @var string */
55
    protected $xrefTokenResolverOptionTokenPrefixKey = 'token-prefix';
56
    /** @var string */
57
    protected $xrefTokenResolverOptionTokenSuffixKey = 'token-suffix';
58
    /** @var string */
59
    protected $xrefTokenResolverOptionTokenFilterDelimiterKey = 'token-filter-delimiter';
60
    /** @var string */
61
    protected $xrefTokenResolverOptionScopeTokenNameKey = 'scope-token-name';
62
    /** @var string */
63
    protected $xrefTokenResolverOptionScopeTokenNameDelimiterKey = 'scope-token-name-delimiter';
64
    /** @var string */
65
    protected $xrefTokenResolverOptionScopeTokenLevelDelimiterKey = 'scope-token-level-delimiter';
66
    /** @var string */
67
    protected $xrefTokenResolverOptionIgnoreOutOfScopeKey = 'ignore-out-of-scope';
68
    /** @var array */
69
    protected $xrefTokenResolverOptionKeys = array();
70
    /** @var array */
71
    protected $xrefTokenResolverRequiredOptionKeys = array();
72
    /** @var string[] */
73
    protected $xrefTokenResolverOptionSetterMapping = array();
74
75
    /** @var string */
76
    protected $removeKey = 'remove';
77
    /** @var string */
78
    protected $addKey = 'add';
79
    /** @var string */
80
    protected $xrefTypeAndLocationDelimiter = ':';
81
82
    /** @var XrefCollection */
83
    protected $xrefs;
84
85
    const INCLUDE_TYPE_GROUP = 'group';
86
    const INCLUDE_TYPE_XREF = 'xref';
87
88 7
    public function __construct(XrefCollection $xrefs = null)
89
    {
90 7
        if (!isset($xrefs)) {
91 7
            $xrefs = new XrefCollection();
92 7
        }
93 7
        $this->xrefs = $xrefs;
94 7
        $stringType = gettype('');
95 7
        $booleanType = gettype(true);
96 7
        $this->xrefTokenResolverOptionKeys = array(
97 7
            RegisteredTokenResolver::getBaseType() => array(
98 7
                $this->xrefTokenResolverOptionIgnoreUnknownTokensKey => $booleanType,
99 7
                $this->xrefTokenResolverOptionIgnoreUnknownFiltersKey => $booleanType,
100 7
                $this->xrefTokenResolverOptionTokenRegexKey => $stringType,
101 7
                $this->xrefTokenResolverOptionTokenPrefixKey => $stringType,
102 7
                $this->xrefTokenResolverOptionTokenSuffixKey => $stringType,
103 7
                $this->xrefTokenResolverOptionTokenFilterDelimiterKey => $stringType,
104 7
            ),
105 7
            ScopeTokenResolver::getBaseType() => array(
106 7
                $this->xrefTokenResolverOptionIgnoreUnknownTokensKey => $booleanType,
107 7
                $this->xrefTokenResolverOptionIgnoreUnknownFiltersKey => $booleanType,
108 7
                $this->xrefTokenResolverOptionIgnoreOutOfScopeKey => $booleanType,
109 7
                $this->xrefTokenResolverOptionTokenRegexKey => $stringType,
110 7
                $this->xrefTokenResolverOptionTokenPrefixKey => $stringType,
111 7
                $this->xrefTokenResolverOptionTokenSuffixKey => $stringType,
112 7
                $this->xrefTokenResolverOptionTokenFilterDelimiterKey => $stringType,
113 7
                $this->xrefTokenResolverOptionScopeTokenNameKey => $stringType,
114 7
                $this->xrefTokenResolverOptionScopeTokenNameDelimiterKey => $stringType,
115 7
                $this->xrefTokenResolverOptionScopeTokenLevelDelimiterKey => $stringType,
116
            )
117 7
        );
118 7
        $this->xrefTokenResolverRequiredOptionKeys = array(
119 7
            RegisteredTokenResolver::getBaseType() => array(),
120 7
            ScopeTokenResolver::getBaseType() => array(
121 7
                $this->xrefTokenResolverOptionScopeTokenNameKey => true,
122
            )
123 7
        );
124 7
    }
125
126 7
    public function getXrefs()
127
    {
128 7
        return $this->xrefs;
129
    }
130
131
    /**
132
     * Get include key.
133
     *
134
     * @return string
135
     */
136
    public function getIncludeKey()
137
    {
138
        return $this->includeKey;
139
    }
140
141
    /**
142
     * Set include key.
143
     *
144
     * @param string $value The new value.
145
     * @return $this
146
     */
147
    public function setIncludeKey($value)
148
    {
149
        $this->includeKey = $value;
150
        return $this;
151
    }
152
153
    /**
154
     * Recursively remove the keys of one array from another.
155
     *
156
     * @param array $toRemove The array containing the keys to be removed.
157
     * @param array $removeFrom The array from which to remove keys.
158
     */
159 1
    public function recursiveRemoveData(array &$toRemove, array &$removeFrom)
160
    {
161 1
        foreach ($toRemove as $keyToRemove => $childKeysToRemove) {
162 1
            if (is_array($childKeysToRemove)) {
163
                if (isset($removeFrom[$keyToRemove]) && is_array($removeFrom[$keyToRemove])) {
164
                    $this->recursiveRemoveData($toRemove[$keyToRemove], $removeFrom[$keyToRemove]);
165
                }
166 1
            } else if (array_key_exists($keyToRemove, $removeFrom)) {
167 1
                unset($removeFrom[$keyToRemove]);
168 1
            }
169 1
        }
170 1
    }
171
172
    /**
173
     * Recursively add and override keys from one array into another.
174
     *
175
     * @param array $addFrom The source array.
176
     * @param array $addTo The destination array.
177
     */
178 5
    public function recursiveAddData(array &$addFrom, array &$addTo)
179
    {
180 5
        if (empty($addTo)) {
181
            /** @noinspection PhpUnusedLocalVariableInspection */
182 5
            $addTo = $addFrom;
183 5
            return;
184
        }
185 2
        foreach ($addFrom as $keyToAdd => $childKeys) {
186 2
            if (is_array($childKeys)) {
187
                if (isset($addTo[$keyToAdd]) && is_array($addTo[$keyToAdd])) {
188
                    $this->recursiveAddData($addFrom[$keyToAdd], $addTo[$keyToAdd]);
189
                    continue;
190
                }
191
            }
192 2
            $addTo[$keyToAdd] = $addFrom[$keyToAdd];
193 2
        }
194 2
    }
195
196
    /**
197
     * Retrieve a list of Xref keys to be resolved.
198
     *
199
     * @param Xref $xref
200
     * @param array $xrefDataInclude
201
     * @param array $xrefDataIncludeXrefs
202
     * @param string $includeType
203
     * @param string|array $includeTypeValue
204
     * @return array
205
     *
206
     * @throws XrefResolverFormatException
207
     */
208 7
    protected function getXrefKeysToBeResolved(Xref $xref, $xrefDataInclude, $xrefDataIncludeXrefs,
209
                                               $includeType, $includeTypeValue)
210
    {
211 7
        $result = array();
212
        switch ($includeType) {
213 7
            case static::INCLUDE_TYPE_XREF:
214
                $missing = array();
215
                if (!is_array($includeTypeValue)) {
216
                    throw new XrefResolverFormatException(
217
                        $xref,
218
                        sprintf(
219
                            'Include type value must be an array, %s given.',
220
                            gettype($includeTypeValue)
221
                        )
222
                    );
223
                }
224
                foreach ($includeTypeValue as $xrefKey) {
225
                    if (!isset($xrefDataIncludeXrefs[$xrefKey])) {
226
                        $missing[] = $xrefKey;
227
                    }
228
                }
229
                if (empty($missing)) {
230
                    unset($missing);
231
                } else {
232
                    throw new XrefResolverFormatException(
233
                        $xref,
234
                        sprintf(
235
                            "Required to explicitly include the list of Xref keys [\"%s\"] " .
236
                            "but unable to find [\"%s\"].",
237
                            implode('", "', $includeTypeValue),
238
                            implode('", "', $missing)
239
                        )
240
                    );
241
                }
242
                $result = $includeTypeValue;
243
                break;
244 7
            case static::INCLUDE_TYPE_GROUP:
245 7
                if (!isset($xrefDataInclude[$includeTypeValue])) {
246
                    throw new XrefResolverFormatException(
247
                        $xref,
248
                        sprintf(
249
                            "Unable to find the required include group of Xref keys named \"%s\"",
250
                            $includeTypeValue
251
                        )
252
                    );
253
                }
254 7
                $result = $xrefDataInclude[$includeTypeValue];
255 7
                break;
256
        }
257 7
        return $result;
258
    }
259
260
    /**
261
     * @param $xrefKey
262
     * @param $tokenResolverDefinitionIndex
263
     * @param $tokenResolverBaseType
264
     * @param $options
265
     * @throws TokenResolverDefinitionException
266
     */
267 5
    protected function validateXrefTokenResolverOptions($xrefKey, $tokenResolverDefinitionIndex, $tokenResolverBaseType,
268
                                                        $options)
269
    {
270 5
        if (!is_array($options)) {
271
            throw new TokenResolverDefinitionException(
272
                sprintf(
273
                    'The "%s" key for token resolver definition at index %d for Xref key "%s" must be an associative array.',
274
                    $this->xrefTokenResolverOptionsKey,
275
                    $tokenResolverDefinitionIndex,
276
                    $xrefKey
277
                )
278
            );
279
        }
280 5
        $required = $this->xrefTokenResolverRequiredOptionKeys[$tokenResolverBaseType];
281 5
        $unknown = array();
282 5
        $found = array();
283 5
        foreach ($options as $optionKey => $optionValue) {
284 5
            if (!isset($this->xrefTokenResolverOptionKeys[$tokenResolverBaseType][$optionKey])) {
285
                $unknown[] = $optionKey;
286
            } else {
287 5
                $found[$optionKey] = $optionValue;
288 5
                if (isset($required[$optionKey])) {
289
                    unset($required[$optionKey]);
290
                }
291
            }
292 5
        }
293 5 View Code Duplication
        if (count($required) > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
294
            throw new TokenResolverDefinitionException(
295
                sprintf(
296
                    'Missing required option(s) "%s" for token resolver definition based on the "%s" type identifier at ' .
297
                    'index %d for Xref key "%s".',
298
                    implode('", "', $required),
299
                    $tokenResolverBaseType,
300
                    $this->xrefTokenResolverOptionsKey,
301
                    $tokenResolverDefinitionIndex,
302
                    $xrefKey
303
                )
304
            );
305
        }
306 5 View Code Duplication
        if (count($unknown) > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
307
            throw new TokenResolverDefinitionException(
308
                sprintf(
309
                    'Unknown option(s) "%s" for token resolver definition based on the "%s" type identifier at ' .
310
                    'index %d for Xref key "%s".',
311
                    implode('", "', $unknown),
312
                    $tokenResolverBaseType,
313
                    $this->xrefTokenResolverOptionsKey,
314
                    $tokenResolverDefinitionIndex,
315
                    $xrefKey
316
                )
317
            );
318
        }
319 5
        foreach ($found as $optionKey => $optionValue) {
320 5
            $valueType = gettype($optionValue);
321 5
            $expectedValueType = $this->xrefTokenResolverOptionKeys[$tokenResolverBaseType][$optionKey];
322 5
            if ($valueType != $expectedValueType) {
323
                throw new TokenResolverDefinitionException(
324
                    sprintf(
325
                        'Wrong type "%s" instead of "%s" for option "%s" for token resolver definition based on the ' .
326
                        '"%s" type identifier at index %d for Xref key "%s".',
327
                        $valueType,
328
                        $expectedValueType,
329
                        $tokenResolverBaseType,
330
                        $this->xrefTokenResolverOptionsKey,
331
                        $tokenResolverDefinitionIndex,
332
                        $xrefKey
333
                    )
334
                );
335
            }
336 5
        }
337 5
    }
338
339
    /**
340
     * @param string $xrefKey
341
     * @param string|array $xrefInfo
342
     * @param XrefTokenResolverCollection $xrefTokenResolvers
343
     * @param Xref[] $xrefPath
344
     * @return Xref|mixed
345
     * @throws TreeCompilerFormatException
346
     * @throws \Exception
347
     */
348 7
    protected function parseXrefInfo($xrefKey, $xrefInfo, XrefTokenResolverCollection $xrefTokenResolvers = null, $xrefPath)
349
    {
350 7
        $xrefData = null;
351 7
        if (gettype($xrefInfo) == 'string') {
352 1
            list($xrefType, $xrefLocation) = Xref::parseDefinitionString($xrefInfo, $this->xrefTypeAndLocationDelimiter);
353 1
        } else {
354 7
            if (!is_array($xrefInfo)) {
355
                throw new TreeCompilerFormatException(
356
                    sprintf(
357
                        'The Xref definition key "%s" must be a string with the format xref_type %s xref_location ' .
358
                        'or an associative array with the keys "%s" for type and "%s" for location.',
359
                        $this->xrefTypeAndLocationDelimiter,
360
                        $this->includeXrefTypeKey,
361
                        $this->includeXrefLocationKey
362
                    )
363
                );
364
            }
365
366 7
            $requiredKeyErrorMessage = 'The "%s" key is missing from the Xref definition with the key "%s".';
367 7 View Code Duplication
            if (!isset($xrefInfo[$this->includeXrefTypeKey])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
368
                throw new TreeCompilerFormatException(
369
                    sprintf($requiredKeyErrorMessage, $this->includeXrefTypeKey, $xrefKey)
370
                );
371
            }
372 7
            $xrefType = $xrefInfo[$this->includeXrefTypeKey];
373 7
            if ($xrefType == InlineXrefResolver::getType()) {
374 View Code Duplication
                if (!isset($xrefInfo[$this->includeXrefDataKey])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
375
                    throw new TreeCompilerFormatException(
376
                        sprintf($requiredKeyErrorMessage, $this->includeXrefDataKey, $xrefKey)
377
                    );
378
                }
379
                $xrefData = $xrefInfo[$this->includeXrefDataKey];
380
                $xrefLocation = Xref::computeId($xrefType, serialize($xrefData));
381
            } else {
382 7 View Code Duplication
                if (!isset($xrefInfo[$this->includeXrefLocationKey])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
383
                    throw new TreeCompilerFormatException(
384
                        sprintf($requiredKeyErrorMessage, $this->includeXrefLocationKey, $xrefKey)
385
                    );
386
                }
387 7
                $xrefLocation = $xrefInfo[$this->includeXrefLocationKey];
388
            }
389
        }
390
391 7
        if (isset($xrefData)) {
392
            if (isset($xrefTokenResolvers)) {
393
                $xrefTokenResolvers->applyToArray($xrefData);
394
            }
395
        } else {
396 7
            if (isset($xrefTokenResolvers)) {
397 3
                $xrefLocation = $xrefTokenResolvers->applyToString($xrefLocation);
398 3
            }
399 7
            $xrefLocation = Xref::computeAbsoluteLocation($xrefType, $xrefLocation, $xrefPath);
400
        }
401
402 7
        $xrefId = Xref::computeId($xrefType, $xrefLocation);
403 7
        if ($this->xrefs->hasById($xrefId)) {
404 7
            return $this->xrefs[$xrefId];
405
        }
406
407
        $xref = new Xref($xrefType, $xrefLocation);
408
        if (isset($xrefData)) {
409
            $xref->setData($xrefData)->setResolved(true);
410
        }
411
        return $xref;
412
    }
413
414
    /**
415
     * Parse the array corresponding to the $includeKey:$includeXrefKey:<xref name>:$includeXrefResolveKey.
416
     *
417
     * @param string $xrefKey
418
     * @param array $tokenResolversInfo
419
     * @param XrefTokenResolverCollection $tokenResolvers
420
     * @param Xref[] $xrefPath
421
     * @return XrefTokenResolverCollection
422
     * @throws \Exception
423
     */
424 7
    protected function parseXrefTokenResolverDefinitions($xrefKey, $tokenResolversInfo, XrefTokenResolverCollection $tokenResolvers = null, $xrefPath)
425
    {
426 7
        $resolverValues = array();
427 7
        $tokenResolverDefinitionIndex = 0;
428
        // validate
429 7
        if (!is_array($tokenResolversInfo)) {
430
            throw new TokenResolverDefinitionException(
431
                sprintf(
432
                    'Token resolver definitions at index %d for Xref key "%s" must be an array. (%s)',
433
                    $tokenResolverDefinitionIndex,
434
                    $xrefKey,
435
                    json_encode($tokenResolversInfo)
436
                )
437
            );
438
        }
439 7
        foreach ($tokenResolversInfo as $tokenResolverKey => $tokenResolverInfo) {
440 7
            if (!is_array($tokenResolverInfo)) {
441 1
                throw new TokenResolverDefinitionException(
442 1
                    sprintf(
443 1
                        'Token resolver definition at index %d for Xref key "%s" must be an associative array. (%s)',
444 1
                        $tokenResolverKey,
445 1
                        $xrefKey,
446 1
                        json_encode($tokenResolversInfo)
447 1
                    )
448 1
                );
449
            }
450 6
            if (!isset($tokenResolverInfo[$this->xrefTokenResolverTypeKey])) {
451 1
                throw new TokenResolverDefinitionException(
452 1
                    sprintf(
453 1
                        "Token resolver definition at index %d for Xref key \"%s\" is missing the \"%s\" type identifier key.\n%s",
454 1
                        $tokenResolverDefinitionIndex,
455 1
                        $xrefKey,
456 1
                        $this->xrefTokenResolverTypeKey,
457 1
                        json_encode($tokenResolverInfo)
458 1
                    )
459 1
                );
460
            }
461 5
            $tokenResolverType = $tokenResolverInfo[$this->xrefTokenResolverTypeKey];
462 5
            if (!TokenResolverFactory::isRegisteredByType($tokenResolverType)) {
463
                throw new TokenResolverDefinitionException(
464
                    sprintf(
465
                        'Unknown token resolver type identifier "%s" at index %d for Xref key "%s".',
466
                        $tokenResolverType,
467
                        $tokenResolverDefinitionIndex,
468
                        $xrefKey
469
                    )
470
                );
471
            }
472 5
            $hasValues = isset($tokenResolverInfo[$this->xrefTokenResolverValuesKey]);
473 5
            $hasValuesXref = isset($tokenResolverInfo[$this->xrefTokenResolverValuesXrefKey]);
474 5
            if ((!$hasValues) && (!$hasValuesXref)) {
475
                throw new TokenResolverDefinitionException(
476
                    sprintf(
477
                        'Token resolver definition at index %d for Xref key "%s" does not have a "%s" key or a "%s" key.',
478
                        $tokenResolverDefinitionIndex,
479
                        $xrefKey,
480
                        $this->xrefTokenResolverValuesKey,
481
                        $this->xrefTokenResolverValuesXrefKey
482
                    )
483
                );
484
            }
485 5
            $tokenResolverBaseType = TokenResolverFactory::getBaseTypeForType($tokenResolverType);
486 5
            if (isset($tokenResolverInfo[$this->xrefTokenResolverOptionsKey])) {
487 5
                $options = $tokenResolverInfo[$this->xrefTokenResolverOptionsKey];
488 5
                $this->validateXrefTokenResolverOptions(
489 5
                    $xrefKey,
490 5
                    $tokenResolverDefinitionIndex,
491 5
                    $tokenResolverBaseType,
492
                    $options
493 5
                );
494 5
            }
495 5
            if ($hasValues) {
496 5
                $values = $tokenResolverInfo[$this->xrefTokenResolverValuesKey];
497 5
            } else { // has values xref
498 1
                $xrefInfo = $tokenResolverInfo[$this->xrefTokenResolverValuesXrefKey];
499 1
                $xref = $this->parseXrefInfo(
500 1
                    sprintf('%s.%s[%d]', $xrefKey, $this->includeXrefResolversKey, $tokenResolverDefinitionIndex),
501 1
                    $xrefInfo,
502 1
                    $tokenResolvers,
503
                    $xrefPath
504 1
                );
505
                // pass down current token resolvers
506 1
                $values = $this->recursiveCompileXref($xref, $tokenResolvers, null, null, $xrefPath);
507
            }
508 5
            if (!is_array($values)) {
509
                throw new TokenResolverDefinitionException(
510
                    sprintf(
511
                        'The "%s" key must be an associative array for token resolver definition at ' .
512
                        'index %d for Xref key "%s".',
513
                        $this->xrefTokenResolverValuesKey,
514
                        $tokenResolverDefinitionIndex,
515
                        $xrefKey
516
                    )
517
                );
518
            }
519 5
            $resolverValues[$tokenResolverKey] = $values;
520 5
            $tokenResolverDefinitionIndex++;
521 5
        }
522
523 5
        $result = new XrefTokenResolverCollection();
524
        // parse
525 5
        foreach ($tokenResolversInfo as $tokenResolverKey => $tokenResolverInfo) {
526 5
            if (isset($tokenResolverInfo[$this->xrefTokenResolverOptionsKey])) {
527 5
                $options = $tokenResolverInfo[$this->xrefTokenResolverOptionsKey];
528 5
            } else {
529
                $options = null;
530
            }
531 5
            $tokenResolver = TokenResolverFactory::get($tokenResolverInfo[$this->xrefTokenResolverTypeKey]);
532 5
            $xrefTokenResolver = new XrefTokenResolver($tokenResolver);
533 5
            $values = $resolverValues[$tokenResolverKey];
534 5
            $xrefTokenResolver->setRegisteredTokenValues($values);
535 5
            if (isset($options)) {
536 5
                if (isset($options[$this->xrefTokenResolverOptionIgnoreUnknownTokensKey])) {
537
                    $tokenResolver->setIgnoreUnknownTokens($options[$this->xrefTokenResolverOptionIgnoreUnknownTokensKey]);
538
                }
539 5
                if ($tokenResolver::getBaseType() == ScopeTokenResolver::getBaseType()) {
540
                    /** @var ScopeTokenResolver $tokenResolver */
541
                    if (isset($options[$this->xrefTokenResolverOptionScopeTokenNameKey])) {
542
                        $tokenResolver->setScopeTokenName($options[$this->xrefTokenResolverOptionScopeTokenNameKey]);
543
                    }
544
                    if (isset($options[$this->xrefTokenResolverOptionScopeTokenNameDelimiterKey])) {
545
                        $tokenResolver->setScopeTokenNameDelimiter($options[$this->xrefTokenResolverOptionScopeTokenNameDelimiterKey]);
546
                    }
547
                    if (isset($options[$this->xrefTokenResolverOptionScopeTokenLevelDelimiterKey])) {
548
                        $tokenResolver->setScopeLevelDelimiter($options[$this->xrefTokenResolverOptionScopeTokenLevelDelimiterKey]);
549
                    }
550
                    if (isset($options[$this->xrefTokenResolverOptionIgnoreOutOfScopeKey])) {
551
                        $tokenResolver->setIgnoreOutOfScope($options[$this->xrefTokenResolverOptionIgnoreOutOfScopeKey]);
552
                    }
553
                }
554 5
                if (isset($options[$this->xrefTokenResolverOptionIgnoreUnknownFiltersKey])) {
555
                    $xrefTokenResolver->setIgnoreUnknownFilters($options[$this->xrefTokenResolverOptionIgnoreUnknownFiltersKey]);
556
                }
557 5
                if (isset($options[$this->xrefTokenResolverOptionTokenRegexKey])) {
558
                    $xrefTokenResolver->setTokenRegex($options[$this->xrefTokenResolverOptionTokenRegexKey]);
559
                }
560 5
                if (isset($options[$this->xrefTokenResolverOptionTokenPrefixKey])) {
561 5
                    $xrefTokenResolver->setTokenPrefix($options[$this->xrefTokenResolverOptionTokenPrefixKey]);
562 5
                }
563 5
                if (isset($options[$this->xrefTokenResolverOptionTokenSuffixKey])) {
564 5
                    $xrefTokenResolver->setTokenSuffix($options[$this->xrefTokenResolverOptionTokenSuffixKey]);
565 5
                }
566 5
                if (isset($options[$this->xrefTokenResolverOptionTokenFilterDelimiterKey])) {
567
                    $xrefTokenResolver->setTokenFilterDelimiter($options[$this->xrefTokenResolverOptionTokenFilterDelimiterKey]);
568
                }
569 5
            }
570 5
            $result->add($xrefTokenResolver);
571 5
        }
572 5
        return $result;
573
    }
574
575
    /**
576
     * Recursively resolve Xrefs and compile data.
577
     *
578
     * @param Xref $xref
579
     * @param XrefTokenResolverCollection $tokenResolvers
580
     * @param string|null $includeType
581
     * @param string|array|null $includeTypeValue
582
     * @param array $xrefPath
583
     * @return array
584
     *
585
     * @throws CircularReferenceException
586
     * @throws Exception\AlreadyRegisteredException
587
     * @throws TreeCompiler\XrefResolver\Exception\UnknownXrefTypeException
588
     * @throws UnknownXrefException
589
     * @throws XrefResolverFormatException
590
     * @throws \Exception
591
     */
592 7
    protected function recursiveCompileXref(Xref $xref, XrefTokenResolverCollection $tokenResolvers = null,
593
                                            $includeType = null, $includeTypeValue = null, &$xrefPath)
594
    {
595 7
        static $XREF_KEY = 0;
596 7
        static $XREF_RESOLVERS_KEY = 1;
597
598 7
        if (!isset($includeType)) {
599 7
            $includeType = static::INCLUDE_TYPE_GROUP;
600 7
        }
601
602
        switch ($includeType) {
603 7
            case static::INCLUDE_TYPE_GROUP:
604 7
                if (!isset($includeTypeValue)) {
605 7
                    $includeTypeValue = $this->includeMainKey;
606 7
                } else if (gettype($includeTypeValue) != 'string') {
607
                    throw new XrefResolverFormatException(
608
                        $xref,
609
                        sprintf(
610
                            "Include type value must be a string representing the include group name. " .
611
                            "\"%s\" given instead.",
612
                            gettype($includeTypeValue)
613
                        )
614
                    );
615
                }
616 7
                break;
617
            case static::INCLUDE_TYPE_XREF:
618
                if (!is_array($includeTypeValue) || empty($includeTypeValue)) {
619
                    throw new XrefResolverFormatException(
620
                        $xref,
621
                        "Include type value must be a non-empty array of strings for named includes."
622
                    );
623
                }
624
                break;
625
            default:
626
                throw new \Exception(sprintf('Unknown include type "%s".', $includeType));
627
        }
628 7
        $mustIncludeSpecificGroup = $includeTypeValue != $this->includeMainKey;
629
630 7
        $xref->resolve();
631
632 7
        $xrefData = $xref->getData();
633 7
        if (empty($xrefData)) {
634
            if (!is_array($xrefData)) {
635
                throw new XrefResolverFormatException(
636
                    $xref,
637
                    "De-serialized data must be an array."
638
                );
639
            }
640
            return array();
641
        }
642
643 7 View Code Duplication
        if ((!isset($xrefData[$this->includeKey])) || (!is_array($xrefData[$this->includeKey]))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
644
            switch ($includeType) {
645 5
                case static::INCLUDE_TYPE_XREF:
646
                    throw new XrefResolverFormatException(
647
                        $xref,
648
                        sprintf(
649
                            "Required to explicitly include the list of Xref keys [\"%s\"] " .
650
                            "but the \"%s\" key is missing from the first level.",
651
                            implode('", "', $includeTypeValue),
652
                            $this->includeKey
653
                        )
654
                    );
655 5
                case static::INCLUDE_TYPE_GROUP:
656 5
                    if (!$mustIncludeSpecificGroup) {
657 5
                        if (isset($xrefData[$this->addKey])) {
658 1
                            $result = $xrefData[$this->addKey];
659 1
                        } else {
660 5
                            if (isset($xrefData[$this->removeKey])) {
661
                                unset($xrefData[$this->removeKey]);
662
                            }
663 5
                            $result = $xrefData;
664
                        }
665 5
                        if (isset($tokenResolvers)) {
666 5
                            $tokenResolvers->applyToArray($result);
667 5
                        }
668 5
                        return $result;
669
                    }
670
                    throw new XrefResolverFormatException(
671
                        $xref,
672
                        sprintf(
673
                            "Required to explicitly include the \"%s\" group of Xref keys " .
674
                            "but the \"%s\" key is missing from the first level.",
675
                            $includeTypeValue,
676
                            $this->includeKey
677
                        )
678
                    );
679
            }
680
        }
681 7
        $xrefDataInclude = &$xrefData[$this->includeKey];
682
683 7 View Code Duplication
        if ((!isset($xrefDataInclude[$this->includeXrefKey])) || (!is_array($xrefDataInclude[$this->includeXrefKey]))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
684
            switch ($includeType) {
685
                case static::INCLUDE_TYPE_XREF:
686
                    throw new XrefResolverFormatException(
687
                        $xref,
688
                        sprintf(
689
                            "Required to explicitly include the list of Xref keys [\"%s\"] " .
690
                            "but the \"%s\" key is missing from the \"%s\" key on the first level.",
691
                            implode('", "', $includeTypeValue),
692
                            $this->includeXrefKey,
693
                            $this->includeKey
694
                        )
695
                    );
696
                case static::INCLUDE_TYPE_GROUP:
697
                    if (!$mustIncludeSpecificGroup) {
698
                        if (isset($xrefData[$this->addKey])) {
699
                            $result = $xrefData[$this->addKey];
700
                        } else {
701
                            if (isset($xrefData[$this->removeKey])) {
702
                                unset($xrefData[$this->removeKey]);
703
                            }
704
                            $result = $xrefData;
705
                        }
706
                        if (isset($tokenResolvers)) {
707
                            $tokenResolvers->applyToArray($result);
708
                        }
709
                        return $result;
710
                    }
711
                    throw new XrefResolverFormatException(
712
                        $xref,
713
                        sprintf(
714
                            "Required to explicitly include the \"%s\" group of Xref keys " .
715
                            "but the \"%s\" key is missing from the \"%s\" key on the first level.",
716
                            $includeTypeValue,
717
                            $this->includeXrefKey,
718
                            $this->includeKey
719
                        )
720
                    );
721
            }
722
        }
723 7
        $xrefDataIncludeXrefs = &$xrefDataInclude[$this->includeXrefKey];
724
725 7
        $xrefKeysToBeResolved = $this->getXrefKeysToBeResolved(
726 7
            $xref,
727 7
            $xrefDataInclude,
728 7
            $xrefDataIncludeXrefs,
729 7
            $includeType,
730
            $includeTypeValue
731 7
        );
732
733 7
        $xrefsToBeParsed = array();
734 7
        foreach ($xrefKeysToBeResolved as $xrefKeyToBeResolved) {
735 7
            if (!isset($xrefDataIncludeXrefs[$xrefKeyToBeResolved])) {
736
                throw new UnknownXrefException(
737
                    sprintf(
738
                        "Unable to find the required Xref definition named \"%s\".",
739
                        $xrefKeyToBeResolved
740
                    )
741
                );
742
            }
743 7
            $xrefsToBeParsed[$xrefKeyToBeResolved] = $xrefDataIncludeXrefs[$xrefKeyToBeResolved];
744 7
        }
745 7
        unset($xrefDataIncludeXrefs);
746
747 7
        $xrefId = $xref->getId();
748 7
        $xrefPath[$xrefId] = $xref;
749
750 7
        $xrefsToBeResolved = array();
751 7
        foreach ($xrefsToBeParsed as $xrefKey => $xrefInfo) {
752 7
            $includedXref = $this->parseXrefInfo(
753 7
                $xrefKey,
754 7
                $xrefInfo,
755 7
                $tokenResolvers,
756
                $xrefPath
757 7
            );
758
759 7
            if (is_array($xrefInfo) && isset($xrefInfo[$this->includeXrefResolversKey])) {
760 7
                $xrefTokenResolvers = $this->parseXrefTokenResolverDefinitions(
761 7
                    $xrefKey,
762 7
                    $xrefInfo[$this->includeXrefResolversKey],
763 7
                    $tokenResolvers,
764
                    $xrefPath
765 7
                );
766 5
            } else {
767 2
                $xrefTokenResolvers = null;
768
            }
769
770 5
            $xrefsToBeResolved[] = array(
771 5
                $XREF_KEY => $includedXref,
772
                $XREF_RESOLVERS_KEY => $xrefTokenResolvers
773 5
            );
774 5
        }
775
776
        /** @var array $includedXrefs */
777 5
        $result = array();
778 5
        foreach ($xrefsToBeResolved as $xrefToBeResolved) {
779
            /** @var Xref $includedXref */
780 5
            $includedXref = $xrefToBeResolved[$XREF_KEY];
781 5
            if (isset($xrefPath[$includedXref->getId()])) {
782
                throw new CircularReferenceException(
783
                    sprintf(
784
                        'Tree compiler encountered circular reference at "%s" in path ["%s"].',
785
                        sprintf('%s:%s', $includedXref->getType(), $includedXref->getLocation()),
786
                        implode('", "', $xrefPath)
787
                    )
788
                );
789
            }
790 5
            $this->xrefs->add($includedXref);
791
            /** @var XrefTokenResolverCollection $includeTokenResolvers */
792 5
            $includeTokenResolvers = $xrefToBeResolved[$XREF_RESOLVERS_KEY];
793 5
            if (isset($includeTokenResolvers)) {
794 5
                $downTokenResolvers = $includeTokenResolvers;
795 5
            } else {
796 2
                $downTokenResolvers = new XrefTokenResolverCollection();
797
            }
798 5
            if (isset($tokenResolvers)) {
799 3
                $downTokenResolvers->addCollection($tokenResolvers);
800 3
            }
801 5
            $includeData = $this->recursiveCompileXref(
802 5
                $includedXref,
803 5
                $downTokenResolvers,
804 5
                static::INCLUDE_TYPE_GROUP,
805 5
                $this->includeMainKey,
806
                $xrefPath
807 5
            );
808 5
            $this->recursiveAddData($includeData, $result);
809 5
        }
810 5
        unset($xrefPath[$xrefId]);
811
812 5 View Code Duplication
        if (isset($xrefData[$this->removeKey])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
813 1
            if (isset($tokenResolvers)) {
814 1
                $tokenResolvers->applyToArray($xrefData[$this->removeKey]);
815 1
            }
816 1
            $this->recursiveRemoveData($xrefData[$this->removeKey], $result);
817 1
        }
818
819 5 View Code Duplication
        if (isset($xrefData[$this->addKey])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
820 1
            if (isset($tokenResolvers)) {
821 1
                $tokenResolvers->applyToArray($xrefData[$this->addKey]);
822 1
            }
823 1
            $this->recursiveAddData($xrefData[$this->addKey], $result);
824 1
        }
825
826 5
        return $result;
827
    }
828
829 7
    public function compileXref(Xref $xref, $includeType = null, $includeTypeValue = null)
830
    {
831 7
        $xrefPath = array();
832 7
        $compiledData = $this->recursiveCompileXref($xref, null, $includeType, $includeTypeValue, $xrefPath);
833 5
        return $compiledData;
834
    }
835
836
    public function compileLocalFile($inputFileName, $includeType = null, $includeKeyValue = null)
837
    {
838
        $xref = new Xref(LocalFileXrefResolver::getType(), $inputFileName);
839
        return $this->compileXref($xref, $includeType, $includeKeyValue);
840
    }
841
842
    public function compileUrl($inputUrl, $includeType = null, $includeKeyValue = null)
843
    {
844
        $xref = new Xref(UrlXrefResolver::getType(), $inputUrl);
845
        return $this->compileXref($xref, $includeType, $includeKeyValue);
846
    }
847
848
    public function save(array $tree, $fileName)
849
    {
850
        $fileExtension = pathinfo($fileName, PATHINFO_EXTENSION);
851
        if (!TreeSerializerFactory::isRegisteredByFileExtension($fileExtension)) {
852
            throw new NotRegisteredException(
853
                sprintf(
854
                    'Unable to find serializer for extension "%s".',
855
                    $fileExtension
856
                )
857
            );
858
        }
859
        $serializer = TreeSerializerFactory::getByFileExtension($fileExtension);
860
        $serializedTree = $serializer->serialize($tree);
861
        file_put_contents($fileName, $serializedTree);
862
    }
863
}
864