Completed
Push — master ( d819eb...202c45 )
by Andrei
03:53
created

TreeCompiler::parseXrefTokenResolverDefinitions()   F

Complexity

Conditions 25
Paths 10904

Size

Total Lines 150
Code Lines 106

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 77
CRAP Score 67.3546

Importance

Changes 9
Bugs 1 Features 2
Metric Value
c 9
b 1
f 2
dl 0
loc 150
ccs 77
cts 130
cp 0.5923
rs 2
cc 25
eloc 106
nc 10904
nop 4
crap 67.3546

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Exceptions\CircularReferenceException;
12
use ConfigToken\TreeCompiler\XrefResolver\Exception\UnknownXrefException;
13
use ConfigToken\TreeCompiler\XrefResolver\Exception\XrefResolverFormatException;
14
use ConfigToken\TreeCompiler\XrefResolver\Types\UrlXrefResolver;
15
use ConfigToken\TreeCompiler\XrefResolver\Types\LocalFileXrefResolver;
16
use ConfigToken\TreeCompiler\Xref;
17
use ConfigToken\TreeCompiler\XrefCollection;
18
use ConfigToken\TreeCompiler\XrefTokenResolver;
19
use ConfigToken\TreeCompiler\XrefTokenResolverCollection;
20
use ConfigToken\TreeSerializer\TreeSerializerFactory;
21
22
23
class TreeCompiler
24
{
25
    /** @var string */
26
    protected $includeKey = 'include';
27
    /** @var string */
28
    protected $includeXrefKey = 'xref';
29
    /** @var string */
30
    protected $includeXrefTypeKey = 'type';
31
    /** @var string */
32
    protected $includeXrefLocationKey = 'src';
33
    /** @var string */
34
    protected $includeXrefResolversKey = 'resolve';
35
    /** @var string */
36
    protected $includeMainKey = 'main';
37
    /** @var string */
38
    protected $xrefTokenResolverTypeKey = 'type';
39
    /** @var string */
40
    protected $xrefTokenResolverOptionsKey = 'options';
41
    /** @var string */
42
    protected $xrefTokenResolverValuesKey = 'values';
43
    /** @var string */
44
    protected $xrefTokenResolverValuesXrefKey = 'values-xref';
45
    /** @var string */
46
    protected $xrefTokenResolverOptionIgnoreUnknownTokensKey = 'ignore-unknown-tokens';
47
    /** @var string */
48
    protected $xrefTokenResolverOptionIgnoreUnknownFiltersKey = 'ignore-unknown-filters';
49
    /** @var string */
50
    protected $xrefTokenResolverOptionTokenRegexKey = 'token-regex';
51
    /** @var string */
52
    protected $xrefTokenResolverOptionTokenPrefixKey = 'token-prefix';
53
    /** @var string */
54
    protected $xrefTokenResolverOptionTokenSuffixKey = 'token-suffix';
55
    /** @var string */
56
    protected $xrefTokenResolverOptionTokenFilterDelimiterKey = 'token-filter-delimiter';
57
    /** @var string */
58
    protected $xrefTokenResolverOptionScopeTokenNameKey = 'scope-token-name';
59
    /** @var string */
60
    protected $xrefTokenResolverOptionScopeTokenNameDelimiterKey = 'scope-token-name-delimiter';
61
    /** @var string */
62
    protected $xrefTokenResolverOptionScopeTokenLevelDelimiterKey = 'scope-token-level-delimiter';
63
    /** @var string */
64
    protected $xrefTokenResolverOptionIgnoreOutOfScopeKey = 'ignore-out-of-scope';
65
    /** @var array */
66
    protected $xrefTokenResolverOptionKeys = array();
67
    /** @var array */
68
    protected $xrefTokenResolverRequiredOptionKeys = array();
69
    /** @var string[] */
70
    protected $xrefTokenResolverOptionSetterMapping = array();
71
72
    /** @var string */
73
    protected $removeKey = 'remove';
74
    /** @var string */
75
    protected $addKey = 'add';
76
    /** @var string */
77
    protected $xrefTypeAndLocationDelimiter = ':';
78
79
    /** @var XrefCollection */
80
    protected $xrefs;
81
82
    const INCLUDE_TYPE_GROUP = 'group';
83
    const INCLUDE_TYPE_XREF = 'xref';
84
85 7
    public function __construct(XrefCollection $xrefs = null)
86
    {
87 7
        if (!isset($xrefs)) {
88 7
            $xrefs = new XrefCollection();
89 7
        }
90 7
        $this->xrefs = $xrefs;
91 7
        $stringType = gettype('');
92 7
        $booleanType = gettype(true);
93 7
        $this->xrefTokenResolverOptionKeys = array(
94 7
            RegisteredTokenResolver::getBaseType() => array(
95 7
                $this->xrefTokenResolverOptionIgnoreUnknownTokensKey => $booleanType,
96 7
                $this->xrefTokenResolverOptionIgnoreUnknownFiltersKey => $booleanType,
97 7
                $this->xrefTokenResolverOptionTokenRegexKey => $stringType,
98 7
                $this->xrefTokenResolverOptionTokenPrefixKey => $stringType,
99 7
                $this->xrefTokenResolverOptionTokenSuffixKey => $stringType,
100 7
                $this->xrefTokenResolverOptionTokenFilterDelimiterKey => $stringType,
101 7
            ),
102 7
            ScopeTokenResolver::getBaseType() => array(
103 7
                $this->xrefTokenResolverOptionIgnoreUnknownTokensKey => $booleanType,
104 7
                $this->xrefTokenResolverOptionIgnoreUnknownFiltersKey => $booleanType,
105 7
                $this->xrefTokenResolverOptionIgnoreOutOfScopeKey => $booleanType,
106 7
                $this->xrefTokenResolverOptionTokenRegexKey => $stringType,
107 7
                $this->xrefTokenResolverOptionTokenPrefixKey => $stringType,
108 7
                $this->xrefTokenResolverOptionTokenSuffixKey => $stringType,
109 7
                $this->xrefTokenResolverOptionTokenFilterDelimiterKey => $stringType,
110 7
                $this->xrefTokenResolverOptionScopeTokenNameKey => $stringType,
111 7
                $this->xrefTokenResolverOptionScopeTokenNameDelimiterKey => $stringType,
112 7
                $this->xrefTokenResolverOptionScopeTokenLevelDelimiterKey => $stringType,
113
            )
114 7
        );
115 7
        $this->xrefTokenResolverRequiredOptionKeys = array(
116 7
            RegisteredTokenResolver::getBaseType() => array(),
117 7
            ScopeTokenResolver::getBaseType() => array(
118 7
                $this->xrefTokenResolverOptionScopeTokenNameKey => true,
119
            )
120 7
        );
121 7
    }
122
123 7
    public function getXrefs()
124
    {
125 7
        return $this->xrefs;
126
    }
127
128
    /**
129
     * Get include key.
130
     *
131
     * @return string
132
     */
133
    public function getIncludeKey()
134
    {
135
        return $this->includeKey;
136
    }
137
138
    /**
139
     * Set include key.
140
     *
141
     * @param string $value The new value.
142
     * @return $this
143
     */
144
    public function setIncludeKey($value)
145
    {
146
        $this->includeKey = $value;
147
        return $this;
148
    }
149
150
    /**
151
     * Recursively remove the keys of one array from another.
152
     *
153
     * @param array $toRemove The array containing the keys to be removed.
154
     * @param array $removeFrom The array from which to remove keys.
155
     */
156 1
    public function recursiveRemoveData(array &$toRemove, array &$removeFrom)
157
    {
158 1
        foreach ($toRemove as $keyToRemove => $childKeysToRemove) {
159 1
            if (is_array($childKeysToRemove)) {
160
                if (isset($removeFrom[$keyToRemove]) && is_array($removeFrom[$keyToRemove])) {
161
                    $this->recursiveRemoveData($toRemove[$keyToRemove], $removeFrom[$keyToRemove]);
162
                }
163 1
            } else if (array_key_exists($keyToRemove, $removeFrom)) {
164 1
                unset($removeFrom[$keyToRemove]);
165 1
            }
166 1
        }
167 1
    }
168
169
    /**
170
     * Recursively add and override keys from one array into another.
171
     *
172
     * @param array $addFrom The source array.
173
     * @param array $addTo The destination array.
174
     */
175 5
    public function recursiveAddData(array &$addFrom, array &$addTo)
176
    {
177 5
        if (empty($addTo)) {
178
            /** @noinspection PhpUnusedLocalVariableInspection */
179 5
            $addTo = $addFrom;
180 5
            return;
181
        }
182 2
        foreach ($addFrom as $keyToAdd => $childKeys) {
183 2
            if (is_array($childKeys)) {
184
                if (isset($addTo[$keyToAdd]) && is_array($addTo[$keyToAdd])) {
185
                    $this->recursiveAddData($addFrom[$keyToAdd], $addTo[$keyToAdd]);
186
                    continue;
187
                }
188
            }
189 2
            $addTo[$keyToAdd] = $addFrom[$keyToAdd];
190 2
        }
191 2
    }
192
193
    /**
194
     * Retrieve a list of Xref keys to be resolved.
195
     *
196
     * @param Xref $xref
197
     * @param array $xrefDataInclude
198
     * @param array $xrefDataIncludeXrefs
199
     * @param string $includeType
200
     * @param string|array $includeTypeValue
201
     * @return array
202
     *
203
     * @throws XrefResolverFormatException
204
     */
205 7
    protected function getXrefKeysToBeResolved(Xref $xref, $xrefDataInclude, $xrefDataIncludeXrefs,
206
                                               $includeType, $includeTypeValue)
207
    {
208 7
        $result = array();
209
        switch ($includeType) {
210 7
            case static::INCLUDE_TYPE_XREF:
211
                $missing = array();
212
                if (!is_array($includeTypeValue)) {
213
                    throw new XrefResolverFormatException(
214
                        $xref,
215
                        sprintf(
216
                            'Include type value must be an array, %s given.',
217
                            gettype($includeTypeValue)
218
                        )
219
                    );
220
                }
221
                foreach ($includeTypeValue as $xrefKey) {
222
                    if (!isset($xrefDataIncludeXrefs[$xrefKey])) {
223
                        $missing[] = $xrefKey;
224
                    }
225
                }
226
                if (empty($missing)) {
227
                    unset($missing);
228
                } else {
229
                    throw new XrefResolverFormatException(
230
                        $xref,
231
                        sprintf(
232
                            "Required to explicitly include the list of Xref keys [\"%s\"] " .
233
                            "but unable to find [\"%s\"].",
234
                            implode('", "', $includeTypeValue),
235
                            implode('", "', $missing)
236
                        )
237
                    );
238
                }
239
                $result = $includeTypeValue;
240
                break;
241 7
            case static::INCLUDE_TYPE_GROUP:
242 7
                if (!isset($xrefDataInclude[$includeTypeValue])) {
243
                    throw new XrefResolverFormatException(
244
                        $xref,
245
                        sprintf(
246
                            "Unable to find the required include group of Xref keys named \"%s\"",
247
                            $includeTypeValue
248
                        )
249
                    );
250
                }
251 7
                $result = $xrefDataInclude[$includeTypeValue];
252 7
                break;
253
        }
254 7
        return $result;
255
    }
256
257
    /**
258
     * @param $xrefKey
259
     * @param $tokenResolverDefinitionIndex
260
     * @param $tokenResolverBaseType
261
     * @param $options
262
     * @throws TokenResolverDefinitionException
263
     */
264 5
    protected function validateXrefTokenResolverOptions($xrefKey, $tokenResolverDefinitionIndex, $tokenResolverBaseType,
265
                                                        $options)
266
    {
267 5
        if (!is_array($options)) {
268
            throw new TokenResolverDefinitionException(
269
                sprintf(
270
                    'The "%s" key for token resolver definition at index %d for Xref key "%s" must be an associative array.',
271
                    $this->xrefTokenResolverOptionsKey,
272
                    $tokenResolverDefinitionIndex,
273
                    $xrefKey
274
                )
275
            );
276
        }
277 5
        $required = $this->xrefTokenResolverRequiredOptionKeys[$tokenResolverBaseType];
278 5
        $unknown = array();
279 5
        $found = array();
280 5
        foreach ($options as $optionKey => $optionValue) {
281 5
            if (!isset($this->xrefTokenResolverOptionKeys[$tokenResolverBaseType][$optionKey])) {
282
                $unknown[] = $optionKey;
283
            } else {
284 5
                $found[$optionKey] = $optionValue;
285 5
                if (isset($required[$optionKey])) {
286
                    unset($required[$optionKey]);
287
                }
288
            }
289 5
        }
290 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...
291
            throw new TokenResolverDefinitionException(
292
                sprintf(
293
                    'Missing required option(s) "%s" for token resolver definition based on the "%s" type identifier at ' .
294
                    'index %d for Xref key "%s".',
295
                    implode('", "', $required),
296
                    $tokenResolverBaseType,
297
                    $this->xrefTokenResolverOptionsKey,
298
                    $tokenResolverDefinitionIndex,
299
                    $xrefKey
300
                )
301
            );
302
        }
303 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...
304
            throw new TokenResolverDefinitionException(
305
                sprintf(
306
                    'Unknown option(s) "%s" for token resolver definition based on the "%s" type identifier at ' .
307
                    'index %d for Xref key "%s".',
308
                    implode('", "', $unknown),
309
                    $tokenResolverBaseType,
310
                    $this->xrefTokenResolverOptionsKey,
311
                    $tokenResolverDefinitionIndex,
312
                    $xrefKey
313
                )
314
            );
315
        }
316 5
        foreach ($found as $optionKey => $optionValue) {
317 5
            $valueType = gettype($optionValue);
318 5
            $expectedValueType = $this->xrefTokenResolverOptionKeys[$tokenResolverBaseType][$optionKey];
319 5
            if ($valueType != $expectedValueType) {
320
                throw new TokenResolverDefinitionException(
321
                    sprintf(
322
                        'Wrong type "%s" instead of "%s" for option "%s" for token resolver definition based on the ' .
323
                        '"%s" type identifier at index %d for Xref key "%s".',
324
                        $valueType,
325
                        $expectedValueType,
326
                        $tokenResolverBaseType,
327
                        $this->xrefTokenResolverOptionsKey,
328
                        $tokenResolverDefinitionIndex,
329
                        $xrefKey
330
                    )
331
                );
332
            }
333 5
        }
334 5
    }
335
336
    /**
337
     * @param string $xrefKey
338
     * @param string|array $xrefInfo
339
     * @param XrefTokenResolverCollection $xrefTokenResolvers
340
     * @param Xref[] $xrefPath
341
     * @return Xref|mixed
342
     * @throws TreeCompilerFormatException
343
     * @throws \Exception
344
     */
345 7
    protected function parseXrefInfo($xrefKey, $xrefInfo, XrefTokenResolverCollection $xrefTokenResolvers = null, $xrefPath)
346
    {
347 7
        if (gettype($xrefInfo) == 'string') {
348 1
            list($xrefType, $xrefLocation) = Xref::parseDefinitionString($xrefInfo, $this->xrefTypeAndLocationDelimiter);
349 1
        } else {
350 7
            if (!is_array($xrefInfo)) {
351
                throw new TreeCompilerFormatException(
352
                    sprintf(
353
                        'The Xref definition key "%s" must be a string with the format xref_type %s xref_location ' .
354
                        'or an associative array with the keys "%s" for type and "%s" for location.',
355
                        $this->xrefTypeAndLocationDelimiter,
356
                        $this->includeXrefTypeKey,
357
                        $this->includeXrefLocationKey
358
                    )
359
                );
360
            }
361
362 7
            $requiredKeys = array($this->includeXrefTypeKey, $this->includeXrefLocationKey);
363 7
            foreach ($requiredKeys as $requiredKey) {
364 7
                if (!isset($xrefInfo[$requiredKey])) {
365
                    throw new TreeCompilerFormatException(
366
                        sprintf(
367
                            'The "%s" key is missing from the Xref definition with the key "%s".',
368
                            $requiredKey,
369
                            $xrefKey
370
                        )
371
                    );
372
                }
373 7
            }
374
375 7
            $xrefType = $xrefInfo[$this->includeXrefTypeKey];
376 7
            $xrefLocation = $xrefInfo[$this->includeXrefLocationKey];
377
        }
378
379 7
        if (isset($xrefTokenResolvers)) {
380 3
            $xrefLocation = $xrefTokenResolvers->applyToString($xrefLocation);
381 3
        }
382 7
        $xrefLocation = Xref::computeAbsoluteLocation($xrefType, $xrefLocation, $xrefPath);
383
384 7
        $xrefId = Xref::computeId($xrefType, $xrefLocation);
385 7
        if ($this->xrefs->hasById($xrefId)) {
386 7
            return $this->xrefs[$xrefId];
387
        }
388
389
        return new Xref($xrefType, $xrefLocation);
390
    }
391
392
    /**
393
     * Parse the array corresponding to the $includeKey:$includeXrefKey:<xref name>:$includeXrefResolveKey.
394
     *
395
     * @param string $xrefKey
396
     * @param array $tokenResolversInfo
397
     * @param XrefTokenResolverCollection $tokenResolvers
398
     * @param Xref[] $xrefPath
399
     * @return XrefTokenResolverCollection
400
     * @throws \Exception
401
     */
402 7
    protected function parseXrefTokenResolverDefinitions($xrefKey, $tokenResolversInfo, XrefTokenResolverCollection $tokenResolvers = null, $xrefPath)
403
    {
404 7
        $resolverValues = array();
405 7
        $tokenResolverDefinitionIndex = 0;
406
        // validate
407 7
        if (!is_array($tokenResolversInfo)) {
408
            throw new TokenResolverDefinitionException(
409
                sprintf(
410
                    'Token resolver definitions at index %d for Xref key "%s" must be an array. (%s)',
411
                    $tokenResolverDefinitionIndex,
412
                    $xrefKey,
413
                    json_encode($tokenResolversInfo)
414
                )
415
            );
416
        }
417 7
        foreach ($tokenResolversInfo as $tokenResolverKey => $tokenResolverInfo) {
418 7
            if (!is_array($tokenResolverInfo)) {
419 1
                throw new TokenResolverDefinitionException(
420 1
                    sprintf(
421 1
                        'Token resolver definition at index %d for Xref key "%s" must be an associative array. (%s)',
422 1
                        $tokenResolverKey,
423 1
                        $xrefKey,
424 1
                        json_encode($tokenResolversInfo)
425 1
                    )
426 1
                );
427
            }
428 6
            if (!isset($tokenResolverInfo[$this->xrefTokenResolverTypeKey])) {
429 1
                throw new TokenResolverDefinitionException(
430 1
                    sprintf(
431 1
                        "Token resolver definition at index %d for Xref key \"%s\" is missing the \"%s\" type identifier key.\n%s",
432 1
                        $tokenResolverDefinitionIndex,
433 1
                        $xrefKey,
434 1
                        $this->xrefTokenResolverTypeKey,
435 1
                        json_encode($tokenResolverInfo)
436 1
                    )
437 1
                );
438
            }
439 5
            $tokenResolverType = $tokenResolverInfo[$this->xrefTokenResolverTypeKey];
440 5
            if (!TokenResolverFactory::isRegisteredByType($tokenResolverType)) {
441
                throw new TokenResolverDefinitionException(
442
                    sprintf(
443
                        'Unknown token resolver type identifier "%s" at index %d for Xref key "%s".',
444
                        $tokenResolverType,
445
                        $tokenResolverDefinitionIndex,
446
                        $xrefKey
447
                    )
448
                );
449
            }
450 5
            $hasValues = isset($tokenResolverInfo[$this->xrefTokenResolverValuesKey]);
451 5
            $hasValuesXref = isset($tokenResolverInfo[$this->xrefTokenResolverValuesXrefKey]);
452 5
            if ((!$hasValues) && (!$hasValuesXref)) {
453
                throw new TokenResolverDefinitionException(
454
                    sprintf(
455
                        'Token resolver definition at index %d for Xref key "%s" does not have a "%s" key or a "%s" key.',
456
                        $tokenResolverDefinitionIndex,
457
                        $xrefKey,
458
                        $this->xrefTokenResolverValuesKey,
459
                        $this->xrefTokenResolverValuesXrefKey
460
                    )
461
                );
462
            }
463 5
            $tokenResolverBaseType = TokenResolverFactory::getBaseTypeForType($tokenResolverType);
464 5
            if (isset($tokenResolverInfo[$this->xrefTokenResolverOptionsKey])) {
465 5
                $options = $tokenResolverInfo[$this->xrefTokenResolverOptionsKey];
466 5
                $this->validateXrefTokenResolverOptions(
467 5
                    $xrefKey,
468 5
                    $tokenResolverDefinitionIndex,
469 5
                    $tokenResolverBaseType,
470
                    $options
471 5
                );
472 5
            }
473 5
            if ($hasValues) {
474 5
                $values = $tokenResolverInfo[$this->xrefTokenResolverValuesKey];
475 5
            } else { // has values xref
476 1
                $xrefInfo = $tokenResolverInfo[$this->xrefTokenResolverValuesXrefKey];
477 1
                $xref = $this->parseXrefInfo(
478 1
                    sprintf('%s.%s[%d]', $xrefKey, $this->includeXrefResolversKey, $tokenResolverDefinitionIndex),
479 1
                    $xrefInfo,
480 1
                    $tokenResolvers,
481
                    $xrefPath
482 1
                );
483
                // pass down current token resolvers
484 1
                $values = $this->recursiveCompileXref($xref, $tokenResolvers, null, null, $xrefPath);
485
            }
486 5
            if (!is_array($values)) {
487
                throw new TokenResolverDefinitionException(
488
                    sprintf(
489
                        'The "%s" key must be an associative array for token resolver definition at ' .
490
                        'index %d for Xref key "%s".',
491
                        $this->xrefTokenResolverValuesKey,
492
                        $tokenResolverDefinitionIndex,
493
                        $xrefKey
494
                    )
495
                );
496
            }
497 5
            $resolverValues[$tokenResolverKey] = $values;
498 5
            $tokenResolverDefinitionIndex++;
499 5
        }
500
501 5
        $result = new XrefTokenResolverCollection();
502
        // parse
503 5
        foreach ($tokenResolversInfo as $tokenResolverKey => $tokenResolverInfo) {
504 5
            if (isset($tokenResolverInfo[$this->xrefTokenResolverOptionsKey])) {
505 5
                $options = $tokenResolverInfo[$this->xrefTokenResolverOptionsKey];
506 5
            } else {
507
                $options = null;
508
            }
509 5
            $tokenResolver = TokenResolverFactory::get($tokenResolverInfo[$this->xrefTokenResolverTypeKey]);
510 5
            $xrefTokenResolver = new XrefTokenResolver($tokenResolver);
511 5
            $values = $resolverValues[$tokenResolverKey];
512 5
            $xrefTokenResolver->setRegisteredTokenValues($values);
513 5
            if (isset($options)) {
514 5
                if (isset($options[$this->xrefTokenResolverOptionIgnoreUnknownTokensKey])) {
515
                    $tokenResolver->setIgnoreUnknownTokens($options[$this->xrefTokenResolverOptionIgnoreUnknownTokensKey]);
516
                }
517 5
                if ($tokenResolver::getBaseType() == ScopeTokenResolver::getBaseType()) {
518
                    /** @var ScopeTokenResolver $tokenResolver */
519
                    if (isset($options[$this->xrefTokenResolverOptionScopeTokenNameKey])) {
520
                        $tokenResolver->setScopeTokenName($options[$this->xrefTokenResolverOptionScopeTokenNameKey]);
521
                    }
522
                    if (isset($options[$this->xrefTokenResolverOptionScopeTokenNameDelimiterKey])) {
523
                        $tokenResolver->setScopeTokenNameDelimiter($options[$this->xrefTokenResolverOptionScopeTokenNameDelimiterKey]);
524
                    }
525
                    if (isset($options[$this->xrefTokenResolverOptionScopeTokenLevelDelimiterKey])) {
526
                        $tokenResolver->setScopeLevelDelimiter($options[$this->xrefTokenResolverOptionScopeTokenLevelDelimiterKey]);
527
                    }
528
                    if (isset($options[$this->xrefTokenResolverOptionIgnoreOutOfScopeKey])) {
529
                        $tokenResolver->setIgnoreOutOfScope($options[$this->xrefTokenResolverOptionIgnoreOutOfScopeKey]);
530
                    }
531
                }
532 5
                if (isset($options[$this->xrefTokenResolverOptionIgnoreUnknownFiltersKey])) {
533
                    $xrefTokenResolver->setIgnoreUnknownFilters($options[$this->xrefTokenResolverOptionIgnoreUnknownFiltersKey]);
534
                }
535 5
                if (isset($options[$this->xrefTokenResolverOptionTokenRegexKey])) {
536
                    $xrefTokenResolver->setTokenRegex($options[$this->xrefTokenResolverOptionTokenRegexKey]);
537
                }
538 5
                if (isset($options[$this->xrefTokenResolverOptionTokenPrefixKey])) {
539 5
                    $xrefTokenResolver->setTokenPrefix($options[$this->xrefTokenResolverOptionTokenPrefixKey]);
540 5
                }
541 5
                if (isset($options[$this->xrefTokenResolverOptionTokenSuffixKey])) {
542 5
                    $xrefTokenResolver->setTokenSuffix($options[$this->xrefTokenResolverOptionTokenSuffixKey]);
543 5
                }
544 5
                if (isset($options[$this->xrefTokenResolverOptionTokenFilterDelimiterKey])) {
545
                    $xrefTokenResolver->setTokenFilterDelimiter($options[$this->xrefTokenResolverOptionTokenFilterDelimiterKey]);
546
                }
547 5
            }
548 5
            $result->add($xrefTokenResolver);
549 5
        }
550 5
        return $result;
551
    }
552
553
    /**
554
     * Recursively resolve Xrefs and compile data.
555
     *
556
     * @param Xref $xref
557
     * @param XrefTokenResolverCollection $tokenResolvers
558
     * @param string|null $includeType
559
     * @param string|array|null $includeTypeValue
560
     * @param array $xrefPath
561
     * @return array
562
     *
563
     * @throws CircularReferenceException
564
     * @throws Exception\AlreadyRegisteredException
565
     * @throws TreeCompiler\XrefResolver\Exception\UnknownXrefTypeException
566
     * @throws UnknownXrefException
567
     * @throws XrefResolverFormatException
568
     * @throws \Exception
569
     */
570 7
    protected function recursiveCompileXref(Xref $xref, XrefTokenResolverCollection $tokenResolvers = null,
571
                                            $includeType = null, $includeTypeValue = null, &$xrefPath)
572
    {
573 7
        static $XREF_KEY = 0;
574 7
        static $XREF_RESOLVERS_KEY = 1;
575
576 7
        if (!isset($includeType)) {
577 7
            $includeType = static::INCLUDE_TYPE_GROUP;
578 7
        }
579
580
        switch ($includeType) {
581 7
            case static::INCLUDE_TYPE_GROUP:
582 7
                if (!isset($includeTypeValue)) {
583 7
                    $includeTypeValue = $this->includeMainKey;
584 7
                } else if (gettype($includeTypeValue) != 'string') {
585
                    throw new XrefResolverFormatException(
586
                        $xref,
587
                        sprintf(
588
                            "Include type value must be a string representing the include group name. " .
589
                            "\"%s\" given instead.",
590
                            gettype($includeTypeValue)
591
                        )
592
                    );
593
                }
594 7
                break;
595
            case static::INCLUDE_TYPE_XREF:
596
                if (!is_array($includeTypeValue) || empty($includeTypeValue)) {
597
                    throw new XrefResolverFormatException(
598
                        $xref,
599
                        "Include type value must be a non-empty array of strings for named includes."
600
                    );
601
                }
602
                break;
603
            default:
604
                throw new \Exception(sprintf('Unknown include type "%s".', $includeType));
605
        }
606 7
        $mustIncludeSpecificGroup = $includeTypeValue != $this->includeMainKey;
607
608 7
        $xref->resolve();
609
610 7
        $xrefData = $xref->getData();
611 7
        if (empty($xrefData)) {
612
            if (!is_array($xrefData)) {
613
                throw new XrefResolverFormatException(
614
                    $xref,
615
                    "De-serialized data must be an array."
616
                );
617
            }
618
            return array();
619
        }
620
621 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...
622
            switch ($includeType) {
623 5
                case static::INCLUDE_TYPE_XREF:
624
                    throw new XrefResolverFormatException(
625
                        $xref,
626
                        sprintf(
627
                            "Required to explicitly include the list of Xref keys [\"%s\"] " .
628
                            "but the \"%s\" key is missing from the first level.",
629
                            implode('", "', $includeTypeValue),
630
                            $this->includeKey
631
                        )
632
                    );
633 5
                case static::INCLUDE_TYPE_GROUP:
634 5
                    if (!$mustIncludeSpecificGroup) {
635 5
                        if (isset($xrefData[$this->addKey])) {
636 1
                            $result = $xrefData[$this->addKey];
637 1
                        } else {
638 5
                            if (isset($xrefData[$this->removeKey])) {
639
                                unset($xrefData[$this->removeKey]);
640
                            }
641 5
                            $result = $xrefData;
642
                        }
643 5
                        if (isset($tokenResolvers)) {
644 5
                            $tokenResolvers->applyToArray($result);
645 5
                        }
646 5
                        return $result;
647
                    }
648
                    throw new XrefResolverFormatException(
649
                        $xref,
650
                        sprintf(
651
                            "Required to explicitly include the \"%s\" group of Xref keys " .
652
                            "but the \"%s\" key is missing from the first level.",
653
                            $includeTypeValue,
654
                            $this->includeKey
655
                        )
656
                    );
657
            }
658
        }
659 7
        $xrefDataInclude = &$xrefData[$this->includeKey];
660
661 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...
662
            switch ($includeType) {
663
                case static::INCLUDE_TYPE_XREF:
664
                    throw new XrefResolverFormatException(
665
                        $xref,
666
                        sprintf(
667
                            "Required to explicitly include the list of Xref keys [\"%s\"] " .
668
                            "but the \"%s\" key is missing from the \"%s\" key on the first level.",
669
                            implode('", "', $includeTypeValue),
670
                            $this->includeXrefKey,
671
                            $this->includeKey
672
                        )
673
                    );
674
                case static::INCLUDE_TYPE_GROUP:
675
                    if (!$mustIncludeSpecificGroup) {
676
                        if (isset($xrefData[$this->addKey])) {
677
                            $result = $xrefData[$this->addKey];
678
                        } else {
679
                            if (isset($xrefData[$this->removeKey])) {
680
                                unset($xrefData[$this->removeKey]);
681
                            }
682
                            $result = $xrefData;
683
                        }
684
                        if (isset($tokenResolvers)) {
685
                            $tokenResolvers->applyToArray($result);
686
                        }
687
                        return $result;
688
                    }
689
                    throw new XrefResolverFormatException(
690
                        $xref,
691
                        sprintf(
692
                            "Required to explicitly include the \"%s\" group of Xref keys " .
693
                            "but the \"%s\" key is missing from the \"%s\" key on the first level.",
694
                            $includeTypeValue,
695
                            $this->includeXrefKey,
696
                            $this->includeKey
697
                        )
698
                    );
699
            }
700
        }
701 7
        $xrefDataIncludeXrefs = &$xrefDataInclude[$this->includeXrefKey];
702
703 7
        $xrefKeysToBeResolved = $this->getXrefKeysToBeResolved(
704 7
            $xref,
705 7
            $xrefDataInclude,
706 7
            $xrefDataIncludeXrefs,
707 7
            $includeType,
708
            $includeTypeValue
709 7
        );
710
711 7
        $xrefsToBeParsed = array();
712 7
        foreach ($xrefKeysToBeResolved as $xrefKeyToBeResolved) {
713 7
            if (!isset($xrefDataIncludeXrefs[$xrefKeyToBeResolved])) {
714
                throw new UnknownXrefException(
715
                    sprintf(
716
                        "Unable to find the required Xref definition named \"%s\".",
717
                        $xrefKeyToBeResolved
718
                    )
719
                );
720
            }
721 7
            $xrefsToBeParsed[$xrefKeyToBeResolved] = $xrefDataIncludeXrefs[$xrefKeyToBeResolved];
722 7
        }
723 7
        unset($xrefDataIncludeXrefs);
724
725 7
        $xrefId = $xref->getId();
726 7
        $xrefPath[$xrefId] = $xref;
727
728 7
        $xrefsToBeResolved = array();
729 7
        foreach ($xrefsToBeParsed as $xrefKey => $xrefInfo) {
730 7
            $includedXref = $this->parseXrefInfo(
731 7
                $xrefKey,
732 7
                $xrefInfo,
733 7
                $tokenResolvers,
734
                $xrefPath
735 7
            );
736
737 7
            if (is_array($xrefInfo) && isset($xrefInfo[$this->includeXrefResolversKey])) {
738 7
                $xrefTokenResolvers = $this->parseXrefTokenResolverDefinitions(
739 7
                    $xrefKey,
740 7
                    $xrefInfo[$this->includeXrefResolversKey],
741 7
                    $tokenResolvers,
742
                    $xrefPath
743 7
                );
744 5
            } else {
745 2
                $xrefTokenResolvers = null;
746
            }
747
748 5
            $xrefsToBeResolved[] = array(
749 5
                $XREF_KEY => $includedXref,
750
                $XREF_RESOLVERS_KEY => $xrefTokenResolvers
751 5
            );
752 5
        }
753
754
        /** @var array $includedXrefs */
755 5
        $result = array();
756 5
        foreach ($xrefsToBeResolved as $xrefToBeResolved) {
757
            /** @var Xref $includedXref */
758 5
            $includedXref = $xrefToBeResolved[$XREF_KEY];
759 5
            if (isset($xrefPath[$includedXref->getId()])) {
760
                throw new CircularReferenceException(
761
                    sprintf(
762
                        'Tree compiler encountered circular reference at "%s" in path ["%s"].',
763
                        sprintf('%s:%s', $includedXref->getType(), $includedXref->getLocation()),
764
                        implode('", "', $xrefPath)
765
                    )
766
                );
767
            }
768 5
            $this->xrefs->add($includedXref);
769
            /** @var XrefTokenResolverCollection $includeTokenResolvers */
770 5
            $includeTokenResolvers = $xrefToBeResolved[$XREF_RESOLVERS_KEY];
771 5
            if (isset($includeTokenResolvers)) {
772 5
                $downTokenResolvers = $includeTokenResolvers;
773 5
            } else {
774 2
                $downTokenResolvers = new XrefTokenResolverCollection();
775
            }
776 5
            if (isset($tokenResolvers)) {
777 3
                $downTokenResolvers->addCollection($tokenResolvers);
778 3
            }
779 5
            $includeData = $this->recursiveCompileXref(
780 5
                $includedXref,
781 5
                $downTokenResolvers,
782 5
                static::INCLUDE_TYPE_GROUP,
783 5
                $this->includeMainKey,
784
                $xrefPath
785 5
            );
786 5
            $this->recursiveAddData($includeData, $result);
787 5
        }
788 5
        unset($xrefPath[$xrefId]);
789
790 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...
791 1
            if (isset($tokenResolvers)) {
792 1
                $tokenResolvers->applyToArray($xrefData[$this->removeKey]);
793 1
            }
794 1
            $this->recursiveRemoveData($xrefData[$this->removeKey], $result);
795 1
        }
796
797 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...
798 1
            if (isset($tokenResolvers)) {
799 1
                $tokenResolvers->applyToArray($xrefData[$this->addKey]);
800 1
            }
801 1
            $this->recursiveAddData($xrefData[$this->addKey], $result);
802 1
        }
803
804 5
        return $result;
805
    }
806
807 7
    public function compileXref(Xref $xref, $includeType = null, $includeTypeValue = null)
808
    {
809 7
        $xrefPath = array();
810 7
        $compiledData = $this->recursiveCompileXref($xref, null, $includeType, $includeTypeValue, $xrefPath);
811 5
        return $compiledData;
812
    }
813
814
    public function compileLocalFile($inputFileName, $includeType = null, $includeKeyValue = null)
815
    {
816
        $xref = new Xref(LocalFileXrefResolver::getType(), $inputFileName);
817
        return $this->compileXref($xref, $includeType, $includeKeyValue);
818
    }
819
820
    public function compileUrl($inputUrl, $includeType = null, $includeKeyValue = null)
821
    {
822
        $xref = new Xref(UrlXrefResolver::getType(), $inputUrl);
823
        return $this->compileXref($xref, $includeType, $includeKeyValue);
824
    }
825
826
    public function save(array $tree, $fileName)
827
    {
828
        $fileExtension = pathinfo($fileName, PATHINFO_EXTENSION);
829
        if (!TreeSerializerFactory::isRegisteredByFileExtension($fileExtension)) {
830
            throw new NotRegisteredException(
831
                sprintf(
832
                    'Unable to find serializer for extension "%s".',
833
                    $fileExtension
834
                )
835
            );
836
        }
837
        $serializer = TreeSerializerFactory::getByFileExtension($fileExtension);
838
        $serializedTree = $serializer->serialize($tree);
839
        file_put_contents($fileName, $serializedTree);
840
    }
841
}