Directory   F
last analyzed

Complexity

Total Complexity 62

Size/Duplication

Total Lines 309
Duplicated Lines 0 %

Test Coverage

Coverage 46.09%

Importance

Changes 12
Bugs 3 Features 0
Metric Value
eloc 117
c 12
b 3
f 0
dl 0
loc 309
ccs 59
cts 128
cp 0.4609
rs 3.44
wmc 62

18 Methods

Rating   Name   Duplication   Size   Complexity  
B seekFirstPage() 0 20 9
A offsetSet() 0 7 2
A getIndexPage() 0 13 4
A removeChild() 0 3 1
A addChild() 0 3 1
A getIterator() 0 3 1
A setFirstPage() 0 3 1
A offsetGet() 0 3 1
A hasContent() 0 14 5
A sortBucket() 0 11 2
A getConfig() 0 7 2
A offsetExists() 0 3 1
A dump() 0 12 4
A offsetUnset() 0 3 1
A getLocalIndexPage() 0 9 2
B getFirstPage() 0 30 9
A getEntries() 0 3 1
C sort() 0 85 15

How to fix   Complexity   

Complex Class

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

1
<?php namespace Todaymade\Daux\Tree;
2
3
use ArrayIterator;
4
use RuntimeException;
5
use Todaymade\Daux\Config;
6
7
class Directory extends Entry implements \ArrayAccess, \IteratorAggregate
8
{
9
    /** @var Entry[] */
10
    protected $children = [];
11
12
    /** @var Content */
13
    protected $first_page;
14
15 34
    public function sort()
16
    {
17
        // Separate the values into buckets to sort them separately
18
        $buckets = [
19 34
            'up_numeric' => [],
20
            'up' => [],
21
            'index' => [],
22
            'numeric' => [],
23
            'normal' => [],
24
            'down_numeric' => [],
25
            'down' => [],
26
        ];
27
28 34
        foreach ($this->children as $key => $entry) {
29
            // In case of generated pages, the name might be empty.
30
            // Thus we are falling back to other solutions, otherwise the page would disappear from the tree.
31 34
            $name = $entry->getName();
32
33 34
            if (!$name) {
34
                $name = $entry->getTitle();
35
            }
36
37 34
            if (!$name) {
38
                $name = $key;
39
            }
40
41 34
            if (!$name) {
42
                continue;
43
            }
44
45 34
            if ($name == 'index' || $name == '_index') {
46 3
                $buckets['index'][$key] = $entry;
47 3
48
                continue;
49
            }
50 34
51 5
            if ($name[0] == '-') {
52 2
                if (is_numeric($name[1])) {
53 2
                    $exploded = explode('_', $name);
54 2
                    $buckets['down_numeric'][abs(substr($exploded[0], 1))][$key] = $entry;
55
56
                    continue;
57 3
                }
58 3
59
                $buckets['down'][$key] = $entry;
60
61 34
                continue;
62 1
            }
63 1
64 1
            if ($name[0] == '+') {
65 1
                if (is_numeric($name[1])) {
66
                    $exploded = explode('_', $name);
67
                    $buckets['up_numeric'][abs(substr($exploded[0], 1))][$key] = $entry;
68 1
69 1
                    continue;
70
                }
71
72 34
                $buckets['up'][$key] = $entry;
73 14
74 14
                continue;
75 14
            }
76
77
            if (is_numeric($name[0])) {
78 29
                $exploded = explode('_', $name);
79
                $buckets['numeric'][abs($exploded[0])][$key] = $entry;
80
81 34
                continue;
82 34
            }
83 34
84 34
            $buckets['normal'][$key] = $entry;
85 34
        }
86 14
87
        $final = [];
88
        foreach ($buckets as $name => $bucket) {
89 34
            if (substr($name, -7) == 'numeric') {
90
                ksort($bucket);
91
                foreach ($bucket as $sub_bucket) {
92
                    $final = $this->sortBucket($sub_bucket, $final);
93 34
                }
94 34
            } else {
95
                $final = $this->sortBucket($bucket, $final);
96 34
            }
97
        }
98
99 26
        $this->children = $final;
100 34
    }
101
102 34
    private function sortBucket($bucket, $final)
103 34
    {
104
        uasort($bucket, function (Entry $a, Entry $b) {
105
            return strcasecmp($a->getName(), $b->getName());
106 34
        });
107
108
        foreach ($bucket as $key => $value) {
109
            $final[$key] = $value;
110
        }
111
112 43
        return $final;
113
    }
114 43
115
    /**
116
     * @return Entry[]
117 51
     */
118
    public function getEntries()
119 51
    {
120 51
        return $this->children;
121
    }
122
123
    public function addChild(Entry $entry): void
124
    {
125
        $this->children[$entry->getUri()] = $entry;
126
    }
127
128
    public function removeChild(Entry $entry): void
129
    {
130 26
        unset($this->children[$entry->getUri()]);
131
    }
132 26
133
    public function getConfig(): Config
134
    {
135
        if (!$this->parent) {
136 26
            throw new \RuntimeException('Could not retrieve configuration. Are you sure that your tree has a Root ?');
137
        }
138
139 19
        return $this->parent->getConfig();
140 19
    }
141
142 19
    public function getLocalIndexPage()
143 1
    {
144
        $index_key = $this->getConfig()->getIndexKey();
145
146 18
        if (isset($this->children[$index_key])) {
147
            return $this->children[$index_key];
148
        }
149
150
        return false;
151
    }
152
153
    public function getIndexPage(): ?Content
154
    {
155
        $indexPage = $this->getLocalIndexPage();
156
157
        if ($indexPage instanceof Content) {
158
            return $indexPage;
159
        }
160
161
        if ($this->getConfig()->shouldInheritIndex() && $first_page = $this->seekFirstPage()) {
162
            return $first_page;
163
        }
164
165
        return null;
166
    }
167
168
    /**
169
     * Seek the first available page from descendants.
170
     */
171
    public function seekFirstPage(): ?Content
172
    {
173
        if ($this instanceof self) {
174
            $index_key = $this->getConfig()->getIndexKey();
175
            if (isset($this->children[$index_key]) && $this->children[$index_key] instanceof Content) {
176
                return $this->children[$index_key];
0 ignored issues
show
Bug Best Practice introduced by Stéphane Goetz
The expression return $this->children[$index_key] returns the type Todaymade\Daux\Tree\Entry which includes types incompatible with the type-hinted return Todaymade\Daux\Tree\Content|null.
Loading history...
177
            }
178
            foreach ($this->children as $node_key => $node) {
179
                if ($node instanceof Content) {
180
                    return $node;
181
                }
182
                if ($node instanceof self
183
                && strpos($node->getUri(), '.') !== 0
184
                && $childNode = $node->seekFirstPage()) {
185
                    return $childNode;
186
                }
187
            }
188
        }
189
190
        return null;
191
    }
192
193
    public function getFirstPage(): ?Content
194
    {
195
        if ($this->first_page) {
196
            return $this->first_page;
197
        }
198
199
        // First we try to find a real page
200
        foreach ($this->getEntries() as $node) {
201
            if ($node instanceof Content) {
202
                if ($this instanceof Root && $this->getIndexPage() == $node) {
203
                    // The homepage should not count as first page
204
                    continue;
205
                }
206
207
                $this->setFirstPage($node);
208
209
                return $node;
210
            }
211
        }
212
213
        // If we can't find one we check in the sub-directories
214
        foreach ($this->getEntries() as $node) {
215
            if ($node instanceof self && $page = $node->getFirstPage()) {
0 ignored issues
show
Bug introduced by onigoetz
The method getFirstPage() does not exist on Todaymade\Daux\Tree\Entry. It seems like you code against a sub-type of Todaymade\Daux\Tree\Entry such as Todaymade\Daux\Tree\Directory. ( Ignorable by Annotation )

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

215
            if ($node instanceof self && $page = $node->/** @scrutinizer ignore-call */ getFirstPage()) {
Loading history...
216
                $this->setFirstPage($page);
217
218
                return $page;
219
            }
220
        }
221
222
        return null;
223
    }
224
225
    public function setFirstPage(Content $first_page)
226
    {
227
        $this->first_page = $first_page;
228
    }
229
230
    /**
231
     * Used when creating the navigation.
232
     * Hides folders without showable content.
233
     */
234
    public function hasContent(): bool
235
    {
236
        foreach ($this->getEntries() as $node) {
237
            if ($node instanceof Content) {
238
                return true;
239
            }
240
            if ($node instanceof self) {
241
                if ($node->hasContent()) {
242
                    return true;
243
                }
244
            }
245
        }
246
247
        return false;
248
    }
249
250
    public function dump()
251
    {
252
        $dump = parent::dump();
253
254
        $dump['index'] = $this->getIndexPage() ? $this->getIndexPage()->getUrl() : '';
255
        $dump['first'] = $this->getFirstPage() ? $this->getFirstPage()->getUrl() : '';
256
257
        foreach ($this->getEntries() as $entry) {
258
            $dump['children'][] = $entry->dump();
259
        }
260
261
        return $dump;
262
    }
263
264
    /**
265
     * Whether a offset exists.
266
     *
267
     * @param mixed $offset an offset to check for
268
     *
269
     * @return bool true on success or false on failure
270
     */
271
    public function offsetExists($offset): bool
272
    {
273
        return array_key_exists($offset, $this->children);
274
    }
275
276
    /**
277
     * Offset to retrieve.
278
     *
279
     * @param mixed $offset the offset to retrieve
280
     *
281
     * @return Entry can return all value types
282
     */
283
    public function offsetGet($offset)
284
    {
285
        return $this->children[$offset];
286 2
    }
287
288 2
    /**
289
     * Offset to set.
290
     *
291
     * @param mixed $offset the offset to assign the value to
292
     * @param Entry $value the value to set
293
     */
294
    public function offsetSet($offset, $value)
295
    {
296
        if (!$value instanceof Entry) {
0 ignored issues
show
introduced by Stéphane Goetz
$value is always a sub-type of Todaymade\Daux\Tree\Entry.
Loading history...
297
            throw new RuntimeException('The value is not of type Entry');
298
        }
299
300
        $this->addChild($value);
301
    }
302
303
    /**
304
     * Offset to unset.
305
     *
306
     * @param string $offset the offset to unset
307
     */
308
    public function offsetUnset($offset)
309
    {
310
        unset($this->children[$offset]);
311
    }
312
313
    public function getIterator()
314
    {
315
        return new ArrayIterator($this->children);
316 1
    }
317
}
318