Passed
Branch master (7a7694)
by Andreas
18:25
created

midcom_helper_reflector_tree   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 320
Duplicated Lines 0 %

Test Coverage

Coverage 68.18%

Importance

Changes 10
Bugs 1 Features 0
Metric Value
eloc 142
dl 0
loc 320
ccs 105
cts 154
cp 0.6818
rs 6.96
c 10
b 1
f 0
wmc 53

14 Methods

Rating   Name   Duplication   Size   Complexity  
A _child_objects_type_qb() 0 19 5
A get_parent_class() 0 8 2
A resolve_path() 0 4 1
A _check_permissions() 0 10 4
A add_schema_sorts_to_qb() 0 9 3
A get_child_objects() 0 23 5
A _root_objects_qb() 0 15 3
B get_link_field() 0 35 7
A get_tree() 0 20 4
A resolve_path_parts() 0 24 3
A _get_type_qb() 0 20 4
A _resolve_root_classes() 0 30 5
A get_child_classes() 0 20 5
A get_root_classes() 0 7 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
/**
3
 * @package midcom.helper.reflector
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
/**
10
 * The Grand Unified Reflector, Tree information
11
 *
12
 * @package midcom.helper.reflector
13
 */
14
class midcom_helper_reflector_tree extends midcom_helper_reflector
15
{
16
    /**
17
     * Creates a QB instance for root objects
18
     */
19 5
    public function _root_objects_qb()
20
    {
21 5
        $schema_type = $this->mgdschema_class;
22
23 5
        $qb = $this->_get_type_qb($schema_type, false);
24 5
        if (!$qb) {
25
            debug_add("Could not get QB for type '{$schema_type}'", MIDCOM_LOG_ERROR);
26
            return false;
27
        }
28
29
        // Only get get top level objects
30 5
        if ($upfield = midgard_object_class::get_property_up($schema_type)) {
31 5
            $qb->add_constraint($upfield, '=', 0);
32
        }
33 5
        return $qb;
34
    }
35
36
    /**
37
     * Get rendered path for object
38
     *
39
     * @param midgard\portable\api\mgdobject $object The object to get path for
40
     * @param string $separator the string used to separate path components
41
     */
42 1
    public static function resolve_path($object, string $separator = ' &gt; ') : string
43
    {
44 1
        $parts = self::resolve_path_parts($object);
45 1
        return implode($separator, array_column($parts, 'label'));
46
    }
47
48
    /**
49
     * Get path components for object
50
     *
51
     * @param midgard\portable\api\mgdobject $object The object to get path for
52
     */
53 3
    public static function resolve_path_parts($object) : array
54
    {
55 3
        static $cache = [];
56 3
        if (isset($cache[$object->guid])) {
57 1
            return $cache[$object->guid];
58
        }
59
60 2
        $ret = [];
61 2
        $ret[] = [
62 2
            'object' => $object,
63 2
            'label' => parent::get($object)->get_object_label($object),
64
        ];
65
66 2
        $parent = $object->get_parent();
67 2
        while (is_object($parent)) {
68 1
            $ret[] = [
69 1
                'object' => $parent,
70 1
                'label' => parent::get($parent)->get_object_label($parent),
71
            ];
72 1
            $parent = $parent->get_parent();
73
        }
74
75 2
        $cache[$object->guid] = array_reverse($ret);
76 2
        return $cache[$object->guid];
77
    }
78
79 21
    private static function _check_permissions(bool $deleted) : bool
80
    {
81
        // PONDER: Check for some generic user privilege instead  ??
82 21
        if (   $deleted
83 21
            && !midcom_connection::is_admin()
84 21
            && !midcom::get()->auth->is_component_sudo()) {
85
            debug_add('Non-admins are not allowed to list deleted objects', MIDCOM_LOG_ERROR);
86
            return false;
87
        }
88 21
        return true;
89
    }
90
91
    /**
92
     * Get direct children of given object
93
     *
94
     * @param midgard\portable\api\mgdobject $object object to get children for
95
     * @param boolean $deleted whether to get (only) deleted or not-deleted objects
96
     * @return array multidimensional array (keyed by classname) of objects
97
     */
98 21
    public static function get_child_objects(object $object, bool $deleted = false) : array
99
    {
100 21
        if (!self::_check_permissions($deleted)) {
101
            return [];
102
        }
103 21
        $resolver = new self($object);
104
105 21
        $child_objects = [];
106 21
        foreach ($resolver->get_child_classes() as $schema_type) {
107 21
            $qb = $resolver->_child_objects_type_qb($schema_type, $object, $deleted);
108 21
            if (!$qb) {
109
                debug_add('Could not get QB instance', MIDCOM_LOG_ERROR);
110
                continue;
111
            }
112
113
            // Sort by title and name if available
114 21
            self::add_schema_sorts_to_qb($qb, $schema_type);
115
116 21
            if ($type_children = $qb->execute()) {
117 1
                $child_objects[$schema_type] = $type_children;
118
            }
119
        }
120 21
        return $child_objects;
121
    }
122
123 106
    private function _get_type_qb(string $schema_type, bool $deleted)
124
    {
125 106
        if (empty($schema_type)) {
126
            debug_add('Passed schema_type argument is empty, this is fatal', MIDCOM_LOG_ERROR);
127
            return false;
128
        }
129 106
        if ($deleted) {
130
            $qb = new midgard_query_builder($schema_type);
131
            $qb->include_deleted();
132
            $qb->add_constraint('metadata.deleted', '<>', 0);
133
            return $qb;
134
        }
135
        // Figure correct MidCOM DBA class to use and get midcom QB
136 106
        $midcom_dba_classname = midcom::get()->dbclassloader->get_midcom_class_name_for_mgdschema_object($schema_type);
137 106
        if (empty($midcom_dba_classname)) {
138
            debug_add("MidCOM DBA does not know how to handle {$schema_type}", MIDCOM_LOG_ERROR);
139
            return false;
140
        }
141
142 106
        return $midcom_dba_classname::new_query_builder();
143
    }
144
145
    /**
146
     * Figure out if $schema_type can be a child of $target_class
147
     */
148 104
    private function get_link_field(string $schema_type, string $target_class) : ?array
149
    {
150 104
        static $cache = [];
151 104
        $cache_key = $schema_type . '-' . $target_class;
152 104
        if (!array_key_exists($cache_key, $cache)) {
153 6
            $cache[$cache_key] = null;
154 6
            $ref = new midgard_reflection_property($schema_type);
155
156 6
            $candidates = array_filter([
157 6
                'up' => midgard_object_class::get_property_up($schema_type),
158 6
                'parent' => midgard_object_class::get_property_parent($schema_type)
159
            ]);
160
161 6
            foreach ($candidates as $type => $field) {
162
                $info = [
163 6
                    'type' => $type,
164 6
                    'name' => $field,
165 6
                    'target' => $ref->get_link_target($field),
166 6
                    'upfield' => $candidates['up'] ?? null
167
                ];
168
169 6
                if ($linked_class = $ref->get_link_name($field)) {
170 5
                    if (!self::is_same_class($linked_class, $target_class)) {
171
                        // This link points elsewhere
172 5
                        continue;
173
                    }
174 6
                } elseif ($ref->get_midgard_type($field) === MGD_TYPE_GUID && empty($info['target'])) {
175
                    // Guid link without class specification, valid for all classes
176 6
                    $info['target'] = 'guid';
177
                }
178 6
                $cache[$cache_key] = $info;
179 6
                break;
180
            }
181
        }
182 104
        return $cache[$cache_key];
183
    }
184
185
    /**
186
     * Creates a QB instance for get_child_objects
187
     */
188 104
    public function _child_objects_type_qb(string $schema_type, object $for_object, bool $deleted)
189
    {
190 104
        $qb = $this->_get_type_qb($schema_type, $deleted);
191 104
        if (!$qb) {
192
            debug_add("Could not get QB for type '{$schema_type}'", MIDCOM_LOG_ERROR);
193
            return false;
194
        }
195
196 104
        if ($info = $this->get_link_field($schema_type, get_class($for_object))) {
197 104
            $qb->add_constraint($info['name'], '=', $for_object->{$info['target']});
198
            // we only return direct children (otherwise they would turn up twice in recursive queries)
199 104
            if ($info['type'] == 'parent' && $info['upfield']) {
200 95
                $qb->add_constraint($info['upfield'], '=', 0);
201
            }
202 104
            return $qb;
203
        }
204
205
        debug_add("Class '{$schema_type}' has no valid link properties pointing to class '" . get_class($for_object) . "', this should not happen here", MIDCOM_LOG_ERROR);
206
        return false;
207
    }
208
209
    /**
210
     * Get the parent class of the class this reflector was instantiated for
211
     */
212 2
    public function get_parent_class() : ?string
213
    {
214 2
        $parent_property = midgard_object_class::get_property_parent($this->mgdschema_class);
215 2
        if (!$parent_property) {
216 2
            return null;
217
        }
218
        $ref = new midgard_reflection_property($this->mgdschema_class);
219
        return $ref->get_link_name($parent_property);
220
    }
221
222
    /**
223
     * Get the child classes of the class this reflector was instantiated for
224
     */
225 105
    public function get_child_classes() : array
226
    {
227 105
        static $cache = [];
228 105
        if (!isset($cache[$this->mgdschema_class])) {
229 4
            $cache[$this->mgdschema_class] = [];
230
231 4
            $types = array_diff(midcom_connection::get_schema_types(), $this->_config->get_array('child_class_exceptions_neverchild'));
232 4
            foreach ($types as $schema_type) {
233 4
                if ($this->get_link_field($schema_type, $this->mgdschema_class)) {
234 4
                    $cache[$this->mgdschema_class][] = $schema_type;
235
                }
236
            }
237
238
            //make sure children of the same type come out on top
239 4
            if ($key = array_search($this->mgdschema_class, $cache[$this->mgdschema_class])) {
240 2
                unset($cache[$this->mgdschema_class][$key]);
241 2
                array_unshift($cache[$this->mgdschema_class], $this->mgdschema_class);
242
            }
243
        }
244 105
        return $cache[$this->mgdschema_class];
245
    }
246
247
    /**
248
     * Get an array of "root level" classes
249
     */
250 12
    public static function get_root_classes() : array
251
    {
252 12
        static $root_classes = false;
253 12
        if (empty($root_classes)) {
254
            $root_classes = self::_resolve_root_classes();
255
        }
256 12
        return $root_classes;
257
    }
258
259
    /**
260
     * Resolves the "root level" DBA classes, used by get_root_classes()
261
     */
262
    private static function _resolve_root_classes() : array
263
    {
264
        $root_exceptions_notroot = midcom_baseclasses_components_configuration::get('midcom.helper.reflector', 'config')->get_array('root_class_exceptions_notroot');
265
        $root_classes = [];
266
        $types = array_diff(midcom_connection::get_schema_types(), $root_exceptions_notroot);
267
        foreach ($types as $schema_type) {
268
            // Class extensions mapping
269
            $schema_type = self::class_rewrite($schema_type);
270
271
            // Make sure we only add classes once
272
            if (in_array($schema_type, $root_classes)) {
273
                // Already listed
274
                continue;
275
            }
276
277
            if (midgard_object_class::get_property_parent($schema_type)) {
278
                // type has parent set, thus cannot be root type
279
                continue;
280
            }
281
282
            if (!midcom::get()->dbclassloader->get_midcom_class_name_for_mgdschema_object($schema_type)) {
283
                // Not a MidCOM DBA object, skip
284
                continue;
285
            }
286
287
            $root_classes[] = $schema_type;
288
        }
289
290
        usort($root_classes, 'strnatcmp');
291
        return $root_classes;
292
    }
293
294
    /**
295
     * Add default ("title" and "name") sorts to a QB instance
296
     *
297
     * @param midgard_query_builder $qb QB instance
298
     */
299 21
    public static function add_schema_sorts_to_qb($qb, string $schema_type)
300
    {
301
        // Sort by "title" and "name" if available
302 21
        $dummy = new $schema_type();
303 21
        if ($title_property = self::get_title_property($dummy)) {
304 21
            $qb->add_order($title_property);
305
        }
306 21
        if ($name_property = self::get_name_property($dummy)) {
307 16
            $qb->add_order($name_property);
308
        }
309 21
    }
310
311
    /**
312
     * List object children
313
     */
314
    public static function get_tree(midcom_core_dbaobject $parent) : array
315
    {
316
        $tree = [];
317
318
        foreach (self::get_child_objects($parent) as $class => $objects) {
319
            $reflector = parent::get($class);
320
321
            foreach ($objects as $object) {
322
                $leaf = [
323
                    'title' => $reflector->get_object_label($object),
324
                    'icon' => $reflector->get_object_icon($object),
325
                    'class' => $class
326
                ];
327
                if ($grandchildren = self::get_tree($object)) {
328
                    $leaf['children'] = $grandchildren;
329
                }
330
                $tree[] = $leaf;
331
            }
332
        }
333
        return $tree;
334
    }
335
}
336