Passed
Push — master ( b56598...c69ded )
by Andreas
19:22
created

convert_midgard_to_midcom()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
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
     * @param mixed $src GUID of object (ids work but are discouraged)
70
     */
71 290
    public function get_cached(string $classname, $src) : midcom_core_dbaobject
72
    {
73 290
        static $cache = [];
74
75 290
        if (empty($src)) {
76 12
            throw new midcom_error('invalid source identifier');
77
        }
78
79 285
        if (!isset($cache[$classname])) {
80 16
            $cache[$classname] = [];
81
        }
82
83 285
        if (isset($cache[$classname][$src])) {
84 264
            return $cache[$classname][$src];
85
        }
86 169
        $object = new $classname($src);
87 158
        $cache[$classname][$object->guid] = $object;
88 158
        $cache[$classname][$object->id] =& $cache[$classname][$object->guid];
89 158
        return $cache[$classname][$object->guid];
90
    }
91
92
    /**
93
     * This function will determine the correct type of midgard_collector that
94
     * has to be created.
95
     *
96
     * @param mixed $value Value match for the collector instance
97
     * @see midcom_core_collector
98
     */
99 137
    public function new_collector(string $classname, ?string $domain, $value) : midcom_core_collector
100
    {
101 137
        return new midcom_core_collector($classname, $domain, $value);
102
    }
103
104
    /**
105
     * This function will determine the correct type of midgard_query_builder that
106
     * has to be created.
107
     */
108 483
    public function new_query_builder(string $classname) : midcom_core_querybuilder
109
    {
110 483
        return new midcom_core_querybuilder($classname);
111
    }
112
113
    /**
114
     * Convert MgdSchema object into a MidCOM DBA object.
115
     */
116 156
    public function convert_midgard_to_midcom(mgdobject $object) : midcom_core_dbaobject
117
    {
118 156
        $classname = $this->dbclassloader->get_midcom_class_name_for_mgdschema_object($object);
119 156
        return new $classname($object);
120
    }
121
122
    /**
123
     * This is a helper for emulating is_a() functionality with MidCOM DBA objects that are decorators.
124
     * This method can be used to check whether an object is of a MidCOM DBA or MgdSchema type
125
     *
126
     * @param mixed $object MgdSchema or MidCOM DBA object
127
     */
128 72
    public function is_a($object, string $class, bool $allow_string = false) : bool
129
    {
130 72
        if (is_a($object, $class, $allow_string)) {
131
            // Direct match
132 6
            return true;
133
        }
134
135 71
        if (   isset($object->__object)
136 71
            && is_object($object->__object)
137 71
            && $object->__object instanceof $class) {
138
            // Decorator whose MgdSchema object matches
139 21
            return true;
140
        }
141
142 62
        return false;
143
    }
144
145
    /**
146
     * Returns the parent object. Tries to utilize the Memcache
147
     * data, loading the actual information only if it is not cached.
148
     *
149
     * @see get_parent_data()
150
     * @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
151
     */
152 335
    public function get_parent(midcom_core_dbaobject $object) : ?midcom_core_dbaobject
153
    {
154 335
        [$classname, $parent_guid] = $this->get_parent_data_cached($object->guid, function() use ($object) {
155 326
            return $this->get_parent_data_uncached($object);
156 335
        });
157
158 335
        if (   empty($parent_guid)
159 335
            || $parent_guid === $object->guid) {
160 322
            return null;
161
        }
162
163
        try {
164 198
            return $this->get_cached($classname, $parent_guid);
165
        } catch (midcom_error $e) {
166
            return null;
167
        }
168
    }
169
170
    /**
171
     * Determines the parent GUID for an existing GUID according to the MidCOM content tree rules.
172
     *
173
     * It tries to look up the GUID in the memory cache, only if this fails, the regular
174
     * content getters are invoked.
175
     *
176
     * @return array The parent GUID and class (value might be null, if this is a top level object).
177
     */
178 91
    public function get_parent_data(string $guid, string $class) : array
179
    {
180 91
        if (!mgd_is_guid($guid)) {
181
            throw new midcom_error('Tried to resolve an invalid GUID.');
182
        }
183 91
        return $this->get_parent_data_cached($guid, function() use ($guid, $class) {
184 42
            return $this->get_parent_data_uncached_static($guid, $class);
185 91
        });
186
    }
187
188 351
    private function get_parent_data_cached(string $guid, callable $callback) : array
189
    {
190 351
        if (mgd_is_guid($guid)) {
191 349
            $parent_data = $this->memcache->lookup_parent_data($guid);
192
        }
193
194 351
        if (empty($parent_data)) {
195
            // No cache hit, retrieve guid and update the cache
196 330
            $parent_data = $callback();
197
198 330
            if (!empty($parent_data[0])) {
199 197
                $parent_data[0] = $this->dbclassloader->get_midcom_class_name_for_mgdschema_object($parent_data[0]);
200
            }
201
202 330
            if (mgd_is_guid($guid)) {
203 328
                $this->memcache->update_parent_data($guid, $parent_data);
204
            }
205
        }
206
207 351
        return $parent_data;
208
    }
209
210 326
    private function get_parent_data_uncached(midcom_core_dbaobject $object) : array
211
    {
212 326
        $candidates = $this->_get_parent_candidates($object->__mgdschema_class_name__);
213 326
        foreach ($candidates as $data) {
214 241
            if ($data['target_property'] === 'guid') {
215 33
                return $this->get_data_from_guid($object->{$data['source_property']});
216
            }
217 225
            $parent_guid = $this->_load_guid($data['target_class'], $data['target_property'], $object->{$data['source_property']});
218 225
            if ($parent_guid) {
219 167
                return [$data['target_class'], $parent_guid];
220
            }
221
        }
222 297
        return ['', null];
223
    }
224
225
    /**
226
     * Get the GUID of the object's parent. This is done by reading up or parent
227
     * property values, which will give us the parent's ID
228
     */
229 42
    private function get_parent_data_uncached_static(string $object_guid, string $class_name) : array
230
    {
231 42
        $class_name = $this->dbclassloader->get_mgdschema_class_name_for_midcom_class($class_name);
232 42
        $candidates = $this->_get_parent_candidates($class_name);
233
234 42
        foreach ($candidates as $data) {
235 33
            $mc = new midgard_collector($class_name, 'guid', $object_guid);
236 33
            $mc->set_key_property($data['source_property']);
237 33
            $mc->execute();
238
239 33
            if ($link_values = $mc->list_keys()) {
240 33
                if ($data['target_property'] === 'guid') {
241 4
                    return $this->get_data_from_guid(key($link_values));
242
                }
243
244 30
                $parent_guid = $this->_load_guid($data['target_class'], $data['target_property'], key($link_values));
245 30
                if ($parent_guid) {
246 8
                    return [$data['target_class'], $parent_guid];
247
                }
248
            }
249
        }
250 36
        return ['', null];
251
    }
252
253 34
    private function get_data_from_guid(string $guid) : array
254
    {
255 34
        if (!mgd_is_guid($guid)) {
256 1
            return ['', null];
257
        }
258 34
        $class_name = connection::get_em()
259 34
            ->createQuery('SELECT r.typename from midgard_repligard r WHERE r.guid = ?1')
260 34
            ->setParameter(1, $guid)
261 34
            ->getSingleScalarResult();
262
263 34
        return [$class_name, $guid];
264
    }
265
266 240
    private function _load_guid(string $target_class, string $target_property, $link_value) : ?string
267
    {
268 240
        if (empty($link_value)) {
269 224
            return null;
270
        }
271 169
        if (!array_key_exists($target_class, $this->_parent_mapping)) {
272 10
            $this->_parent_mapping[$target_class] = [];
273
        }
274 169
        if (!array_key_exists($link_value, $this->_parent_mapping[$target_class])) {
275 95
            $mc = new midgard_collector($target_class, $target_property, $link_value);
276 95
            $mc->set_key_property('guid');
277 95
            $mc->execute();
278 95
            $this->_parent_mapping[$target_class][$link_value] = key($mc->list_keys());
279
        }
280
281 169
        return $this->_parent_mapping[$target_class][$link_value];
282
    }
283
284 330
    private function _get_parent_candidates(string $classname) : array
285
    {
286 330
        if (!isset($this->_parent_candidates[$classname])) {
287 23
            $this->_parent_candidates[$classname] = [];
288
289 23
            if ($up = midgard_object_class::get_property_up($classname)) {
290 7
                $this->add_candidate($classname, $up);
291
            }
292
293 23
            if ($parent = midgard_object_class::get_property_parent($classname)) {
294 17
                $this->add_candidate($classname, $parent);
295
            }
296
        }
297 330
        return $this->_parent_candidates[$classname];
298
    }
299
300 18
    private function add_candidate(string $classname, string $property)
301
    {
302 18
        $mrp = new midgard_reflection_property($classname);
303
304 18
        $data = [
305 18
            'source_property' => $property,
306 18
            'target_property' => $mrp->get_link_target($property) ?? 'guid',
307 18
            'target_class' => $mrp->get_link_name($property)
308 18
        ];
309
310 18
        if ($data['target_class'] == 'midgard_person') {
311
            $data['target_class'] = $this->person_class;
312
        }
313
314 18
        $this->_parent_candidates[$classname][] = $data;
315
    }
316
}
317