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