NamespaceIndexBuilder   B
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 197
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 197
rs 8.295
c 0
b 0
f 0
wmc 42

9 Methods

Rating   Name   Duplication   Size   Complexity  
B findChildNamespaces() 0 13 7
B addIndex() 0 19 5
A getHeaderForType() 0 3 1
A shouldRenderIndex() 0 10 4
C getElementList() 0 23 7
A addElementTocEntry() 0 8 3
A __construct() 0 7 1
A render() 0 23 3
C addFunctions() 0 39 11

How to fix   Complexity   

Complex Class

Complex classes like NamespaceIndexBuilder 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.

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

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
27
use phpDocumentor\Reflection\DocBlock\Tags\Param;
28
use phpDocumentor\Reflection\Fqsen;
29
use phpDocumentor\Reflection\Php\Constant;
30
use phpDocumentor\Reflection\Php\Argument;
31
use phpDocumentor\Reflection\Php\Function_;
32
use phpDocumentor\Reflection\Php\Namespace_;
33
34
/**
35
 * This class will build an index for each namespace.
36
 * It contains a toc for child namespaces, classes, traits, interfaces and functions
37
 *
38
 * @package JuliusHaertl\PHPDocToRst\Builder
39
 */
40
class NamespaceIndexBuilder extends PhpDomainBuilder {
41
42
    const RENDER_INDEX_NAMESPACE = 0;
43
    const RENDER_INDEX_CLASSES = 1;
44
    const RENDER_INDEX_TRAITS = 2;
45
    const RENDER_INDEX_INTERFACES = 3;
46
    const RENDER_INDEX_FUNCTIONS = 4;
47
    const RENDER_INDEX_CONSTANTS = 5;
48
49
    /** @var Namespace_ */
50
    private $currentNamespace;
51
52
    /** @var Namespace_[] */
53
    private $namespaces;
54
55
    /** @var Namespace_[] */
56
    private $childNamespaces = [];
57
58
    /** @var Function_[] */
59
    private $functions;
60
61
    /** @var Constant[] */
62
    private $constants;
63
64
    public function __construct($extensions, $namespaces, Namespace_ $current, $functions, $constants) {
65
        parent::__construct($extensions);
66
        $this->namespaces = $namespaces;
67
        $this->currentNamespace = $current;
68
        $this->functions = $functions;
69
        $this->constants = $constants;
70
        $this->findChildNamespaces();
71
    }
72
73
    /**
74
     * Find child namespaces for current namespace
75
     */
76
    private function findChildNamespaces() {
77
        $currentNamespaceFqsen = (string)$this->currentNamespace->getFqsen();
78
        /** @var Namespace_ $namespace */
79
        foreach ($this->namespaces as $namespace) {
80
            // check if not root and doesn't start with current namespace
81
            if ($currentNamespaceFqsen !== '\\' && strpos((string)$namespace->getFqsen(), $currentNamespaceFqsen . '\\') !== 0) {
82
                continue;
83
            }
84
            if ((string)$namespace->getFqsen() !== $currentNamespaceFqsen && strpos((string)$namespace->getFqsen(), $currentNamespaceFqsen) === 0) {
85
                // only keep first level children
86
                $childrenPath = substr((string)$namespace->getFqsen(), strlen((string)$this->currentNamespace->getFqsen()) + 1);
87
                if (strpos($childrenPath, '\\') === false) {
88
                    $this->childNamespaces[] = $namespace;
89
                }
90
            }
91
        }
92
    }
93
94
    public function render() {
95
        $currentNamespaceFqsen = (string)$this->currentNamespace->getFqsen();
96
        if ($currentNamespaceFqsen !== '\\') {
97
            $label = str_replace('\\', '-', $currentNamespaceFqsen);
98
            $this->addLine('.. _namespace' . $label . ':')->addLine();
99
            $this->addH1(RstBuilder::escape($this->currentNamespace->getName()));
100
            $this->addLine(self::escape($currentNamespaceFqsen))->addLine();
101
        } else {
102
            $label = 'root-namespace';
103
            $this->addLine('.. _namespace-' . $label . ':')->addLine();
104
            $this->addH1(RstBuilder::escape('\\'));
105
        }
106
        $this->addLine();
107
108
        $this->addIndex(self::RENDER_INDEX_NAMESPACE);
109
        $this->addIndex(self::RENDER_INDEX_INTERFACES);
110
        $this->addIndex(self::RENDER_INDEX_CLASSES);
111
        $this->addIndex(self::RENDER_INDEX_TRAITS);
112
113
        if ($this->shouldRenderIndex(self::RENDER_INDEX_CONSTANTS)) {
114
            $this->addConstants($this->constants);
115
        }
116
        $this->addFunctions();
117
    }
118
119
    protected function addIndex($type) {
120
        if ($this->shouldRenderIndex($type)) {
121
            $this->addH2($this->getHeaderForType($type));
122
            $this->addLine('.. toctree::');
123
            $this->indent();
124
            $this->addLine(':maxdepth: 1')->addLine();
125
            /** @var Fqsen $entry */
126
            foreach ($this->getElementList($type) as $entry) {
127
                if (!$this->shouldRenderIndex($type, $entry)) {
128
                    continue;
129
                }
130
                if ($type === self::RENDER_INDEX_NAMESPACE) {
131
                    $this->addLine($entry->getName() . ' <' . $entry->getName() . '/index>');
132
                } else {
133
                    $this->addElementTocEntry($entry);
134
                }
135
            }
136
            $this->unindent();
137
            $this->addLine();
138
        }
139
    }
140
141
    private function addFunctions() {
142
        if (!$this->shouldRenderIndex(self::RENDER_INDEX_FUNCTIONS)) {
143
            return;
144
        }
145
        $this->addH2('Functions');
146
        /** @var Function_ $function */
147
        foreach ($this->functions as $function) {
148
            if (!$this->shouldRenderIndex(self::RENDER_INDEX_FUNCTIONS, $function)) {
149
                continue;
150
            }
151
            $docBlock = $function->getDocBlock();
152
            $params = [];
153
            if ($docBlock !== null) {
154
                /** @var Param $param */
155
                foreach ($docBlock->getTagsByName('param') as $param) {
156
                    $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

156
                    $params[$param->/** @scrutinizer ignore-call */ getVariableName()] = $param;
Loading history...
157
                }
158
            }
159
            $args = '';
160
            /** @var Argument $argument */
161
            foreach ($function->getArguments() as $argument) {
162
                // TODO: defaults, types
163
                $args .= '$' . $argument->getName() . ', ';
164
            }
165
            $args = substr($args, 0, -2);
166
            $this->beginPhpDomain('function', $function->getName() . '(' . $args . ')');
167
            $this->addDocBlockDescription($function);
168
            if (!empty($params)) {
169
                foreach ($function->getArguments() as $argument) {
170
                    if (array_key_exists($argument->getName(), $params)) {
171
                        /** @var Param $param */
172
                        $param = $params[$argument->getName()];
173
                        if ($param !== null) {
174
                            $this->addMultiline(':param ' . self::escape($param->getType()) . ' $' . $argument->getName() . ': ' . $param->getDescription(), true);
175
                        }
176
                    }
177
                }
178
            }
179
            $this->endPhpDomain('function');
180
        }
181
    }
182
183
    private function addElementTocEntry(Fqsen $entry) {
184
        $currentNamespaceFqsen = (string)$this->currentNamespace->getFqsen();
185
        $subPath = $entry;
186
        if ($currentNamespaceFqsen !== '\\' && substr($entry, 0, strlen($currentNamespaceFqsen)) === $currentNamespaceFqsen) {
187
            $subPath = substr($entry, strlen($currentNamespaceFqsen));
188
        }
189
        $path = substr(str_replace('\\', '/', $subPath), 1);
190
        $this->addLine($entry->getName() . ' <' . $path . '>');
191
    }
192
193
    private function shouldRenderIndex($type, $element = null) {
194
        foreach ($this->extensions as $extension) {
195
            if (!$extension->shouldRenderIndex($type, $element)) {
196
                return false;
197
            }
198
        }
199
        if ($element === null) {
200
            return (count($this->getElementList($type)) > 0);
201
        }
202
        return true;
203
    }
204
205
    private function getHeaderForType($type) {
206
        $headers = [self::RENDER_INDEX_NAMESPACE => 'Namespaces', self::RENDER_INDEX_INTERFACES => 'Interfaces', self::RENDER_INDEX_CLASSES => 'Classes', self::RENDER_INDEX_TRAITS => 'Traits', self::RENDER_INDEX_FUNCTIONS => 'Functions', self::RENDER_INDEX_CONSTANTS => 'Constants'];
207
        return $headers[$type];
208
    }
209
210
    /**
211
     * @param int $type
212
     * @return array
213
     */
214
    private function getElementList($type) {
215
        $elements = [];
216
        switch ($type) {
217
            case self::RENDER_INDEX_NAMESPACE:
218
                $elements = $this->childNamespaces;
219
                break;
220
            case self::RENDER_INDEX_CLASSES:
221
                $elements = $this->currentNamespace->getClasses();
222
                break;
223
            case self::RENDER_INDEX_INTERFACES:
224
                $elements = $this->currentNamespace->getInterfaces();
225
                break;
226
            case self::RENDER_INDEX_TRAITS:
227
                $elements = $this->currentNamespace->getTraits();
228
                break;
229
            case self::RENDER_INDEX_FUNCTIONS:
230
                $elements = $this->functions;
231
                break;
232
            case self::RENDER_INDEX_CONSTANTS:
233
                $elements = $this->constants;
234
                break;
235
        }
236
        return $elements;
237
    }
238
239
}