Passed
Push — master ( 598445...fef947 )
by Andreas
11:12
created

midcom_helper__dbfactory   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 292
Duplicated Lines 0 %

Test Coverage

Coverage 91.2%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 114
dl 0
loc 292
ccs 114
cts 125
cp 0.912
rs 8.5599
c 1
b 0
f 0
wmc 48

16 Methods

Rating   Name   Duplication   Size   Complexity  
A new_collector() 0 3 1
A __construct() 0 5 1
A get_object_by_guid() 0 14 4
A convert_midgard_to_midcom() 0 4 1
A get_parent_data() 0 7 2
A get_parent_data_cached() 0 20 5
A get_parent_data_uncached() 0 13 4
A get_data_from_guid() 0 11 2
A get_parent_data_uncached_static() 0 22 5
A get_parent() 0 15 4
A new_query_builder() 0 3 1
A _load_guid() 0 16 4
A _get_parent_candidates() 0 14 4
A is_a() 0 15 5
A get_cached() 0 17 3
A add_candidate() 0 15 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
/**
3
 * @package midcom.helper
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
use midgard\portable\api\error\exception as mgd_exception;
10
use midgard\portable\api\mgdobject;
11
use midgard\portable\storage\connection;
12
13
/**
14
 * This class contains various factory methods to retrieve objects from the database.
15
 * The only instance of this class you should ever use is available through
16
 * midcom::get()->dbfactory.
17
 *
18
 * @package midcom.helper
19
 */
20
class midcom_helper__dbfactory
21
{
22
    /**
23
     * ID => GUID cache for parents
24
     */
25
    private array $_parent_mapping = [];
26
27
    /**
28
     * Cache for possible parent configurations per mgdschema class
29
     */
30
    private array $_parent_candidates = [];
31
32
    private string $person_class;
33
34
    private midcom_services_cache_module_memcache $memcache;
35
36
    private midcom_services_dbclassloader $dbclassloader;
37
38
    public function __construct(string $person_class, midcom_services_dbclassloader $dbclassloader, midcom_services_cache_module_memcache $memcache)
39
    {
40
        $this->person_class = $person_class;
41
        $this->dbclassloader = $dbclassloader;
42
        $this->memcache = $memcache;
43
    }
44
45
    /**
46
     * This is a replacement for the original midgard_object_class::get_object_by_guid method, which takes
47
     * the MidCOM DBA system into account.
48
     */
49 154
    public function get_object_by_guid(string $guid) : midcom_core_dbaobject
50
    {
51
        try {
52 154
            $tmp = midgard_object_class::get_object_by_guid($guid);
53
        } catch (mgd_exception $e) {
54
            debug_add('Loading object by GUID ' . $guid . ' failed, reason: ' . $e->getMessage(), MIDCOM_LOG_INFO);
55
56
            throw new midcom_error_midgard($e, $guid);
57
        }
58 154
        if (   get_class($tmp) == 'midgard_person'
59 154
            && $this->person_class != 'midgard_person') {
60 122
            $tmp = new $this->person_class($guid);
61
        }
62 154
        return $this->convert_midgard_to_midcom($tmp);
63
    }
64
65
    /**
66
     * Retrieve a reference to an object, uses in-request caching
67
     *
68
     * @param string $classname Which DBA are we dealing with
69
     */
70 290
    public function get_cached(string $classname, string|int $src) : midcom_core_dbaobject
71
    {
72 290
        static $cache = [];
73
74 290
        if (empty($src)) {
75 11
            throw new midcom_error('invalid source identifier');
76
        }
77
78 286
        $cache[$classname] ??= [];
79
80 286
        if (isset($cache[$classname][$src])) {
81 264
            return $cache[$classname][$src];
82
        }
83 172
        $object = new $classname($src);
84 160
        $cache[$classname][$object->guid] = $object;
85 160
        $cache[$classname][$object->id] =& $cache[$classname][$object->guid];
86 160
        return $cache[$classname][$object->guid];
87
    }
88
89
    /**
90
     * This function will determine the correct type of midgard_collector that
91
     * has to be created.
92
     *
93
     * @param mixed $value Value match for the collector instance
94
     * @see midcom_core_collector
95
     */
96 137
    public function new_collector(string $classname, ?string $domain, $value) : midcom_core_collector
97
    {
98 137
        return new midcom_core_collector($classname, $domain, $value);
99
    }
100
101
    /**
102
     * This function will determine the correct type of midgard_query_builder that
103
     * has to be created.
104
     */
105 484
    public function new_query_builder(string $classname) : midcom_core_querybuilder
106
    {
107 484
        return new midcom_core_querybuilder($classname);
108
    }
109
110
    /**
111
     * Convert MgdSchema object into a MidCOM DBA object.
112
     */
113 156
    public function convert_midgard_to_midcom(mgdobject $object) : midcom_core_dbaobject
114
    {
115 156
        $classname = $this->dbclassloader->get_midcom_class_name_for_mgdschema_object($object);
116 156
        return new $classname($object);
117
    }
118
119
    /**
120
     * This is a helper for emulating is_a() functionality with MidCOM DBA objects that are decorators.
121
     * This method can be used to check whether an object is of a MidCOM DBA or MgdSchema type
122
     *
123
     * @param mixed $object MgdSchema or MidCOM DBA object
124
     */
125 66
    public function is_a($object, string $class, bool $allow_string = false) : bool
126
    {
127 66
        if (is_a($object, $class, $allow_string)) {
128
            // Direct match
129 6
            return true;
130
        }
131
132 65
        if (   isset($object->__object)
133 65
            && is_object($object->__object)
134 65
            && $object->__object instanceof $class) {
135
            // Decorator whose MgdSchema object matches
136 15
            return true;
137
        }
138
139 62
        return false;
140
    }
141
142
    /**
143
     * Returns the parent object. Tries to utilize the Memcache
144
     * data, loading the actual information only if it is not cached.
145
     *
146
     * @see get_parent_data()
147
     * @todo rethink this, IMO we should trust midgard core's get_parent and then just do the object conversion if necessary since this can return stale objects and other nastiness
148
     */
149 336
    public function get_parent(midcom_core_dbaobject $object) : ?midcom_core_dbaobject
150
    {
151 336
        [$classname, $parent_guid] = $this->get_parent_data_cached($object->guid, function() use ($object) {
152 327
            return $this->get_parent_data_uncached($object);
153 336
        });
154
155 336
        if (   empty($parent_guid)
156 336
            || $parent_guid === $object->guid) {
157 323
            return null;
158
        }
159
160
        try {
161 198
            return $this->get_cached($classname, $parent_guid);
162
        } catch (midcom_error $e) {
163
            return null;
164
        }
165
    }
166
167
    /**
168
     * Determines the parent GUID for an existing GUID according to the MidCOM content tree rules.
169
     *
170
     * It tries to look up the GUID in the memory cache, only if this fails, the regular
171
     * content getters are invoked.
172
     *
173
     * @return array The parent GUID and class (value might be null, if this is a top level object).
174
     */
175 91
    public function get_parent_data(string $guid, string $class) : array
176
    {
177 91
        if (!mgd_is_guid($guid)) {
178
            throw new midcom_error('Tried to resolve an invalid GUID.');
179
        }
180 91
        return $this->get_parent_data_cached($guid, function() use ($guid, $class) {
181 42
            return $this->get_parent_data_uncached_static($guid, $class);
182 91
        });
183
    }
184
185 352
    private function get_parent_data_cached(string $guid, callable $callback) : array
186
    {
187 352
        if (mgd_is_guid($guid)) {
188 350
            $parent_data = $this->memcache->lookup_parent_data($guid);
189
        }
190
191 352
        if (empty($parent_data)) {
192
            // No cache hit, retrieve guid and update the cache
193 331
            $parent_data = $callback();
194
195 331
            if (!empty($parent_data[0])) {
196 197
                $parent_data[0] = $this->dbclassloader->get_midcom_class_name_for_mgdschema_object($parent_data[0]);
197
            }
198
199 331
            if (mgd_is_guid($guid)) {
200 329
                $this->memcache->update_parent_data($guid, $parent_data);
201
            }
202
        }
203
204 352
        return $parent_data;
205
    }
206
207 327
    private function get_parent_data_uncached(midcom_core_dbaobject $object) : array
208
    {
209 327
        $candidates = $this->_get_parent_candidates($object->__mgdschema_class_name__);
210 327
        foreach ($candidates as $data) {
211 242
            if ($data['target_property'] === 'guid') {
212 33
                return $this->get_data_from_guid($object->{$data['source_property']});
213
            }
214 226
            $parent_guid = $this->_load_guid($data['target_class'], $data['target_property'], $object->{$data['source_property']});
215 226
            if ($parent_guid) {
216 167
                return [$data['target_class'], $parent_guid];
217
            }
218
        }
219 298
        return ['', null];
220
    }
221
222
    /**
223
     * Get the GUID of the object's parent. This is done by reading up or parent
224
     * property values, which will give us the parent's ID
225
     */
226 42
    private function get_parent_data_uncached_static(string $object_guid, string $class_name) : array
227
    {
228 42
        $class_name = $this->dbclassloader->get_mgdschema_class_name_for_midcom_class($class_name);
229 42
        $candidates = $this->_get_parent_candidates($class_name);
230
231 42
        foreach ($candidates as $data) {
232 33
            $mc = new midgard_collector($class_name, 'guid', $object_guid);
233 33
            $mc->set_key_property($data['source_property']);
234 33
            $mc->execute();
235
236 33
            if ($link_values = $mc->list_keys()) {
237 33
                if ($data['target_property'] === 'guid') {
238 4
                    return $this->get_data_from_guid(key($link_values));
239
                }
240
241 30
                $parent_guid = $this->_load_guid($data['target_class'], $data['target_property'], key($link_values));
242 30
                if ($parent_guid) {
243 8
                    return [$data['target_class'], $parent_guid];
244
                }
245
            }
246
        }
247 36
        return ['', null];
248
    }
249
250 34
    private function get_data_from_guid(string $guid) : array
251
    {
252 34
        if (!mgd_is_guid($guid)) {
253 1
            return ['', null];
254
        }
255 34
        $class_name = connection::get_em()
256 34
            ->createQuery('SELECT r.typename from midgard_repligard r WHERE r.guid = ?1')
257 34
            ->setParameter(1, $guid)
258 34
            ->getSingleScalarResult();
259
260 34
        return [$class_name, $guid];
261
    }
262
263 241
    private function _load_guid(string $target_class, string $target_property, $link_value) : ?string
264
    {
265 241
        if (empty($link_value)) {
266 225
            return null;
267
        }
268 169
        if (!array_key_exists($target_class, $this->_parent_mapping)) {
269 10
            $this->_parent_mapping[$target_class] = [];
270
        }
271 169
        if (!array_key_exists($link_value, $this->_parent_mapping[$target_class])) {
272 97
            $mc = new midgard_collector($target_class, $target_property, $link_value);
273 97
            $mc->set_key_property('guid');
274 97
            $mc->execute();
275 97
            $this->_parent_mapping[$target_class][$link_value] = key($mc->list_keys());
276
        }
277
278 169
        return $this->_parent_mapping[$target_class][$link_value];
279
    }
280
281 331
    private function _get_parent_candidates(string $classname) : array
282
    {
283 331
        if (!isset($this->_parent_candidates[$classname])) {
284 23
            $this->_parent_candidates[$classname] = [];
285
286 23
            if ($up = midgard_object_class::get_property_up($classname)) {
287 7
                $this->add_candidate($classname, $up);
288
            }
289
290 23
            if ($parent = midgard_object_class::get_property_parent($classname)) {
291 17
                $this->add_candidate($classname, $parent);
292
            }
293
        }
294 331
        return $this->_parent_candidates[$classname];
295
    }
296
297 18
    private function add_candidate(string $classname, string $property)
298
    {
299 18
        $mrp = new midgard_reflection_property($classname);
300
301 18
        $data = [
302 18
            'source_property' => $property,
303 18
            'target_property' => $mrp->get_link_target($property) ?? 'guid',
304 18
            'target_class' => $mrp->get_link_name($property)
305 18
        ];
306
307 18
        if ($data['target_class'] == 'midgard_person') {
308
            $data['target_class'] = $this->person_class;
309
        }
310
311 18
        $this->_parent_candidates[$classname][] = $data;
312
    }
313
}
314