Completed
Push — master ( 69a36d...5ea431 )
by Bernhard
34:36 queued 22:56
created

PackageJsonSerializer   F

Complexity

Total Complexity 87

Size/Duplication

Total Lines 531
Duplicated Lines 8.47 %

Coupling/Cohesion

Components 1
Dependencies 19

Test Coverage

Coverage 97.43%

Importance

Changes 8
Bugs 2 Features 2
Metric Value
wmc 87
c 8
b 2
f 2
lcom 1
cbo 19
dl 45
loc 531
ccs 303
cts 311
cp 0.9743
rs 1.5789

15 Methods

Rating   Name   Duplication   Size   Complexity  
A compareBindingDescriptors() 0 5 1
A __construct() 0 15 2
A serializePackageFile() 17 17 1
A serializeRootPackageFile() 18 18 1
A unserializePackageFile() 0 16 1
A unserializeRootPackageFile() 0 17 1
D packageFileToJson() 0 111 22
C rootPackageFileToJson() 0 55 10
D jsonToPackageFile() 0 84 23
C jsonToRootPackageFile() 0 40 11
A encode() 0 11 1
A decode() 0 20 3
A objectsToArrays() 10 10 3
A assertVersionSupported() 0 10 2
B validate() 0 23 5

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 PackageJsonSerializer 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 PackageJsonSerializer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the puli/manager package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Puli\Manager\Package;
13
14
use Puli\Discovery\Api\Type\BindingParameter;
15
use Puli\Discovery\Api\Type\BindingType;
16
use Puli\Discovery\Binding\ClassBinding;
17
use Puli\Discovery\Binding\ResourceBinding;
18
use Puli\Manager\Api\Config\Config;
19
use Puli\Manager\Api\Discovery\BindingDescriptor;
20
use Puli\Manager\Api\Discovery\BindingTypeDescriptor;
21
use Puli\Manager\Api\Environment;
22
use Puli\Manager\Api\InvalidConfigException;
23
use Puli\Manager\Api\Package\InstallInfo;
24
use Puli\Manager\Api\Package\PackageFile;
25
use Puli\Manager\Api\Package\PackageFileSerializer;
26
use Puli\Manager\Api\Package\RootPackageFile;
27
use Puli\Manager\Api\Package\UnsupportedVersionException;
28
use Puli\Manager\Api\Repository\PathMapping;
29
use Puli\Manager\Migration\MigrationManager;
30
use Rhumsaa\Uuid\Uuid;
31
use stdClass;
32
use Webmozart\Json\DecodingFailedException;
33
use Webmozart\Json\JsonDecoder;
34
use Webmozart\Json\JsonEncoder;
35
use Webmozart\Json\JsonValidator;
36
use Webmozart\PathUtil\Path;
37
38
/**
39
 * Serializes and unserializes package files to/from JSON.
40
 *
41
 * The JSON is validated against the schema `res/schema/package-schema.json`.
42
 *
43
 * @since  1.0
44
 *
45
 * @author Bernhard Schussek <[email protected]>
46
 */
47
class PackageJsonSerializer implements PackageFileSerializer
48
{
49
    /**
50
     * The default order of the keys in the written package file.
51
     *
52
     * @var string[]
53
     */
54
    private static $keyOrder = array(
55
        'version',
56
        'name',
57
        'path-mappings',
58
        'bindings',
59
        'binding-types',
60
        'override',
61
        'override-order',
62
        'config',
63
        'plugins',
64
        'extra',
65
        'packages',
66
    );
67
68
    /**
69
     * @var MigrationManager
70
     */
71
    private $migrationManager;
72
73
    /**
74
     * @var string
75
     */
76
    private $schemaDir;
77
78
    /**
79
     * @var string
80
     */
81
    private $targetVersion;
82
83
    /**
84
     * @var string[]
85
     */
86
    private $knownVersions;
87
88 3
    public static function compareBindingDescriptors(BindingDescriptor $a, BindingDescriptor $b)
89
    {
90
        // Make sure that bindings are always printed in the same order
91 3
        return strcmp($a->getUuid()->toString(), $b->getUuid()->toString());
92
    }
93
94
    /**
95
     * Creates a new serializer.
96
     *
97
     * @param MigrationManager $migrationManager The manager for migrating
98
     *                                           puli.json files between
99
     *                                           versions.
100
     * @param string           $schemaDir        The directory that contains the
101
     *                                           schema files.
102
     * @param string           $targetVersion    The file version that this
103
     *                                           serializer reads and produces.
104
     */
105 77
    public function __construct(MigrationManager $migrationManager, $schemaDir, $targetVersion = PackageFile::DEFAULT_VERSION)
106
    {
107 77
        $this->migrationManager = $migrationManager;
108 77
        $this->targetVersion = $targetVersion;
109 77
        $this->knownVersions = $this->migrationManager->getKnownVersions();
110
111 77
        if (!in_array($targetVersion, $this->knownVersions, true)) {
112 26
            $this->knownVersions[] = $targetVersion;
113 26
            usort($this->knownVersions, 'version_compare');
114 26
        }
115
116
        // We can't use realpath(), which doesn't work inside PHARs.
117
        // However, we want to display nice paths if the file is not found.
118 77
        $this->schemaDir = Path::canonicalize($schemaDir);
119 77
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124 15 View Code Duplication
    public function serializePackageFile(PackageFile $packageFile)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
125
    {
126 15
        $this->assertVersionSupported($packageFile->getVersion());
127
128 15
        $jsonData = (object) array('version' => $this->targetVersion);
129
130 15
        $this->packageFileToJson($packageFile, $jsonData);
131
132
        // Sort according to key order
133 15
        $jsonArray = (array) $jsonData;
134 15
        $orderedKeys = array_intersect_key(array_flip(self::$keyOrder), $jsonArray);
135 15
        $jsonData = (object) array_replace($orderedKeys, $jsonArray);
136
137 15
        $this->migrationManager->migrate($jsonData, $packageFile->getVersion());
138
139 15
        return $this->encode($jsonData, $packageFile->getPath());
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145 7 View Code Duplication
    public function serializeRootPackageFile(RootPackageFile $packageFile)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
146
    {
147 7
        $this->assertVersionSupported($packageFile->getVersion());
148
149 7
        $jsonData = (object) array('version' => $this->targetVersion);
150
151 7
        $this->packageFileToJson($packageFile, $jsonData);
152 7
        $this->rootPackageFileToJson($packageFile, $jsonData);
153
154
        // Sort according to key order
155 7
        $jsonArray = (array) $jsonData;
156 7
        $orderedKeys = array_intersect_key(array_flip(self::$keyOrder), $jsonArray);
157 7
        $jsonData = (object) array_replace($orderedKeys, $jsonArray);
158
159 7
        $this->migrationManager->migrate($jsonData, $packageFile->getVersion());
160
161 7
        return $this->encode($jsonData, $packageFile->getPath());
162
    }
163
164
    /**
165
     * {@inheritdoc}
166
     */
167 35
    public function unserializePackageFile($serialized, $path = null)
168
    {
169 35
        $packageFile = new PackageFile(null, $path);
170
171 35
        $jsonData = $this->decode($serialized, $path);
172
173
        // Remember original version of the package file
174 24
        $packageFile->setVersion($jsonData->version);
175
176
        // Migrate to the expected version
177 24
        $this->migrationManager->migrate($jsonData, $this->targetVersion);
178
179 24
        $this->jsonToPackageFile($jsonData, $packageFile);
180
181 24
        return $packageFile;
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187 34
    public function unserializeRootPackageFile($serialized, $path = null, Config $baseConfig = null)
188
    {
189 34
        $packageFile = new RootPackageFile(null, $path, $baseConfig);
190
191 34
        $jsonData = $this->decode($serialized, $path);
192
193
        // Remember original version of the package file
194 31
        $packageFile->setVersion($jsonData->version);
195
196
        // Migrate to the expected version
197 31
        $this->migrationManager->migrate($jsonData, $this->targetVersion);
198
199 31
        $this->jsonToPackageFile($jsonData, $packageFile);
200 31
        $this->jsonToRootPackageFile($jsonData, $packageFile);
201
202 31
        return $packageFile;
203
    }
204
205 22
    private function packageFileToJson(PackageFile $packageFile, stdClass $jsonData)
206
    {
207 22
        $mappings = $packageFile->getPathMappings();
208 22
        $bindingDescriptors = $packageFile->getBindingDescriptors();
209 22
        $typeDescriptors = $packageFile->getTypeDescriptors();
210 22
        $overrides = $packageFile->getOverriddenPackages();
211 22
        $extra = $packageFile->getExtraKeys();
212
213 22
        if (null !== $packageFile->getPackageName()) {
214 6
            $jsonData->name = $packageFile->getPackageName();
215 6
        }
216
217 22
        if (count($mappings) > 0) {
218 4
            $jsonData->{'path-mappings'} = new stdClass();
219
220 4
            foreach ($mappings as $mapping) {
221 4
                $puliPath = $mapping->getRepositoryPath();
222 4
                $localPaths = $mapping->getPathReferences();
223
224 4
                $jsonData->{'path-mappings'}->$puliPath = count($localPaths) > 1 ? $localPaths : reset($localPaths);
225 4
            }
226 4
        }
227
228 22
        if (count($bindingDescriptors) > 0) {
229 6
            uasort($bindingDescriptors, array(__CLASS__, 'compareBindingDescriptors'));
230
231 6
            $jsonData->bindings = new stdClass();
232
233 6
            foreach ($bindingDescriptors as $bindingDescriptor) {
234 6
                $binding = $bindingDescriptor->getBinding();
235 6
                $bindingData = new stdClass();
236 6
                $bindingData->_class = get_class($binding);
237
238
                // This needs to be moved to external classes to allow adding
239
                // custom binding classes at some point
240 6
                if ($binding instanceof ResourceBinding) {
241 6
                    $bindingData->query = $binding->getQuery();
242
243 6
                    if ('glob' !== $binding->getLanguage()) {
244 1
                        $bindingData->language = $binding->getLanguage();
245 1
                    }
246 6
                } elseif ($binding instanceof ClassBinding) {
247 3
                    $bindingData->class = $binding->getClassName();
248 3
                }
249
250 6
                $bindingData->type = $bindingDescriptor->getTypeName();
251
252
                // Don't include the default values of the binding type
253 6
                if ($binding->hasParameterValues(false)) {
254 1
                    $parameterData = $binding->getParameterValues(false);
255 1
                    ksort($parameterData);
256 1
                    $bindingData->parameters = (object) $parameterData;
257 1
                }
258
259 6
                $jsonData->bindings->{$bindingDescriptor->getUuid()->toString()} = $bindingData;
260 6
            }
261 6
        }
262
263 22
        if (count($typeDescriptors) > 0) {
264 8
            $bindingTypesData = array();
265
266 8
            foreach ($typeDescriptors as $typeDescriptor) {
267 8
                $type = $typeDescriptor->getType();
268 8
                $typeData = new stdClass();
269
270 8
                if ($typeDescriptor->getDescription()) {
271 2
                    $typeData->description = $typeDescriptor->getDescription();
272 2
                }
273
274 8
                if ($type->hasParameters()) {
275 6
                    $parametersData = array();
276
277 6
                    foreach ($type->getParameters() as $parameter) {
278 6
                        $parameterData = new stdClass();
279
280 6
                        if ($typeDescriptor->hasParameterDescription($parameter->getName())) {
281 3
                            $parameterData->description = $typeDescriptor->getParameterDescription($parameter->getName());
282 3
                        }
283
284 6
                        if ($parameter->isRequired()) {
285 1
                            $parameterData->required = true;
286 1
                        }
287
288 6
                        if (null !== $parameter->getDefaultValue()) {
289 3
                            $parameterData->default = $parameter->getDefaultValue();
290 3
                        }
291
292 6
                        $parametersData[$parameter->getName()] = $parameterData;
293 6
                    }
294
295 6
                    ksort($parametersData);
296
297 6
                    $typeData->parameters = (object) $parametersData;
298 6
                }
299
300 8
                $bindingTypesData[$type->getName()] = $typeData;
301 8
            }
302
303 8
            ksort($bindingTypesData);
304
305 8
            $jsonData->{'binding-types'} = (object) $bindingTypesData;
306 8
        }
307
308 22
        if (count($overrides) > 0) {
309 3
            $jsonData->override = count($overrides) > 1 ? $overrides : reset($overrides);
310 3
        }
311
312 22
        if (count($extra) > 0) {
313 2
            $jsonData->extra = (object) $extra;
314 2
        }
315 22
    }
316
317 7
    private function rootPackageFileToJson(RootPackageFile $packageFile, stdClass $jsonData)
318
    {
319 7
        $overrideOrder = $packageFile->getOverrideOrder();
320 7
        $installInfos = $packageFile->getInstallInfos();
321
322
        // Pass false to exclude base configuration values
323 7
        $configValues = $packageFile->getConfig()->toRawArray(false);
324
325 7
        if (count($overrideOrder) > 0) {
326 1
            $jsonData->{'override-order'} = $overrideOrder;
327 1
        }
328
329 7
        if (count($configValues) > 0) {
330 1
            $jsonData->config = (object) $configValues;
331 1
        }
332
333 7
        if (array() !== $packageFile->getPluginClasses()) {
334 2
            $jsonData->plugins = $packageFile->getPluginClasses();
335
336 2
            sort($jsonData->plugins);
337 2
        }
338
339 7
        if (count($installInfos) > 0) {
340 3
            $packagesData = array();
341
342 3
            foreach ($installInfos as $installInfo) {
343 3
                $installData = new stdClass();
344 3
                $installData->{'install-path'} = $installInfo->getInstallPath();
345
346 3
                if (InstallInfo::DEFAULT_INSTALLER_NAME !== $installInfo->getInstallerName()) {
347 1
                    $installData->installer = $installInfo->getInstallerName();
348 1
                }
349
350 3
                if ($installInfo->hasDisabledBindingUuids()) {
351 2
                    $installData->{'disabled-bindings'} = array();
352
353 2
                    foreach ($installInfo->getDisabledBindingUuids() as $uuid) {
354 2
                        $installData->{'disabled-bindings'}[] = $uuid->toString();
355 2
                    }
356
357 2
                    sort($installData->{'disabled-bindings'});
358 2
                }
359
360 3
                if (Environment::PROD !== $installInfo->getEnvironment()) {
361 1
                    $installData->env = $installInfo->getEnvironment();
362 1
                }
363
364 3
                $packagesData[$installInfo->getPackageName()] = $installData;
365 3
            }
366
367 3
            ksort($packagesData);
368
369 3
            $jsonData->packages = (object) $packagesData;
370 3
        }
371 7
    }
372
373 41
    private function jsonToPackageFile(stdClass $jsonData, PackageFile $packageFile)
374
    {
375 41
        if (isset($jsonData->name)) {
376 31
            $packageFile->setPackageName($jsonData->name);
377 31
        }
378
379 41
        if (isset($jsonData->{'path-mappings'})) {
380 4
            foreach ($jsonData->{'path-mappings'} as $path => $relativePaths) {
381 4
                $packageFile->addPathMapping(new PathMapping($path, (array) $relativePaths));
382 4
            }
383 4
        }
384
385 41
        if (isset($jsonData->bindings)) {
386 8
            foreach ($jsonData->bindings as $uuid => $bindingData) {
387 8
                $binding = null;
388 8
                $class = isset($bindingData->_class)
389 8
                    ? $bindingData->_class
390 8
                    : 'Puli\Discovery\Binding\ResourceBinding';
391
392
                // Move this code to external classes to allow use of custom
393
                // bindings
394
                switch ($class) {
395 8
                    case 'Puli\Discovery\Binding\ClassBinding':
396 5
                        $binding = new ClassBinding(
397 5
                            $bindingData->class,
398 5
                            $bindingData->type,
399 5
                            isset($bindingData->parameters) ? (array) $bindingData->parameters : array(),
400 5
                            Uuid::fromString($uuid)
401 5
                        );
402 5
                        break;
403 7
                    case 'Puli\Discovery\Binding\ResourceBinding':
404 7
                        $binding = new ResourceBinding(
405 7
                            $bindingData->query,
406 7
                            $bindingData->type,
407 7
                            isset($bindingData->parameters) ? (array) $bindingData->parameters : array(),
408 7
                            isset($bindingData->language) ? $bindingData->language : 'glob',
409 7
                            Uuid::fromString($uuid)
410 7
                        );
411 7
                        break;
412
                    default:
413
                        continue;
414
                }
415
416 8
                $packageFile->addBindingDescriptor(new BindingDescriptor($binding));
0 ignored issues
show
Bug introduced by
It seems like $binding defined by null on line 387 can also be of type null; however, Puli\Manager\Api\Discove...scriptor::__construct() does only seem to accept object<Puli\Discovery\Api\Binding\Binding>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
417 8
            }
418 8
        }
419
420 41
        if (isset($jsonData->{'binding-types'})) {
421 5
            foreach ((array) $jsonData->{'binding-types'} as $typeName => $data) {
422 5
                $parameters = array();
423 5
                $parameterDescriptions = array();
424
425 5
                if (isset($data->parameters)) {
426 5
                    foreach ((array) $data->parameters as $parameterName => $parameterData) {
427 5
                        $required = isset($parameterData->required) ? $parameterData->required : false;
428
429 5
                        $parameters[] = new BindingParameter(
430 5
                            $parameterName,
431 5
                            $required ? BindingParameter::REQUIRED : BindingParameter::OPTIONAL,
432 5
                            isset($parameterData->default) ? $parameterData->default : null
433 5
                        );
434
435 5
                        if (isset($parameterData->description)) {
436 4
                            $parameterDescriptions[$parameterName] = $parameterData->description;
437 4
                        };
438 5
                    }
439 5
                }
440
441 5
                $packageFile->addTypeDescriptor(new BindingTypeDescriptor(
442 5
                    new BindingType($typeName, $parameters),
443 5
                    isset($data->description) ? $data->description : null,
444
                    $parameterDescriptions
445 5
                ));
446 5
            }
447 5
        }
448
449 41
        if (isset($jsonData->override)) {
450 5
            $packageFile->setOverriddenPackages((array) $jsonData->override);
451 5
        }
452
453 41
        if (isset($jsonData->extra)) {
454 4
            $packageFile->setExtraKeys((array) $jsonData->extra);
455 4
        }
456 41
    }
457
458 31
    private function jsonToRootPackageFile(stdClass $jsonData, RootPackageFile $packageFile)
459
    {
460 31
        if (isset($jsonData->{'override-order'})) {
461 2
            $packageFile->setOverrideOrder((array) $jsonData->{'override-order'});
462 2
        }
463
464 31
        if (isset($jsonData->plugins)) {
465 28
            $packageFile->setPluginClasses($jsonData->plugins);
466 28
        }
467
468 31
        if (isset($jsonData->config)) {
469 3
            $config = $packageFile->getConfig();
470
471 3
            foreach ($this->objectsToArrays($jsonData->config) as $key => $value) {
472 3
                $config->set($key, $value);
473 3
            }
474 3
        }
475
476 31
        if (isset($jsonData->packages)) {
477 28
            foreach ($jsonData->packages as $packageName => $packageData) {
478 28
                $installInfo = new InstallInfo($packageName, $packageData->{'install-path'});
479
480 28
                if (isset($packageData->env)) {
481 2
                    $installInfo->setEnvironment($packageData->env);
482 2
                }
483
484 28
                if (isset($packageData->installer)) {
485 2
                    $installInfo->setInstallerName($packageData->installer);
486 2
                }
487
488 28
                if (isset($packageData->{'disabled-bindings'})) {
489 2
                    foreach ($packageData->{'disabled-bindings'} as $uuid) {
490 2
                        $installInfo->addDisabledBindingUuid(Uuid::fromString($uuid));
491 2
                    }
492 2
                }
493
494 28
                $packageFile->addInstallInfo($installInfo);
495 28
            }
496 28
        }
497 31
    }
498
499 22
    private function encode(stdClass $jsonData, $path = null)
500
    {
501 22
        $encoder = new JsonEncoder();
502 22
        $encoder->setPrettyPrinting(true);
503 22
        $encoder->setEscapeSlash(false);
504 22
        $encoder->setTerminateWithLineFeed(true);
505
506 22
        $this->validate($jsonData, $path);
507
508 22
        return $encoder->encode($jsonData);
509
    }
510
511 55
    private function decode($json, $path = null)
512
    {
513 55
        $decoder = new JsonDecoder();
514
515
        try {
516 55
            $jsonData = $decoder->decode($json);
517 55
        } catch (DecodingFailedException $e) {
518 2
            throw new InvalidConfigException(sprintf(
519 2
                "The configuration%s could not be decoded:\n%s",
520 2
                $path ? ' in '.$path : '',
521 2
                $e->getMessage()
522 2
            ), $e->getCode(), $e);
523
        }
524
525 53
        $this->assertVersionSupported($jsonData->version, $path);
526
527 51
        $this->validate($jsonData, $path);
528
529 41
        return $jsonData;
530
    }
531
532 3 View Code Duplication
    private function objectsToArrays($data)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
533
    {
534 3
        $data = (array) $data;
535
536 3
        foreach ($data as $key => $value) {
537 3
            $data[$key] = is_object($value) ? $this->objectsToArrays($value) : $value;
538 3
        }
539
540 3
        return $data;
541
    }
542
543 75
    private function assertVersionSupported($version, $path = null)
544
    {
545 75
        if (!in_array($version, $this->knownVersions, true)) {
546 2
            throw UnsupportedVersionException::forVersion(
547 2
                $version,
548 2
                $this->knownVersions,
549
                $path
550 2
            );
551
        }
552 73
    }
553
554 73
    private function validate($jsonData, $path = null)
555
    {
556 73
        $validator = new JsonValidator();
557 73
        $schema = $this->schemaDir.'/package-schema-'.$jsonData->version.'.json';
558
559 73
        if (!file_exists($schema)) {
560
            throw new InvalidConfigException(sprintf(
561
                'The JSON schema file for version %s was not found%s.',
562
                $jsonData->version,
563
                $path ? ' in '.$path : ''
564
            ));
565
        }
566
567 73
        $errors = $validator->validate($jsonData, $schema);
568
569 73
        if (count($errors) > 0) {
570 10
            throw new InvalidConfigException(sprintf(
571 10
                "The configuration%s is invalid:\n%s",
572 10
                $path ? ' in '.$path : '',
573 10
                implode("\n", $errors)
574 10
            ));
575
        }
576 63
    }
577
}
578