Directory   D
last analyzed

Complexity

Total Complexity 59

Size/Duplication

Total Lines 302
Duplicated Lines 0 %

Test Coverage

Coverage 46.83%

Importance

Changes 0
Metric Value
eloc 113
dl 0
loc 302
ccs 59
cts 126
cp 0.4683
rs 4.08
c 0
b 0
f 0
wmc 59

18 Methods

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

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

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