Passed
Push — master ( 4b6191...118b18 )
by Julius
01:48
created

PhpDomainBuilder::getNamespace()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
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
54
    use ExtensionBuilder {
55
        ExtensionBuilder::__construct as private __extensionConstructor;
56
    }
57
58
    public function __construct($extensions) {
59
        $this->__extensionConstructor($extensions);
60
        $this->addMultiline('.. role:: php(code)' . PHP_EOL . ':language: php', true);
61
        $this->addLine();
62
    }
63
64
    /**
65
     * Strip element name from Fqsen to return the namespace only
66
     *
67
     * @param Element $element
68
     * @return mixed
69
     */
70
    public static function getNamespace(Element $element) {
71
        return substr($element->getFqsen(), 0, strlen($element->getFqsen())-strlen('\\'. $element->getName()));
72
        //return str_replace('\\' . $element->getName(), '', $element->getFqsen());
73
    }
74
75
    /**
76
     * Add namespace
77
     * @param Element $element
78
     */
79
    protected function addPageHeader(Element $element) {
80
        $this->addH1(self::escape($element->getFqsen()))->addLine();
81
        if (self::getNamespace($element) !== '') {
82
            $this->beginPhpDomain('namespace', substr(self::getNamespace($element), 1), false);
83
        }
84
        if ($element instanceof Class_) {
85
            $modifiers = $element->isAbstract() ? ' abstract' : '';
86
            $modifiers = $element->isFinal() ? ' final' : $modifiers;
87
            if ($modifiers !== '') {
88
                $this->addLine('.. rst-class:: ' . $modifiers)->addLine();
89
            }
90
        }
91
92
        $this->beginPhpDomain($this->getTypeForClass($element), $element->getName(), false);
93
        $this->addLine();
94
    }
95
96
    private function getTypeForClass($element) {
97
        switch (get_class($element)) {
98
            case Class_::class:
99
                return 'class';
100
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
101
            case Interface_::class:
102
                return 'interface';
103
                break;
104
            case Trait_::class:
105
                return 'trait';
106
                break;
107
            case Function_::class:
108
                return 'function';
109
                break;
110
            case Method::class:
111
                return 'method';
112
            default:
113
                return '';
114
        }
115
    }
116
117
118
    protected function addConstants($constants) {
119
        if (count($constants) > 0) {
120
            $this->addH2('Constants');
121
            foreach ($constants as $constant) {
122
                if ($this->shouldRenderElement($constant)) {
123
                    $this->addConstant($constant);
124
                }
125
            }
126
        }
127
    }
128
129
    /**
130
     * @param Constant $constant
131
     */
132 View Code Duplication
    private function addConstant(Constant $constant) {
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...
133
        $this->beginPhpDomain('const', $constant->getName() . ' = ' . $constant->getValue());
134
        $docBlock = $constant->getDocBlock();
135
        $this->addDocBlockDescription($constant);
136
        if ($docBlock) {
137
            foreach ($docBlock->getTags() as $tag) {
138
                $this->addDocblockTag($tag->getName(), $docBlock);
139
            }
140
        }
141
        $this->endPhpDomain();
142
    }
143
144
    /**
145
     * @param Property[] $properties
146
     */
147
    protected function addProperties($properties) {
148
        if (count($properties) > 0) {
149
            $this->addH2('Properties');
150
            foreach ($properties as $property) {
151
                if ($this->shouldRenderElement($property)) {
152
                    $this->addProperty($property);
153
                }
154
            }
155
        }
156
    }
157
158
    /**
159
     * @param Property $property
160
     */
161 View Code Duplication
    private function addProperty(Property $property) {
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...
162
        $this->beginPhpDomain('attr', $property->getName());
163
        $docBlock = $property->getDocBlock();
164
        $this->addDocBlockDescription($property);
165
        if ($docBlock) {
166
            foreach ($docBlock->getTags() as $tag) {
167
                $this->addDocblockTag($tag->getName(), $docBlock);
168
            }
169
        }
170
        $this->endPhpDomain();
171
    }
172
173
    /**
174
     * @param Interface_|Class_|Trait_ $element
175
     */
176
    protected function addParent($element) {
177 View Code Duplication
        if ($element instanceof Class_) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

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

Loading history...
178
            $parent = $element->getParent();
179
            if ($parent !== null) {
180
                $this->addFieldList('Parent', $parent !== null ? $this->getLink('class', $parent) : '');
181
            }
182
        }
183 View Code Duplication
        if ($element instanceof Trait_) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

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

Loading history...
184
            $parent = $element->getParent();
1 ignored issue
show
Bug introduced by
The method getParent() does not exist on phpDocumentor\Reflection\Php\Trait_. ( Ignorable by Annotation )

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

184
            $parent = $element->/** @scrutinizer ignore-call */ getParent();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getParent() does not exist on phpDocumentor\Reflection\Php\Interface_. Did you maybe mean getParents()? ( Ignorable by Annotation )

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

184
            $parent = $element->/** @scrutinizer ignore-call */ getParent();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
185
            if ($parent !== null) {
186
                $this->addFieldList('Parent', $parent !== null ? $this->getLink('trait', $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
    protected function addMethods($methods) {
211
        if (count($methods) > 0) {
212
            $this->addH2('Methods');
213
            foreach ($methods as $method) {
214
                $this->addMethod($method);
215
            }
216
        }
217
    }
218
219
    private function addMethod(Method $method) {
220
        if (!$this->shouldRenderElement($method)) {
221
            return;
222
        }
223
        $docBlock = $method->getDocBlock();
224
        $params = [];
225
        if ($docBlock !== null) {
226
            /** @var Param $param */
227
            foreach ($docBlock->getTagsByName('param') as $param) {
228
                $params[$param->getVariableName()] = $param;
1 ignored issue
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

228
                $params[$param->/** @scrutinizer ignore-call */ getVariableName()] = $param;
Loading history...
229
            }
230
        }
231
        $args = '';
232
        /** @var Argument $argument */
233
        foreach ($method->getArguments() as $argument) {
234
            // TODO: defaults, types
235
            $args .= ' $' . $argument->getName() . ', ';
236
        }
237
        $args = substr($args, 0, -2);
238
239
        $modifiers = $method->getVisibility();
240
        $modifiers .= $method->isAbstract() ? ' abstract' : '';
241
        $modifiers .= $method->isFinal() ? ' final' : '';
242
        $modifiers .= $method->isStatic() ? ' static' : '';
243
        $this->addLine('.. rst-class:: ' . $modifiers)->addLine();
244
        $this->indent();
245
        $this->beginPhpDomain('method', $method->getName() . '(' . $args . ')');
246
        $this->addDocBlockDescription($method);
247
        $this->addLine();
248 View Code Duplication
        if (!empty($params)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

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

Loading history...
249
            foreach ($method->getArguments() as $argument) {
250
                /** @var Param $param */
251
                $param = $params[$argument->getName()];
252
                if ($param !== null) $this->addMultiline(':param ' . self::escape($param->getType()) . ' $' . $argument->getName() . ': ' . $param->getDescription(), true);
253
            }
254
        }
255
        $this->endPhpDomain('method');
256
        $this->unindent();
257
    }
258
259
    /**
260
     * @param $type string
261
     * @param $fqsen string
262
     * @return string
263
     */
264
    public function getLink($type, $fqsen) {
265
        return ':php:' . $type . ':`' . RstBuilder::escape(substr($fqsen, 1)) . '`';
266
    }
267
268
    /**
269
     * @param $type string
270
     * @param $name string
271
     * @param $indent bool Should indent after the section started
272
     */
273
    public function beginPhpDomain($type, $name, $indent = true) {
274
        // FIXME: Add checks if it is properly ended
275
        $this->addLine('.. php:' . $type . ':: ' . $name)->addLine();
276
        if ($indent === true) {
277
            $this->indent();
278
        }
279
    }
280
281
    /**
282
     * @param string $type
283
     */
284
    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

284
    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...
285
        $this->unindent();
286
        $this->addLine();
287
    }
288
289
    /**
290
     * @param Class_|Interface_|Trait_|Property|Method|Constant $element
291
     * @return $this
292
     */
293
    public function addDocBlockDescription($element) {
294
        if ($element === null) {
295
            return;
296
        }
297
        $docBlock = $element->getDocBlock();
298
        $this->callExtensions(self::SECTION_BEFORE_DESCRIPTION, $element);
299
        if ($docBlock !== null) {
300
            $this->addMultiline($docBlock->getSummary())->addLine();
301
            $this->addMultiline($docBlock->getDescription())->addLine();
302
        }
303
        $this->callExtensions(self::SECTION_AFTER_DESCRIPTION, $element);
304
        return $this;
305
    }
306
307
    /**
308
     * @param string $tagName Name of the tag to parse
309
     * @param DocBlock $docBlock
310
     */
311
    protected function addDocblockTag($tagName, DocBlock $docBlock) {
312
        $tags = $docBlock->getTagsByName($tagName);
313
        switch ($tagName) {
314 View Code Duplication
            case 'return':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

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

Loading history...
315
                if (count($tags) === 0) continue;
316
                /** @var Return_ $return */
317
                $return = $tags[0];
318
                $this->addMultiline(':Returns: ' . $return->getType() . ' ' . RstBuilder::escape($return->getDescription()), true);
319
                break;
320 View Code Duplication
            case 'var':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

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

Loading history...
321
                if (count($tags) === 0) continue;
322
                /** @var DocBlock\Tags\Var_ $return */
323
                $return = $tags[0];
324
                $this->addMultiline(':Type: ' . self::typesToRst($return->getType()) . ' ' . RstBuilder::escape($return->getDescription()), true);
325
                break;
326 View Code Duplication
            case 'throws':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

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

Loading history...
327
                if (count($tags) === 0) continue;
328
                /** @var Throws $tag */
329
                foreach ($tags as $tag) {
330
                    $this->addMultiline(':Throws: ' . $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

330
                    $this->addMultiline(':Throws: ' . $tag->/** @scrutinizer ignore-call */ getType() . ' ' . RstBuilder::escape($tag->getDescription()), true);
Loading history...
331
                }
332
                break;
333 View Code Duplication
            case 'since':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

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

Loading history...
334
                if (count($tags) === 0) continue;
335
                /** @var Since $return */
336
                $return = $tags[0];
337
                $this->addMultiline(':Since: ' . $return->getVersion() . ' ' . RstBuilder::escape($return->getDescription()), true);
338
                break;
339 View Code Duplication
            case 'deprecated':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

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

Loading history...
340
                if (count($tags) === 0) continue;
341
                /** @var Deprecated $return */
342
                $return = $tags[0];
343
                $this->addMultiline(':Deprecated: ' . $return->getVersion() . ' ' . RstBuilder::escape($return->getDescription()), true);
344
                break;
345 View Code Duplication
            case 'see':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

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

Loading history...
346
                if (count($tags) === 0) continue;
347
                /** @var See $return */
348
                $return = $tags[0];
349
                $this->addMultiline(':See: ' . $return->getReference() . ' ' . RstBuilder::escape($return->getDescription()), true);
350
                break;
351 View Code Duplication
            case 'license':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

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

Loading history...
352
                if (count($tags) === 0) continue;
353
                /** @var DocBlock\Tags\BaseTag $return */
354
                $return = $tags[0];
355
                $this->addMultiline(':License: ' . RstBuilder::escape($return->getDescription()), true);
356
                break;
357
            case 'param':
358
                // param handling is done by subclasses since it is more that docbook parsing
359
                break;
360
            default:
361
                //echo 'Tag handling not defined for: ' . $tag . PHP_EOL;
362
                break;
363
        }
364
365
    }
366
367
    public static function typesToRst($types) {
368
        // http://docs.phpdoc.org/guides/types.html
369
        $whitelist = [
370
            'string', 'int', 'integer', 'float', 'bool', 'boolean', 'array', 'resource', 'null', 'callable',
371
            'mixed', 'void', 'object', 'false', 'true', 'self', 'static', '$this'
372
        ];
373
        $types = explode('|', $types);
374
        $result = '';
375
        /** @var string $type */
376
        foreach ($types as $type) {
377
            $type = str_replace('[]', '', $type);
378
            if (in_array($type, $whitelist)) {
379
                $result .= $type . ' | ';
380
                continue;
381
            }
382
            if (0 === strpos($type, '\\'))
383
                $type = substr($type, 1);
384
            // we could use :any: here but resolve_any_xref is not implemented by sphinxcontrib.phpdomain
385
            // FIXME: once https://github.com/markstory/sphinxcontrib-phpdomain/pull/14 is merged
386
            // $result .= ':any:`' . RstBuilder::escape($type) . '` | ';
387
            $result .= '`' . RstBuilder::escape($type) . '` | ';
388
        }
389
        return substr($result, 0, -3);
390
    }
391
392
    public function shouldRenderElement(Element $element) {
393
        /** @var Extension $extension */
394
        foreach ($this->extensions as $extension) {
395
            if ($extension->shouldRenderElement($element) === false) {
396
                return false;
397
            }
398
        }
399
        return true;
400
    }
401
402
403
}