Completed
Push — develop ( fbdd82...317691 )
by Mike
09:29
created

src/Application/Renderer/StructureXmlRenderer.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
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
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)
107
    {
108
        $child = new \DOMElement('file');
109
        $parent->appendChild($child);
110
111
        $router = new ForFileProxy($this->router->match($file));
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)
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
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