PhpDomainBuilder::addConstant()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 2
nop 1
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2017 Julius Härtl <[email protected]>
4
 *
5
 * @author Julius Härtl <[email protected]>
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 *  This program is free software: you can redistribute it and/or modify
10
 *  it under the terms of the GNU Affero General Public License as
11
 *  published by the Free Software Foundation, either version 3 of the
12
 *  License, or (at your option) any later version.
13
 *
14
 *  This program is distributed in the hope that it will be useful,
15
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 *  GNU Affero General Public License for more details.
18
 *
19
 *  You should have received a copy of the GNU Affero General Public License
20
 *  along with this program. If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
24
namespace JuliusHaertl\PHPDocToRst\Builder;
25
26
use JuliusHaertl\PHPDocToRst\Extension\Extension;
27
use phpDocumentor\Reflection\DocBlock;
28
use phpDocumentor\Reflection\DocBlock\Tags\Param;
29
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
30
use phpDocumentor\Reflection\DocBlock\Tags\See;
31
use phpDocumentor\Reflection\DocBlock\Tags\Since;
32
use phpDocumentor\Reflection\DocBlock\Tags\Throws;
33
use phpDocumentor\Reflection\Element;
34
use phpDocumentor\Reflection\Php\Argument;
35
use phpDocumentor\Reflection\Php\Class_;
36
use phpDocumentor\Reflection\Php\Constant;
37
use phpDocumentor\Reflection\Php\Function_;
38
use phpDocumentor\Reflection\Php\Interface_;
39
use phpDocumentor\Reflection\Php\Method;
40
use phpDocumentor\Reflection\Php\Property;
41
use phpDocumentor\Reflection\DocBlock\Tags\Deprecated;
42
use phpDocumentor\Reflection\Php\Trait_;
43
44
/**
45
 * Class to build reStructuredText file with sphinxcontrib-phpdomain syntax
46
 *
47
 * @package JuliusHaertl\PHPDocToRst\Builder
48
 */
49
class PhpDomainBuilder extends RstBuilder {
50
51
    const SECTION_BEFORE_DESCRIPTION = self::class . '::SECTION_BEFORE_DESCRIPTION';
52
    const SECTION_AFTER_DESCRIPTION = self::class . '::SECTION_AFTER_DESCRIPTION';
53
    const SECTION_AFTER_TITLE = self::class . '::SECTION_AFTER_TITLE';
54
    const SECTION_AFTER_INTRODUCTION = self::class . '::SECTION_AFTER_INTRODUCTION';
55
56
    use ExtensionBuilder {
57
        ExtensionBuilder::__construct as private __extensionConstructor;
58
    }
59
60
    public function __construct($extensions) {
61
        $this->__extensionConstructor($extensions);
62
        $this->addMultiline('.. role:: php(code)' . PHP_EOL . ':language: php', true);
63
        $this->addLine();
64
    }
65
66
    /**
67
     * Strip element name from Fqsen to return the namespace only
68
     *
69
     * @param Element $element
70
     * @return mixed
71
     */
72
    public static function getNamespace(Element $element) {
73
        return substr($element->getFqsen(), 0, strlen($element->getFqsen())-strlen('\\'. $element->getName()));
74
        //return str_replace('\\' . $element->getName(), '', $element->getFqsen());
75
    }
76
77
    /**
78
     * Add namespace
79
     * @param Element $element
80
     */
81
    protected function addPageHeader(Element $element) {
82
        $this->addH1(self::escape($element->getName()))->addLine();
83
        if (self::getNamespace($element) !== '') {
84
            $this->beginPhpDomain('namespace', substr(self::getNamespace($element), 1), false);
85
        }
86
        if ($element instanceof Class_) {
87
            $modifiers = $element->isAbstract() ? ' abstract' : '';
88
            $modifiers = $element->isFinal() ? ' final' : $modifiers;
89
            if ($modifiers !== '') {
90
                $this->addLine('.. rst-class:: ' . $modifiers)->addLine();
91
            }
92
        }
93
94
        $this->callExtensions(self::SECTION_AFTER_TITLE, $element);
95
96
97
        $this->beginPhpDomain($this->getTypeForClass($element), $element->getName(), false);
98
        $this->addLine();
99
    }
100
101
    private function getTypeForClass($element) {
102
        switch (get_class($element)) {
103
            case Class_::class:
104
                return 'class';
105
            case Interface_::class:
106
                return 'interface';
107
            case Trait_::class:
108
                return 'trait';
109
            case Function_::class:
110
                return 'function';
111
            case Method::class:
112
                return 'method';
113
            default:
114
                return '';
115
        }
116
    }
117
118
    protected function addAfterIntroduction($element) {
119
        $this->callExtensions(self::SECTION_AFTER_INTRODUCTION, $element);
120
    }
121
122
123
    protected function addConstants($constants) {
124
        if (count($constants) > 0) {
125
            $this->addH2('Constants');
126
            foreach ($constants as $constant) {
127
                if ($this->shouldRenderElement($constant)) {
128
                    $this->addConstant($constant);
129
                }
130
            }
131
        }
132
    }
133
134
    /**
135
     * @param Constant $constant
136
     */
137
    private function addConstant(Constant $constant) {
138
        $this->beginPhpDomain('const', $constant->getName() . ' = ' . self::escape($constant->getValue()));
139
        $docBlock = $constant->getDocBlock();
140
        $this->addDocBlockDescription($constant);
141
        if ($docBlock) {
142
            foreach ($docBlock->getTags() as $tag) {
143
                $this->addDocblockTag($tag->getName(), $docBlock);
144
            }
145
        }
146
        $this->endPhpDomain();
147
    }
148
149
    /**
150
     * @param Property[] $properties
151
     */
152
    protected function addProperties($properties) {
153
        if (count($properties) > 0) {
154
            $this->addH2('Properties');
155
            foreach ($properties as $property) {
156
                if ($this->shouldRenderElement($property)) {
157
                    $this->addProperty($property);
158
                }
159
            }
160
        }
161
    }
162
163
    /**
164
     * @param Property $property
165
     */
166
    private function addProperty(Property $property) {
167
        $modifiers = $property->isStatic() ? '' : ' static' ;
168
        $this->beginPhpDomain('attr', $property->getVisibility() . $modifiers . ' ' . $property->getName());
169
        $docBlock = $property->getDocBlock();
170
        $this->addDocBlockDescription($property);
171
        if ($docBlock) {
172
            foreach ($docBlock->getTags() as $tag) {
173
                $this->addDocblockTag($tag->getName(), $docBlock);
174
            }
175
        }
176
        $this->endPhpDomain();
177
    }
178
179
    /**
180
     * @param Interface_|Class_ $element
181
     */
182
    protected function addParent($element) {
183
        if ($element instanceof Class_) {
184
            $parent = $element->getParent();
185
            if ($parent !== null) {
186
                $this->addFieldList('Parent', $parent !== null ? $this->getLink('class', $parent) : '');
187
            }
188
        }
189
        if ($element instanceof Interface_) {
190
            $parents = $element->getParents();
191
            foreach ($parents as $parent) {
192
                $this->addFieldList('Parent', $parent !== null ? $this->getLink('interface', $parent) : '');
193
            }
194
        }
195
    }
196
197
    /**
198
     * @param Class_|Trait_ $element
199
     */
200
    protected function addUsedTraits($element) {
201
        $usedTraits = '';
202
        foreach ($element->getUsedTraits() as $trait) {
203
            $usedTraits .= $this->getLink('trait', $trait) . ' ';
204
        }
205
        if ($usedTraits !== '') {
206
            $this->addFieldList('Used traits', $usedTraits);
207
        }
208
    }
209
210
    /**
211
     * @param $methods
212
     */
213
    protected function addMethods($methods) {
214
        if (count($methods) > 0) {
215
            $this->addH2('Methods');
216
            foreach ($methods as $method) {
217
                $this->addMethod($method);
218
            }
219
        }
220
    }
221
222
    private function addMethod(Method $method) {
223
        if (!$this->shouldRenderElement($method)) {
224
            return;
225
        }
226
        $docBlock = $method->getDocBlock();
227
        $params = [];
228
        $deprecated = [];
229
        if ($docBlock !== null) {
230
            /** @var Param $param */
231
            foreach ($docBlock->getTagsByName('param') as $param) {
232
                $params[$param->getVariableName()] = $param;
0 ignored issues
show
Bug introduced by
The method getVariableName() does not exist on phpDocumentor\Reflection\DocBlock\Tag. It seems like you code against a sub-type of phpDocumentor\Reflection\DocBlock\Tag such as phpDocumentor\Reflection\DocBlock\Tags\Property or phpDocumentor\Reflection\DocBlock\Tags\Var_ or phpDocumentor\Reflection\DocBlock\Tags\Param or phpDocumentor\Reflection...lock\Tags\PropertyWrite or phpDocumentor\Reflection...Block\Tags\PropertyRead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

232
                $params[$param->/** @scrutinizer ignore-call */ getVariableName()] = $param;
Loading history...
233
            }
234
            $deprecated = $docBlock->getTagsByName('deprecated');
235
        }
236
        $args = '';
237
        /** @var Argument $argument */
238
        foreach ($method->getArguments() as $argument) {
239
            // This will work after https://github.com/phpDocumentor/Reflection/pull/109 is merged
240
            foreach ($argument->getTypes() as $type) {
241
                $args .= self::escape($type) . '|';
242
            }
243
            $args = substr($args, 0, -1) . ' ';
244
            if($argument->isVariadic()) {
245
                $args .= '...';
246
            }
247
            if($argument->isByReference()) {
248
                $args .= '&';
249
            }
250
            $args .= '$' . $argument->getName();
251
            $default = $argument->getDefault();
252
            if ($default !== null) {
253
                $default = $default === '' ? '""' : $default;
254
                $args .= '=' . self::escape($default);
255
            }
256
            $args .= ', ';
257
        }
258
        $args = substr($args, 0, -2);
259
260
        $modifiers = $method->getVisibility();
261
        $modifiers .= $method->isAbstract() ? ' abstract' : '';
262
        $modifiers .= $method->isFinal() ? ' final' : '';
263
        $modifiers .= $method->isStatic() ? ' static' : '';
264
        $deprecated = count($deprecated) > 0 ? ' deprecated' : '';
265
        $this->addLine('.. rst-class:: ' . $modifiers . $deprecated)->addLine();
266
        $this->indent();
267
        $this->beginPhpDomain('method', $modifiers . ' ' . $method->getName() . '(' . $args . ')');
268
        $this->addDocBlockDescription($method);
269
        $this->addLine();
270
        if (!empty($params)) {
271
            $parameterDetails = '';
272
            foreach ($method->getArguments() as $argument) {
273
                if (!array_key_exists($argument->getName(), $params)) {
274
                    continue;
275
                }
276
                /** @var Param $param */
277
                $param = $params[$argument->getName()];
278
                if ($param !== null) {
279
                    $typString = $param->getType();
280
                    // Remove first \ to allow references
281
                    if (0 === strpos($typString, '\\')) {
282
                        $typString = substr($typString, 1);
283
                    }
284
                    $paramItem = '* ';
285
                    $paramItem .= '**$' . $argument->getName() . '** ';
286
                    if ($typString !== null) {
287
                        $paramItem .= '(' . self::typesToRst($typString) . ') ';
288
                    }
289
                    $paramItem .= ' ' . $param->getDescription();
290
                    $parameterDetails .= $paramItem . PHP_EOL;
291
                }
292
            }
293
            $this->addFieldList('Parameters', $parameterDetails);
294
        }
295
        if ($docBlock !== null) {
296
            foreach ($docBlock->getTags() as $tag) {
297
                $this->addDocblockTag($tag->getName(), $docBlock);
298
            }
299
        }
300
        $this->endPhpDomain('method');
301
        $this->unindent();
302
    }
303
304
    /**
305
     * @param $type string
306
     * @param $fqsen string
307
     * @return string
308
     */
309
    public static function getLink($type, $fqsen, $description='') {
310
        if($description !== '') {
311
            return ':php:' . $type . ':`' . RstBuilder::escape($description) . '<' . RstBuilder::escape(substr($fqsen, 1)) . '>`';
312
        }
313
        return ':php:' . $type . ':`' . RstBuilder::escape(substr($fqsen, 1)) . '`';
314
    }
315
316
    /**
317
     * @param $type string
318
     * @param $name string
319
     * @param $indent bool Should indent after the section started
320
     */
321
    public function beginPhpDomain($type, $name, $indent = true) {
322
        // FIXME: Add checks if it is properly ended
323
        $this->addLine('.. php:' . $type . ':: ' . $name)->addLine();
324
        if ($indent === true) {
325
            $this->indent();
326
        }
327
    }
328
329
    /**
330
     * @param string $type
331
     * @return $this
332
     */
333
    public function endPhpDomain($type = '') {
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

333
    public function endPhpDomain(/** @scrutinizer ignore-unused */ $type = '') {

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

Loading history...
334
        $this->unindent();
335
        $this->addLine();
336
    }
337
338
    /**
339
     * @param Class_|Interface_|Trait_|Property|Method|Constant $element
340
     * @return $this
341
     */
342
    public function addDocBlockDescription($element) {
343
        if ($element === null) {
344
            return $this;
345
        }
346
        $docBlock = $element->getDocBlock();
347
        $this->callExtensions(self::SECTION_BEFORE_DESCRIPTION, $element);
348
        if ($docBlock !== null && $docBlock->getSummary() !== '') {
349
            $this->addLine('.. rst-class:: phpdoc-description')->addLine();
350
            $this->indent();
351
            $this->addMultilineWithoutRendering(RstBuilder::escape($docBlock->getSummary()))->addLine();
352
            if ((string)$docBlock->getDescription() !== '') {
353
                $this->addMultilineWithoutRendering(RstBuilder::escape($docBlock->getDescription()))->addLine();
354
            }
355
            $this->unindent();
356
        }
357
        $this->callExtensions(self::SECTION_AFTER_DESCRIPTION, $element);
358
        return $this;
359
    }
360
361
    /**
362
     * @param string $tagName Name of the tag to parse
363
     * @param DocBlock $docBlock
364
     */
365
    protected function addDocblockTag($tagName, DocBlock $docBlock) {
366
        $tags = $docBlock->getTagsByName($tagName);
367
        switch ($tagName) {
368
            case 'return':
369
                if (count($tags) === 0) continue;
370
                /** @var Return_ $return */
371
                $return = $tags[0];
372
                $this->addMultiline(':Returns: ' . self::typesToRst($return->getType()) . ' ' . RstBuilder::escape($return->getDescription()), true);
373
                break;
374
            case 'var':
375
                if (count($tags) === 0) continue;
376
                /** @var DocBlock\Tags\Var_ $return */
377
                $return = $tags[0];
378
                $this->addMultiline(':Type: ' . self::typesToRst($return->getType()) . ' ' . RstBuilder::escape($return->getDescription()), true);
379
                break;
380
            case 'throws':
381
                if (count($tags) === 0) continue;
382
                /** @var Throws $tag */
383
                foreach ($tags as $tag) {
384
                    $this->addMultiline(':Throws: ' . self::typesToRst($tag->getType()) . ' ' . RstBuilder::escape($tag->getDescription()), true);
0 ignored issues
show
Bug introduced by
The method getType() does not exist on phpDocumentor\Reflection\DocBlock\Tag. It seems like you code against a sub-type of phpDocumentor\Reflection\DocBlock\Tag such as phpDocumentor\Reflection\DocBlock\Tags\Property or phpDocumentor\Reflection\DocBlock\Tags\Var_ or phpDocumentor\Reflection\DocBlock\Tags\Param or phpDocumentor\Reflection...lock\Tags\PropertyWrite or phpDocumentor\Reflection\DocBlock\Tags\Return_ or phpDocumentor\Reflection\DocBlock\Tags\Throws or phpDocumentor\Reflection...Block\Tags\PropertyRead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

384
                    $this->addMultiline(':Throws: ' . self::typesToRst($tag->/** @scrutinizer ignore-call */ getType()) . ' ' . RstBuilder::escape($tag->getDescription()), true);
Loading history...
385
                }
386
                break;
387
            case 'since':
388
                if (count($tags) === 0) continue;
389
                /** @var Since $return */
390
                $return = $tags[0];
391
                $this->addMultiline(':Since: ' . $return->getVersion() . ' ' . RstBuilder::escape($return->getDescription()), true);
392
                break;
393
            case 'deprecated':
394
                if (count($tags) === 0) continue;
395
                /** @var Deprecated $return */
396
                $return = $tags[0];
397
                $this->addMultiline(':Deprecated: ' . $return->getVersion() . ' ' . RstBuilder::escape($return->getDescription()), true);
398
                break;
399
            case 'see':
400
                if (count($tags) === 0) continue;
401
                /** @var See $return */
402
                $return = $tags[0];
403
                $this->addMultiline(':See: ' . self::typesToRst($return->getReference()) . ' ' . RstBuilder::escape($return->getDescription()), true);
404
                break;
405
            case 'license':
406
                if (count($tags) === 0) continue;
407
                /** @var DocBlock\Tags\BaseTag $return */
408
                $return = $tags[0];
409
                $this->addMultiline(':License: ' . RstBuilder::escape($return->getDescription()), true);
410
                break;
411
            case 'param':
412
                // param handling is done by subclasses since it is more that docbook parsing
413
                break;
414
            default:
415
                //echo 'Tag handling not defined for: ' . $tag . PHP_EOL;
416
                break;
417
        }
418
419
    }
420
421
    /**
422
     * @param string $typesString
423
     * @return bool|string
424
     */
425
    public static function typesToRst($typesString) {
426
        // http://docs.phpdoc.org/guides/types.html
427
        $whitelist = [
428
            'string', 'int', 'integer', 'float', 'bool', 'boolean', 'array', 'resource', 'null', 'callable',
429
            'mixed', 'void', 'object', 'false', 'true', 'self', 'static', '$this'
430
        ];
431
        $types = explode('|', $typesString);
432
        $result = '';
433
        /** @var string $type */
434
        foreach ($types as $typeFull) {
435
            $type = str_replace('[]', '', $typeFull);
436
            if (in_array($type, $whitelist, true)) {
437
                $result .= $typeFull . ' | ';
438
                continue;
439
            }
440
            if (0 === strpos($type, '\\'))
441
                $type = substr($type, 1);
442
            $result .= ':any:`' . RstBuilder::escape($typeFull) . ' <' . RstBuilder::escape($type) . '>` | ';
443
        }
444
        return substr($result, 0, -3);
445
    }
446
447
    /**
448
     * @param Element $element
449
     * @return bool
450
     */
451
    public function shouldRenderElement(Element $element) {
452
        /** @var Extension $extension */
453
        foreach ($this->extensions as $extension) {
454
            if ($extension->shouldRenderElement($element) === false) {
455
                return false;
456
            }
457
        }
458
        return true;
459
    }
460
461
462
}