Passed
Push — master ( 437a52...3276d4 )
by Stéphane
02:35
created

Directory   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 310
Duplicated Lines 0 %

Test Coverage

Coverage 47.29%

Importance

Changes 12
Bugs 2 Features 0
Metric Value
eloc 116
c 12
b 2
f 0
dl 0
loc 310
ccs 61
cts 129
cp 0.4729
rs 3.52
wmc 61

18 Methods

Rating   Name   Duplication   Size   Complexity  
A offsetSet() 0 7 2
A getIndexPage() 0 11 4
A removeChild() 0 3 1
A addChild() 0 3 1
C sort() 0 79 15
A getIterator() 0 3 1
A setFirstPage() 0 3 1
A offsetGet() 0 3 1
A hasContent() 0 13 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 8 2
B getFirstPage() 0 30 9
B seekFirstPage() 0 20 8
A getEntries() 0 3 1

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
                continue;
48
            }
49
50 34
            if ($name[0] == '-') {
51 5
                if (is_numeric($name[1])) {
52 2
                    $exploded = explode('_', $name);
53 2
                    $buckets['down_numeric'][abs(substr($exploded[0], 1))][$key] = $entry;
54 2
                    continue;
55
                }
56
57 3
                $buckets['down'][$key] = $entry;
58 3
                continue;
59
            }
60
61 34
            if ($name[0] == '+') {
62 1
                if (is_numeric($name[1])) {
63 1
                    $exploded = explode('_', $name);
64 1
                    $buckets['up_numeric'][abs(substr($exploded[0], 1))][$key] = $entry;
65 1
                    continue;
66
                }
67
68 1
                $buckets['up'][$key] = $entry;
69 1
                continue;
70
            }
71
72 34
            if (is_numeric($name[0])) {
73 14
                $exploded = explode('_', $name);
74 14
                $buckets['numeric'][abs($exploded[0])][$key] = $entry;
75 14
                continue;
76
            }
77
78 29
            $buckets['normal'][$key] = $entry;
79
        }
80
81 34
        $final = [];
82 34
        foreach ($buckets as $name => $bucket) {
83 34
            if (substr($name, -7) == 'numeric') {
84 34
                ksort($bucket);
85 34
                foreach ($bucket as $sub_bucket) {
86 14
                    $final = $this->sortBucket($sub_bucket, $final);
87
                }
88
            } else {
89 34
                $final = $this->sortBucket($bucket, $final);
90
            }
91
        }
92
93 34
        $this->children = $final;
94 34
    }
95
96 34
    private function sortBucket($bucket, $final)
97
    {
98
        uasort($bucket, function(Entry $a, Entry $b) {
99 26
            return strcasecmp($a->getName(), $b->getName());
100 34
        });
101
102 34
        foreach ($bucket as $key => $value) {
103 34
            $final[$key] = $value;
104
        }
105
106 34
        return $final;
107
    }
108
109
    /**
110
     * @return Entry[]
111
     */
112 43
    public function getEntries()
113
    {
114 43
        return $this->children;
115
    }
116
117 51
    public function addChild(Entry $entry): void
118
    {
119 51
        $this->children[$entry->getUri()] = $entry;
120 51
    }
121
122
    public function removeChild(Entry $entry): void
123
    {
124
        unset($this->children[$entry->getUri()]);
125
    }
126
127
    /**
128
     * @return Config
129
     */
130 26
    public function getConfig(): Config
131
    {
132 26
        if (!$this->parent) {
133
            throw new \RuntimeException('Could not retrieve configuration. Are you sure that your tree has a Root ?');
134
        }
135
136 26
        return $this->parent->getConfig();
137
    }
138
139 19
    public function getLocalIndexPage() {
140 19
        $index_key = $this->getConfig()->getIndexKey();
141
142 19
        if (isset($this->children[$index_key])) {
143 1
            return $this->children[$index_key];
144
        }
145
146 18
        return false;
147
    }
148
149
    /**
150
     * @return Content|null
151
     */
152
    public function getIndexPage(): ?Content
153
    {
154
        if ($this->getLocalIndexPage()) {
155
            return $this->getLocalIndexPage();
0 ignored issues
show
Bug Best Practice introduced by Stéphane Goetz
The expression return $this->getLocalIndexPage() returns the type Todaymade\Daux\Tree\Entry which includes types incompatible with the type-hinted return Todaymade\Daux\Tree\Content|null.
Loading history...
156
        }
157
158
        if ($this->getConfig()->shouldInheritIndex() && $first_page = $this->seekFirstPage()) {
159
            return $first_page;
160
        }
161
162
        return null;
163
    }
164
165
    /**
166
     * Seek the first available page from descendants
167
     * @return Content|null
168
     */
169
    public function seekFirstPage(): ?Content
170
    {
171
        if ($this instanceof self) {
172
            $index_key = $this->getConfig()->getIndexKey();
173
            if (isset($this->children[$index_key])) {
174
                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...
175
            }
176
            foreach ($this->children as $node_key => $node) {
177
                if ($node instanceof Content) {
178
                    return $node;
179
                }
180
                if ($node instanceof self
181
                && strpos($node->getUri(), '.') !== 0
182
                && $childNode = $node->seekFirstPage()) {
183
                    return $childNode;
184
                }
185
            }
186
        }
187
188
        return null;
189
    }
190
191
    /**
192
     * @return Content|null
193
     */
194
    public function getFirstPage(): ?Content
195
    {
196
        if ($this->first_page) {
197
            return $this->first_page;
198
        }
199
200
        // First we try to find a real page
201
        foreach ($this->getEntries() as $node) {
202
            if ($node instanceof Content) {
203
                if ($this instanceof Root && $this->getIndexPage() == $node) {
204
                    // The homepage should not count as first page
205
                    continue;
206
                }
207
208
                $this->setFirstPage($node);
209
210
                return $node;
211
            }
212
        }
213
214
        // If we can't find one we check in the sub-directories
215
        foreach ($this->getEntries() as $node) {
216
            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

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