Completed
Push — develop ( c4d989...a97889 )
by Jaap
12s
created

StructureXmlRenderer   F

Complexity

Total Complexity 58

Size/Duplication

Total Lines 501
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 22

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 58
c 1
b 0
f 0
lcom 1
cbo 22
dl 0
loc 501
rs 1.7098

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 1
A render() 0 19 2
F buildFile() 0 81 13
A createErrorEntry() 0 11 1
B buildFunction() 0 22 4
F buildClass() 0 85 20
B finalize() 0 31 1
A buildPackageTree() 0 16 3
A buildNamespaceTree() 0 16 3
A buildDeprecationList() 0 8 1
A getNodeListForTagBasedQuery() 0 17 1
B generateNamespaceTree() 0 24 5
A generateNamespaceElements() 0 13 3

How to fix   Complexity   

Complex Class

Complex classes like StructureXmlRenderer 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 StructureXmlRenderer, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * phpDocumentor
4
 *
5
 * PHP Version 5.4
6
 *
7
 * @copyright 2010-2014 Mike van Riel / Naenius (http://www.naenius.com)
8
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
9
 * @link      http://phpdoc.org
10
 */
11
12
namespace phpDocumentor\Application\Renderer;
13
14
use phpDocumentor\DomainModel\Path;
15
use phpDocumentor\Application\Renderer\Template\Action;
16
use phpDocumentor\Application\Renderer\Template\Action\Xml;
17
use phpDocumentor\Application\Renderer\StructureXmlRenderer\ArgumentConverter;
18
use phpDocumentor\Application\Renderer\StructureXmlRenderer\ConstantConverter;
19
use phpDocumentor\Application\Renderer\StructureXmlRenderer\DocBlockConverter;
20
use phpDocumentor\Application\Renderer\StructureXmlRenderer\InterfaceConverter;
21
use phpDocumentor\Application\Renderer\StructureXmlRenderer\MethodConverter;
22
use phpDocumentor\Application\Renderer\StructureXmlRenderer\PropertyConverter;
23
use phpDocumentor\Application\Renderer\StructureXmlRenderer\TagConverter;
24
use phpDocumentor\Application\Renderer\StructureXmlRenderer\TraitConverter;
25
use phpDocumentor\DomainModel\Renderer\Router\ForFileProxy;
26
use phpDocumentor\DomainModel\Renderer\Router\RouterAbstract;
27
use phpDocumentor\Application\Application;
28
use phpDocumentor\Application\Renderer\StructureXmlRenderer\Tag\AuthorTag;
29
use phpDocumentor\Application\Renderer\StructureXmlRenderer\Tag\CoversTag;
30
use phpDocumentor\Application\Renderer\StructureXmlRenderer\Tag\IgnoreTag;
31
use phpDocumentor\Application\Renderer\StructureXmlRenderer\Tag\InternalTag;
32
use phpDocumentor\Application\Renderer\StructureXmlRenderer\Tag\LicenseTag;
33
use phpDocumentor\Application\Renderer\StructureXmlRenderer\Tag\MethodTag;
34
use phpDocumentor\Application\Renderer\StructureXmlRenderer\Tag\ParamTag;
35
use phpDocumentor\Application\Renderer\StructureXmlRenderer\Tag\PropertyTag;
36
use phpDocumentor\Application\Renderer\StructureXmlRenderer\Tag\ReturnTag;
37
use phpDocumentor\Application\Renderer\StructureXmlRenderer\Tag\UsesTag;
38
use phpDocumentor\Application\Renderer\StructureXmlRenderer\Tag\VarTag;
39
use phpDocumentor\DomainModel\ReadModel\ReadModel;
40
41
/**
42
 * Converts the structural information of phpDocumentor into an XML file.
43
 */
44
final class StructureXmlRenderer
0 ignored issues
show
Complexity introduced by
This class has a complexity of 58 which exceeds the configured maximum of 50.

The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.

Some resources for further reading:

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

Loading history...
Complexity introduced by
The class StructureXmlRenderer has a coupling between objects value of 36. Consider to reduce the number of dependencies under 13.
Loading history...
45
{
46
    /** @var \DOMDocument $xml */
47
    protected $xml;
48
49
    protected $docBlockConverter;
50
51
    protected $argumentConverter;
52
53
    protected $methodConverter;
54
55
    protected $propertyConverter;
56
57
    protected $constantConverter;
58
59
    protected $interfaceConverter;
60
61
    protected $traitConverter;
62
63
    /** @var RouterAbstract */
64
    private $router;
65
66
    public function __construct(RouterAbstract $router)
67
    {
68
        $this->docBlockConverter  = new DocBlockConverter(new TagConverter(), $router);
69
        $this->argumentConverter  = new ArgumentConverter();
70
        $this->methodConverter    = new MethodConverter($this->argumentConverter, $this->docBlockConverter);
71
        $this->propertyConverter  = new PropertyConverter($this->docBlockConverter);
72
        $this->constantConverter  = new ConstantConverter($this->docBlockConverter);
73
        $this->interfaceConverter = new InterfaceConverter(
74
            $this->docBlockConverter,
75
            $this->methodConverter,
76
            $this->constantConverter
77
        );
78
        $this->traitConverter = new TraitConverter(
79
            $this->docBlockConverter,
80
            $this->methodConverter,
81
            $this->propertyConverter
82
        );
83
        $this->router = $router;
84
    }
85
86
    public function render(ReadModel $view, Path $destination, $template = null)
0 ignored issues
show
Unused Code introduced by
The parameter $template is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
87
    {
88
        $artifact = (string)$destination;
89
90
        $this->xml = new \DOMDocument('1.0', 'utf-8');
91
        $this->xml->formatOutput = true;
92
        $document_element = new \DOMElement('project');
93
        $this->xml->appendChild($document_element);
94
95
        $document_element->setAttribute('title', $view()->getName());
96
        $document_element->setAttribute('version', Application::$VERSION);
97
98
        foreach ($view()->getFiles() as $file) {
99
            $this->buildFile($document_element, $file);
100
        }
101
102
        $this->finalize($view());
103
        file_put_contents($artifact, $this->xml->saveXML());
104
    }
105
106
    protected function buildFile(\DOMElement $parent, FileDescriptor $file)
0 ignored issues
show
Complexity introduced by
This operation has 1536 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

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

Loading history...
107
    {
108
        $child = new \DOMElement('file');
109
        $parent->appendChild($child);
110
111
        $router = new ForFileProxy($this->router->match($file));
0 ignored issues
show
Bug introduced by
It seems like $this->router->match($file) can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
112
        $path = ltrim($file->getPath(), './');
113
        $child->setAttribute('path', $path);
114
        $child->setAttribute('generated-path', $router->generate($file));
115
        $child->setAttribute('hash', $file->getHash());
116
117
        $this->docBlockConverter->convert($child, $file);
118
119
        // add namespace aliases
120
        foreach ($file->getNamespaceAliases() as $alias => $namespace) {
121
            $alias_obj = new \DOMElement('namespace-alias', $namespace);
122
            $child->appendChild($alias_obj);
123
            $alias_obj->setAttribute('name', $alias);
124
        }
125
126
        /** @var ConstantDescriptor $constant */
127
        foreach ($file->getConstants() as $constant) {
128
            $this->constantConverter->convert($child, $constant);
129
        }
130
131
        /** @var FunctionDescriptor $function */
132
        foreach ($file->getFunctions() as $function) {
133
            $this->buildFunction($child, $function);
134
        }
135
136
        /** @var InterfaceDescriptor $interface */
137
        foreach ($file->getInterfaces() as $interface) {
138
            $this->interfaceConverter->convert($child, $interface);
139
        }
140
141
        /** @var ClassDescriptor $class */
142
        foreach ($file->getClasses() as $class) {
143
            $this->buildClass($child, $class);
144
        }
145
146
        /** @var TraitDescriptor $class */
147
        foreach ($file->getTraits() as $trait) {
148
            $this->traitConverter->convert($child, $trait);
149
        }
150
151
        // add markers
152
        if (count($file->getMarkers()) > 0) {
153
            $markers = new \DOMElement('markers');
154
            $child->appendChild($markers);
155
156
            foreach ($file->getMarkers() as $marker) {
157
                if (! $marker['type']) {
158
                    continue;
159
                }
160
161
                $marker_obj = new \DOMElement(strtolower($marker['type']));
162
                $markers->appendChild($marker_obj);
163
164
                $marker_obj->appendChild(new \DOMText(trim($marker['message'])));
165
                $marker_obj->setAttribute('line', $marker['line']);
166
            }
167
        }
168
169
        $errors = $file->getAllErrors();
170
        if (count($errors) > 0) {
171
            $parse_errors = new \DOMElement('parse_markers');
172
            $child->appendChild($parse_errors);
173
174
            /** @var Error $error */
175
            foreach ($errors as $error) {
176
                $this->createErrorEntry($error, $parse_errors);
177
            }
178
        }
179
180
        // if we want to include the source for each file; append a new
181
        // element 'source' which contains a compressed, encoded version
182
        // of the source
183
        if ($file->getSource()) {
184
            $child->appendChild(new \DOMElement('source', base64_encode(gzcompress($file->getSource()))));
185
        }
186
    }
187
188
    /**
189
     * Creates an entry in the ParseErrors collection of a file for a given error.
190
     *
191
     * @param Error       $error
192
     * @param \DOMElement $parse_errors
193
     *
194
     * @return void
195
     */
196
    protected function createErrorEntry($error, $parse_errors)
197
    {
198
        $marker_obj = new \DOMElement(strtolower($error->getSeverity()));
199
        $parse_errors->appendChild($marker_obj);
200
201
        $message = vsprintf($error->getCode(), $error->getContext());
202
203
        $marker_obj->appendChild(new \DOMText($message));
204
        $marker_obj->setAttribute('line', $error->getLine());
205
        $marker_obj->setAttribute('code', $error->getCode());
206
    }
207
208
    /**
209
     * Export this function definition to the given parent DOMElement.
210
     *
211
     * @param \DOMElement        $parent   Element to augment.
212
     * @param FunctionDescriptor $function Element to export.
213
     * @param \DOMElement        $child    if supplied this element will be augmented instead of freshly added.
214
     *
215
     * @return void
216
     */
217
    public function buildFunction(\DOMElement $parent, FunctionDescriptor $function, \DOMElement $child = null)
218
    {
219
        if (!$child) {
220
            $child = new \DOMElement('function');
221
            $parent->appendChild($child);
222
        }
223
224
        $namespace = $function->getNamespace()
225
            ? $function->getNamespace()
226
            : $parent->getAttribute('namespace');
227
        $child->setAttribute('namespace', ltrim($namespace, '\\'));
228
        $child->setAttribute('line', $function->getLine());
229
230
        $child->appendChild(new \DOMElement('name', $function->getName()));
231
        $child->appendChild(new \DOMElement('full_name', $function->getFullyQualifiedStructuralElementName()));
232
233
        $this->docBlockConverter->convert($child, $function);
234
235
        foreach ($function->getArguments() as $argument) {
236
            $this->argumentConverter->convert($child, $argument);
237
        }
238
    }
239
240
    /**
241
     * Exports the given reflection object to the parent XML element.
242
     *
243
     * This method creates a new child element on the given parent XML element
244
     * and takes the properties of the Reflection argument and sets the
245
     * elements and attributes on the child.
246
     *
247
     * If a child DOMElement is provided then the properties and attributes are
248
     * set on this but the child element is not appended onto the parent. This
249
     * is the responsibility of the invoker. Essentially this means that the
250
     * $parent argument is ignored in this case.
251
     *
252
     * @param \DOMElement     $parent The parent element to augment.
253
     * @param ClassDescriptor $class  The data source.
254
     * @param \DOMElement     $child  Optional: child element to use instead of creating a
255
     *      new one on the $parent.
256
     *
257
     * @return void
258
     */
259
    public function buildClass(\DOMElement $parent, ClassDescriptor $class, \DOMElement $child = null)
0 ignored issues
show
Complexity introduced by
This operation has 2187000 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

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

Loading history...
260
    {
261
        if (!$child) {
262
            $child = new \DOMElement('class');
263
            $parent->appendChild($child);
264
        }
265
266
        $child->setAttribute('final', $class->isFinal() ? 'true' : 'false');
267
        $child->setAttribute('abstract', $class->isAbstract() ? 'true' : 'false');
268
269
        $parentFqcn = !$class->getParent() instanceof DescriptorAbstract
0 ignored issues
show
Bug introduced by
The class phpDocumentor\Applicatio...erer\DescriptorAbstract does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
270
            ? $class->getParent()
271
            : $class->getParent()->getFullyQualifiedStructuralElementName();
272
        $child->appendChild(new \DOMElement('extends', $parentFqcn));
273
274
        /** @var InterfaceDescriptor $interface */
275
        foreach ($class->getInterfaces() as $interface) {
276
            $interfaceFqcn = is_string($interface)
277
                ? $interface
278
                : $interface->getFullyQualifiedStructuralElementName();
279
            $child->appendChild(new \DOMElement('implements', $interfaceFqcn));
280
        }
281
282
        if ($child === null) {
283
            $child = new \DOMElement('interface');
284
            $parent->appendChild($child);
285
        }
286
287
        $namespace = $class->getNamespace()->getFullyQualifiedStructuralElementName();
288
        $child->setAttribute('namespace', ltrim($namespace, '\\'));
289
        $child->setAttribute('line', $class->getLine());
290
291
        $child->appendChild(new \DOMElement('name', $class->getName()));
292
        $child->appendChild(new \DOMElement('full_name', $class->getFullyQualifiedStructuralElementName()));
293
294
        $this->docBlockConverter->convert($child, $class);
295
296
        foreach ($class->getConstants() as $constant) {
297
            // TODO #840: Workaround; for some reason there are NULLs in the constants array.
298
            if ($constant) {
299
                $this->constantConverter->convert($child, $constant);
300
            }
301
        }
302
303
        foreach ($class->getInheritedConstants() as $constant) {
304
            // TODO #840: Workaround; for some reason there are NULLs in the constants array.
305
            if ($constant) {
306
                $this->constantConverter->convert($child, $constant);
307
            }
308
        }
309
310
        foreach ($class->getProperties() as $property) {
311
            // TODO #840: Workaround; for some reason there are NULLs in the properties array.
312
            if ($property) {
313
                $this->propertyConverter->convert($child, $property);
314
            }
315
        }
316
317
        foreach ($class->getInheritedProperties() as $property) {
318
            // TODO #840: Workaround; for some reason there are NULLs in the properties array.
319
            if ($property) {
320
                $this->propertyConverter->convert($child, $property);
321
            }
322
        }
323
324
        foreach ($class->getMethods() as $method) {
325
            // TODO #840: Workaround; for some reason there are NULLs in the methods array.
326
            if ($method) {
327
                $this->methodConverter->convert($child, $method);
328
            }
329
        }
330
331
        foreach ($class->getInheritedMethods() as $method) {
332
            // TODO #840: Workaround; for some reason there are NULLs in the methods array.
333
            if ($method) {
334
                $methodElement = $this->methodConverter->convert($child, $method);
335
                $methodElement->appendChild(
336
                    new \DOMElement(
337
                        'inherited_from',
338
                        $method->getParent()->getFullyQualifiedStructuralElementName()
339
                    )
340
                );
341
            }
342
        }
343
    }
344
345
    /**
346
     * Finalizes the processing and executing all post-processing actions.
347
     *
348
     * This method is responsible for extracting and manipulating the data that
349
     * is global to the project, such as:
350
     *
351
     * - Package tree
352
     * - Namespace tree
353
     * - Marker list
354
     * - Deprecated elements listing
355
     * - Removal of objects related to visibility
356
     *
357
     * @param ProjectInterface $projectDescriptor
358
     *
359
     * @return void
360
     */
361
    protected function finalize(ProjectInterface $projectDescriptor)
362
    {
363
        // TODO: move all these behaviours to a central location for all template parsers
364
        $behaviour = new AuthorTag();
365
        $behaviour->process($this->xml);
366
        $behaviour = new CoversTag();
367
        $behaviour->process($this->xml);
368
        $behaviour = new IgnoreTag();
369
        $behaviour->process($this->xml);
370
        $behaviour = new InternalTag(
371
            $projectDescriptor->isVisibilityAllowed(ProjectDescriptor\Settings::VISIBILITY_INTERNAL)
372
        );
373
        $behaviour->process($this->xml);
374
        $behaviour = new LicenseTag();
375
        $behaviour->process($this->xml);
376
        $behaviour = new MethodTag();
377
        $behaviour->process($this->xml);
378
        $behaviour = new ParamTag();
379
        $behaviour->process($this->xml);
380
        $behaviour = new PropertyTag();
381
        $behaviour->process($this->xml);
382
        $behaviour = new ReturnTag();
383
        $behaviour->process($this->xml);
384
        $behaviour = new UsesTag();
385
        $behaviour->process($this->xml);
386
        $behaviour = new VarTag();
387
        $behaviour->process($this->xml);
388
        $this->buildPackageTree($this->xml);
389
        $this->buildNamespaceTree($this->xml);
390
        $this->buildDeprecationList($this->xml);
391
    }
392
393
    /**
394
     * Collects all packages and subpackages, and adds a new section in the
395
     * DOM to provide an overview.
396
     *
397
     * @param \DOMDocument $dom Packages are extracted and a summary inserted
398
     *     in this object.
399
     *
400
     * @return void
401
     */
402
    protected function buildPackageTree(\DOMDocument $dom)
403
    {
404
        $xpath = new \DOMXPath($dom);
405
        $packages = array('global' => true);
406
        $qry = $xpath->query('//@package');
407
        for ($i = 0; $i < $qry->length; $i++) {
408
            if (isset($packages[$qry->item($i)->nodeValue])) {
409
                continue;
410
            }
411
412
            $packages[$qry->item($i)->nodeValue] = true;
413
        }
414
415
        $packages = $this->generateNamespaceTree(array_keys($packages));
416
        $this->generateNamespaceElements($packages, $dom->documentElement, 'package');
417
    }
418
419
    /**
420
     * Collects all namespaces and sub-namespaces, and adds a new section in
421
     * the DOM to provide an overview.
422
     *
423
     * @param \DOMDocument $dom Namespaces are extracted and a summary inserted
424
     *     in this object.
425
     *
426
     * @return void
427
     */
428
    protected function buildNamespaceTree(\DOMDocument $dom)
429
    {
430
        $xpath = new \DOMXPath($dom);
431
        $namespaces = array();
432
        $qry = $xpath->query('//@namespace');
433
        for ($i = 0; $i < $qry->length; $i++) {
434
            if (isset($namespaces[$qry->item($i)->nodeValue])) {
435
                continue;
436
            }
437
438
            $namespaces[$qry->item($i)->nodeValue] = true;
439
        }
440
441
        $namespaces = $this->generateNamespaceTree(array_keys($namespaces));
442
        $this->generateNamespaceElements($namespaces, $dom->documentElement);
443
    }
444
445
    /**
446
     * Adds a node to the xml for deprecations and the count value
447
     *
448
     * @param \DOMDocument $dom Markers are extracted and a summary inserted in this object.
449
     *
450
     * @return void
451
     */
452
    protected function buildDeprecationList(\DOMDocument $dom)
453
    {
454
        $nodes = $this->getNodeListForTagBasedQuery($dom, 'deprecated');
455
456
        $node = new \DOMElement('deprecated');
457
        $dom->documentElement->appendChild($node);
458
        $node->setAttribute('count', $nodes->length);
459
    }
460
461
    /**
462
     * Build a tag based query string and return result
463
     *
464
     * @param \DOMDocument $dom    Markers are extracted and a summary inserted
465
     *      in this object.
466
     * @param string       $marker The marker we're searching for throughout xml
467
     *
468
     * @return \DOMNodeList
469
     */
470
    protected function getNodeListForTagBasedQuery($dom, $marker)
471
    {
472
        $xpath = new \DOMXPath($dom);
473
474
        $query = '/project/file/markers/'.$marker.'|';
475
        $query .= '/project/file/docblock/tag[@name="'.$marker.'"]|';
476
        $query .= '/project/file/class/docblock/tag[@name="'.$marker.'"]|';
477
        $query .= '/project/file/class/*/docblock/tag[@name="'.$marker.'"]|';
478
        $query .= '/project/file/interface/docblock/tag[@name="'.$marker.'"]|';
479
        $query .= '/project/file/interface/*/docblock/tag[@name="'.$marker.'"]|';
480
        $query .= '/project/file/function/docblock/tag[@name="'.$marker.'"]|';
481
        $query .= '/project/file/constant/docblock/tag[@name="'.$marker.'"]';
482
483
        $nodes = $xpath->query($query);
484
485
        return $nodes;
486
    }
487
488
    /**
489
     * Generates a hierarchical array of namespaces with their singular name
490
     * from a single level list of namespaces with their full name.
491
     *
492
     * @param array $namespaces the list of namespaces as retrieved from the xml.
493
     *
494
     * @return array
495
     */
496
    protected function generateNamespaceTree($namespaces)
497
    {
498
        sort($namespaces);
499
500
        $result = array();
501
        foreach ($namespaces as $namespace) {
502
            if (!$namespace) {
503
                $namespace = 'global';
504
            }
505
506
            $namespace_list = explode('\\', $namespace);
507
508
            $node = &$result;
509
            foreach ($namespace_list as $singular) {
510
                if (!isset($node[$singular])) {
511
                    $node[$singular] = array();
512
                }
513
514
                $node = &$node[$singular];
515
            }
516
        }
517
518
        return $result;
519
    }
520
521
    /**
522
     * Recursive method to create a hierarchical set of nodes in the dom.
523
     *
524
     * @param array[]     $namespaces     the list of namespaces to process.
525
     * @param \DOMElement $parent_element the node to receive the children of
526
     *                                    the above list.
527
     * @param string      $node_name      the name of the summary element.
528
     *
529
     * @return void
530
     */
531
    protected function generateNamespaceElements($namespaces, $parent_element, $node_name = 'namespace')
532
    {
533
        foreach ($namespaces as $name => $sub_namespaces) {
534
            $node = new \DOMElement($node_name);
535
            $parent_element->appendChild($node);
536
            $node->setAttribute('name', $name);
537
            $fullName = $parent_element->nodeName == $node_name
538
                ? $parent_element->getAttribute('full_name') . '\\' . $name
539
                : $name;
540
            $node->setAttribute('full_name', $fullName);
541
            $this->generateNamespaceElements($sub_namespaces, $node, $node_name);
542
        }
543
    }
544
}
545