|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* @link http://www.newicon.net/neon |
|
4
|
|
|
* @copyright Copyright (c) 2016 Newicon Ltd |
|
5
|
|
|
* @license http://www.newicon.net/neon/license/ |
|
6
|
|
|
*/ |
|
7
|
|
|
|
|
8
|
|
|
namespace neon\daedalus\services\ddsManager; |
|
9
|
|
|
|
|
10
|
|
|
use neon\daedalus\interfaces\IDdsObjectMapManagement; |
|
11
|
|
|
|
|
12
|
|
|
use neon\daedalus\services\ddsManager\DdsCore; |
|
13
|
|
|
use neon\daedalus\services\ddsManager\models\DdsMember; |
|
14
|
|
|
use neon\daedalus\services\ddsManager\models\DdsObject; |
|
15
|
|
|
use neon\daedalus\services\ddsManager\models\DdsLink; |
|
16
|
|
|
use neon\core\helpers\Arr; |
|
17
|
|
|
|
|
18
|
|
|
class DdsObjectMapManager extends DdsCore implements IDdsObjectMapManagement |
|
19
|
|
|
{ |
|
20
|
|
|
/** |
|
21
|
|
|
* Caches for results from queries so can protect against over-zealous |
|
22
|
|
|
* requests to the database within one call |
|
23
|
|
|
* @var array |
|
24
|
|
|
*/ |
|
25
|
|
|
private static $_mapResultsCache=[]; |
|
26
|
|
|
/** |
|
27
|
|
|
* The set of map requests |
|
28
|
|
|
* An array with the requests and the maximum number in the chain |
|
29
|
|
|
* @var array |
|
30
|
|
|
*/ |
|
31
|
|
|
private static $_mapRequests = ['keys'=>[], 'chains'=>[], 'depth'=>0]; |
|
32
|
|
|
/** |
|
33
|
|
|
* The set of map results |
|
34
|
|
|
* @var array of ['key'=>[chain] |
|
35
|
|
|
*/ |
|
36
|
|
|
private static $_mapResults = []; |
|
37
|
|
|
/** |
|
38
|
|
|
* The set of multiMapRequests which store individual map requests |
|
39
|
|
|
* @var array of ['requestKey' => ['subRequestKey']] |
|
40
|
|
|
*/ |
|
41
|
|
|
private static $_multiMapRequests = []; |
|
42
|
|
|
|
|
43
|
|
|
private static $_defaultMapChain = '__DEFAULT_MAP_CHAIN__'; |
|
44
|
|
|
|
|
45
|
|
|
/** |
|
46
|
|
|
* @inheritdoc |
|
47
|
|
|
*/ |
|
48
|
4 |
|
public function getObjectMap($classType, $filters=[], $fields=[], $start=0, $length=1000, $includeDeleted=false) |
|
49
|
|
|
{ |
|
50
|
4 |
|
if (!$classType) |
|
51
|
|
|
throw new \InvalidArgumentException("Invalid classType '$classType' in getObjectMap. Correct calling code"); |
|
52
|
|
|
|
|
53
|
4 |
|
$cacheKey = md5(serialize(func_get_args())); |
|
54
|
4 |
|
if (empty(self::$_mapResultsCache[$cacheKey])) { |
|
55
|
|
|
try { |
|
56
|
4 |
|
$limit = ['start' => max(0, (integer)$start), 'length' => min(1000, (integer)$length)]; |
|
57
|
4 |
|
$classType = $this->canonicaliseRef($classType); |
|
58
|
4 |
|
$fieldsClause = ''; |
|
59
|
4 |
|
$linkJoins = []; |
|
60
|
4 |
|
if ($fields) { |
|
61
|
4 |
|
$getFields = []; |
|
62
|
4 |
|
$field2link = []; |
|
63
|
4 |
|
$linkedMemberRows = DdsMember::find() |
|
64
|
4 |
|
->select(['member_ref', 'link_class', 'data_type_ref']) |
|
65
|
4 |
|
->where(['class_type'=>$classType, 'data_type_ref' => ['link_uni', 'link_multi'], 'member_ref'=>$fields, 'deleted' => 0]) |
|
66
|
4 |
|
->asArray()->all(); |
|
67
|
4 |
|
$linkedMembers = Arr::index($linkedMemberRows, 'member_ref'); |
|
68
|
4 |
|
if ($linkedMemberRows) { |
|
|
|
|
|
|
69
|
2 |
|
$linkedMemberClasses = DdsMember::find() |
|
70
|
2 |
|
->select(['class_type', 'member_ref']) |
|
71
|
2 |
|
->where(['class_type'=>Arr::pluck($linkedMemberRows, 'link_class'), 'map_field'=>1, 'deleted' => 0]) |
|
72
|
2 |
|
->asArray()->all(); |
|
73
|
2 |
|
$class2Member = Arr::index($linkedMemberClasses, 'class_type'); |
|
74
|
2 |
|
foreach ($linkedMembers as $lm) { |
|
75
|
|
|
// only try to link through to tables that are within daedalus |
|
76
|
2 |
|
if (isset($class2Member[$lm['link_class']])) |
|
77
|
2 |
|
$field2link[$lm['member_ref']] = $class2Member[$lm['link_class']]; |
|
78
|
|
|
} |
|
79
|
|
|
} |
|
80
|
4 |
|
foreach ($fields as $field) { |
|
81
|
4 |
|
if (array_key_exists($field, $field2link)) { |
|
82
|
2 |
|
$fl = $field2link[$field]; |
|
83
|
2 |
|
$table = $this->getTableFromClassType($fl['class_type']); |
|
84
|
2 |
|
$getFields[] = "`$table`.`$fl[member_ref]`"; |
|
85
|
2 |
|
if ($linkedMembers[$field]['data_type_ref'] == 'link_uni') |
|
86
|
2 |
|
$linkJoins[$table] = "LEFT JOIN `$table` ON `t`.`$field`=`$table`.`_uuid`"; |
|
87
|
|
|
else { |
|
88
|
|
|
$linkName = 'link'.$field; |
|
89
|
|
|
$linkJoins[$linkName] = "LEFT JOIN `dds_link` `$linkName` ON (`o`.`_uuid`=`$linkName`.`from_id` AND `$linkName`.`from_member`='$field')"; |
|
90
|
2 |
|
$linkJoins[$table] = "LEFT JOIN `$table` ON `$linkName`.`to_id` =`$table`.`_uuid`"; |
|
91
|
|
|
} |
|
92
|
|
|
} else { |
|
93
|
4 |
|
$getFields[] = "`t`.`$field`"; |
|
94
|
|
|
} |
|
95
|
|
|
} |
|
96
|
4 |
|
$fieldsClause = implode(', ', $getFields); |
|
97
|
|
|
} else { |
|
98
|
2 |
|
$member = $this->getMapMemberForClass($classType); |
|
99
|
2 |
|
if (!$member) |
|
100
|
|
|
throw new \RuntimeException("There is no mappable member field set on class $classType"); |
|
101
|
|
|
// set this as an array for later |
|
102
|
2 |
|
$fields = [$member['member_ref']]; |
|
103
|
2 |
|
$fieldsClause = "`t`.`$member[member_ref]`"; |
|
104
|
|
|
} |
|
105
|
4 |
|
$map = []; |
|
106
|
4 |
|
$notDeleted = $includeDeleted ? '' : 'AND `o`.`_deleted`=0'; |
|
107
|
4 |
|
$table = $this->getTableFromClassType($classType); |
|
108
|
|
|
// set up any additional filtering |
|
109
|
4 |
|
$filterClause = ''; |
|
110
|
4 |
|
$filterValues = []; |
|
111
|
4 |
|
if ($filters) { |
|
112
|
2 |
|
$ddsObjectFields = ['_uuid','_created','_deleted','_updated']; |
|
113
|
|
|
// whitelist filter keys to prevent sql injection |
|
114
|
2 |
|
$members = $this->getClassMembers($classType); |
|
115
|
2 |
|
$filterSubClause = []; |
|
116
|
2 |
|
foreach ($filters as $field=>$value) { |
|
117
|
2 |
|
if ($field == '_map_field_') { |
|
118
|
2 |
|
$mapMember = $this->getMapMemberForClass($classType); |
|
119
|
2 |
|
if ($mapMember && $value) |
|
120
|
2 |
|
$filterSubClause[] = "AND `t`.`$mapMember[member_ref]` LIKE '%$value%'"; |
|
121
|
2 |
|
continue; |
|
122
|
|
|
} |
|
123
|
2 |
|
if (!(isset($members[$field]) || in_array($field, $ddsObjectFields))) |
|
124
|
|
|
continue; |
|
125
|
2 |
|
$keyClause = (in_array($field, $ddsObjectFields)) ? "`o`.`$field`" : "`t`.`$field`"; |
|
126
|
2 |
|
$value = is_array($value) ? $value : [$value]; |
|
127
|
2 |
|
$vcount = 1; |
|
128
|
2 |
|
$inClause = []; |
|
129
|
2 |
|
foreach ($value as $v) { |
|
130
|
2 |
|
$inClause[] = ":$field$vcount"; |
|
131
|
2 |
|
$filterValues[":$field$vcount"] = $v; |
|
132
|
2 |
|
$vcount++; |
|
133
|
|
|
} |
|
134
|
2 |
|
$inClause = implode(',',$inClause); |
|
135
|
2 |
|
if (!empty($members[$field]) && $members[$field]['data_type_ref'] == 'link_multi') { |
|
136
|
|
|
$linkName = "link$field"; |
|
137
|
|
|
if (!isset($linkJoins[$linkName])) |
|
138
|
|
|
$linkJoins[$linkName] = "LEFT JOIN `dds_link` `$linkName` ON (`o`.`_uuid`=`$linkName`.`from_id` AND `$linkName`.`from_member`='$field')"; |
|
139
|
|
|
$filterSubClause[] = "AND `$linkName`.`to_id` IN ($inClause)"; |
|
140
|
|
|
} else { |
|
141
|
2 |
|
$filterSubClause[] = "AND $keyClause IN ($inClause)"; |
|
142
|
|
|
} |
|
143
|
|
|
} |
|
144
|
2 |
|
$filterClause = implode (' ', $filterSubClause); |
|
145
|
|
|
} |
|
146
|
4 |
|
$linkJoinsClause = implode(' ', $linkJoins); |
|
147
|
|
|
|
|
148
|
4 |
|
$query = "SELECT DISTINCT `o`.`_uuid`, $fieldsClause FROM `dds_object` `o` LEFT JOIN `$table` `t` ON `o`.`_uuid`=`t`.`_uuid` " . |
|
149
|
4 |
|
"$linkJoinsClause WHERE `o`.`_class_type` = '$classType' $filterClause $notDeleted ORDER BY $fieldsClause " . |
|
150
|
4 |
|
"LIMIT $limit[start], $limit[length]"; |
|
151
|
4 |
|
$command = neon()->db->createCommand($query); |
|
152
|
4 |
|
if ($filters) |
|
153
|
2 |
|
$command->bindValues($filterValues); |
|
154
|
4 |
|
$rows = $command->queryAll(); |
|
155
|
4 |
|
foreach ($rows as $r) { |
|
156
|
|
|
// extract out the uuid result |
|
157
|
4 |
|
$uuid = $r['_uuid']; |
|
158
|
4 |
|
unset($r['_uuid']); |
|
159
|
|
|
|
|
160
|
|
|
// set the map |
|
161
|
4 |
|
$map[$uuid] = (count($fields)==1) ? current($r) : $r; |
|
162
|
|
|
} |
|
163
|
4 |
|
self::$_mapResultsCache[$cacheKey] = $map; |
|
164
|
|
|
} catch (\Exception $e) { |
|
165
|
|
|
$errorMessage = "Exception during getting a map. This may be caused by empty filters - you cannot filter on null. ". |
|
166
|
|
|
"Your filters were: ".print_r($filters,true).". The error message is ".$e->getMessage(); |
|
|
|
|
|
|
167
|
|
|
\Neon::error($errorMessage, 'DDS'); |
|
168
|
|
|
debug_message($errorMessage); |
|
169
|
|
|
return []; |
|
170
|
|
|
} |
|
171
|
|
|
} |
|
172
|
4 |
|
return self::$_mapResultsCache[$cacheKey]; |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
/** |
|
176
|
|
|
* @inheritdoc |
|
177
|
|
|
*/ |
|
178
|
24 |
|
public function makeMapLookupRequest($objectUuids, $chainMapFields=[], $classType=null) |
|
179
|
|
|
{ |
|
180
|
24 |
|
if (empty($objectUuids)) |
|
181
|
2 |
|
return null; |
|
182
|
|
|
/* See if there are any linked fields that we need to get |
|
183
|
|
|
The implementation here is somewhat crude. Generally speaking we are probably looking |
|
184
|
|
|
at needing to implement graphql or something similar to easily manage all the possible |
|
185
|
|
|
ways in which we might want to handle linked data through a request. For now if we need to |
|
186
|
|
|
link from one table to another, the simplest is to do it as another map chain request |
|
187
|
|
|
and merge them all together at the end when getting results. |
|
188
|
|
|
*/ |
|
189
|
22 |
|
$linkedMembers = []; |
|
190
|
22 |
|
if ($classType && $chainMapFields) { |
|
191
|
|
|
$linkMemberKeys = array_keys($this->getClassMembers($classType, ['link_uni'])); |
|
192
|
|
|
$linkedMembers = array_values(array_intersect($chainMapFields, $linkMemberKeys)); |
|
193
|
|
|
} |
|
194
|
22 |
|
$requestKeys = []; |
|
195
|
|
|
// make the initial request key |
|
196
|
22 |
|
$requestKeys[] = $this->makeMapChainLookupRequest($objectUuids, null, [$chainMapFields]); |
|
197
|
|
|
// now make one for each linked member |
|
198
|
22 |
|
foreach ($linkedMembers as $lm) { |
|
199
|
|
|
$requestKeys[] = $this->makeMapChainLookupRequest($objectUuids, [$lm]); |
|
200
|
|
|
} |
|
201
|
22 |
|
return implode('|',$requestKeys); |
|
202
|
|
|
} |
|
203
|
|
|
|
|
204
|
|
|
/** |
|
205
|
|
|
* @inheritdoc |
|
206
|
|
|
*/ |
|
207
|
24 |
|
public function getMapLookupResults($requestKey) |
|
208
|
|
|
{ |
|
209
|
24 |
|
$requestKeys = explode('|', $requestKey); |
|
210
|
24 |
|
$partials = []; |
|
211
|
24 |
|
foreach ($requestKeys as $rk) { |
|
212
|
24 |
|
$partials[] = $this->getMapChainLookupResults($rk); |
|
213
|
|
|
} |
|
214
|
24 |
|
if (count($requestKeys) > 1) { |
|
215
|
|
|
// skip over the first partial as this is our map |
|
216
|
|
|
// and look through others to fill back in |
|
217
|
|
|
foreach ($partials[0] as $u=>$p) { |
|
218
|
|
|
foreach ($p as $i=>$v) { |
|
219
|
|
|
if ($this->isUuid($v)) { |
|
220
|
|
|
for ($i=1; $i<count($requestKeys); $i++) { |
|
|
|
|
|
|
221
|
|
|
if (array_key_exists($v, $partials[$i][1])) { |
|
222
|
|
|
$partials[0][$u][$i] = $partials[$i][1][$v]; |
|
223
|
|
|
continue; |
|
224
|
|
|
} |
|
225
|
|
|
} |
|
226
|
|
|
} |
|
227
|
|
|
} |
|
228
|
|
|
} |
|
229
|
|
|
} |
|
230
|
24 |
|
return $partials[0]; |
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
|
|
/** |
|
234
|
|
|
* @inheritdoc |
|
235
|
|
|
*/ |
|
236
|
22 |
|
public function makeMapChainLookupRequest($objectUuids, $chainMembers=[], $chainMapFields=[], $inReverse=false) |
|
237
|
|
|
{ |
|
238
|
22 |
|
if (empty($objectUuids)) |
|
239
|
|
|
return null; |
|
240
|
|
|
|
|
241
|
|
|
// create a request key based on the input parameters |
|
242
|
22 |
|
$requestKey = md5(serialize(func_get_args())); |
|
243
|
|
|
|
|
244
|
|
|
// handle this request if we haven't already done so |
|
245
|
|
|
if ( |
|
246
|
22 |
|
!isset(static::$_mapRequests[$requestKey]) |
|
|
|
|
|
|
247
|
22 |
|
&& !isset(static::$_mapResults[$requestKey]) |
|
|
|
|
|
|
248
|
|
|
) { |
|
249
|
22 |
|
$this->addRequest( |
|
250
|
22 |
|
$requestKey, |
|
251
|
|
|
$objectUuids, |
|
252
|
|
|
$chainMembers, |
|
253
|
|
|
$chainMapFields, |
|
254
|
|
|
$inReverse |
|
255
|
|
|
); |
|
256
|
|
|
} |
|
257
|
|
|
|
|
258
|
22 |
|
return $requestKey; |
|
259
|
|
|
} |
|
260
|
|
|
|
|
261
|
|
|
/** |
|
262
|
|
|
* @inheritdoc |
|
263
|
|
|
*/ |
|
264
|
24 |
|
public function commitMapRequests() |
|
265
|
|
|
{ |
|
266
|
24 |
|
$requests = &static::$_mapRequests; |
|
|
|
|
|
|
267
|
|
|
// check we have any requests to make |
|
268
|
24 |
|
if (count($requests['chains']) === 0) |
|
269
|
22 |
|
return; |
|
270
|
|
|
// run through the chain and get the reverse maps for each level of requests |
|
271
|
22 |
|
foreach ($requests['chains'] as $i=>&$chain) { |
|
272
|
|
|
// the requests will be filled in during this call |
|
273
|
22 |
|
$this->getMapLookupByLevel($chain); |
|
274
|
22 |
|
foreach ($chain as $k=>$r) { |
|
275
|
22 |
|
if ($r['next_uuid']) |
|
276
|
8 |
|
$requests['chains'][($i+1)][$k]['uuid'] = $r['next_uuid']; |
|
277
|
|
|
} |
|
278
|
|
|
} |
|
279
|
22 |
|
$this->convertMapRequestResults(); |
|
280
|
22 |
|
$this->resetMapRequests(); |
|
281
|
22 |
|
} |
|
282
|
|
|
|
|
283
|
|
|
/** |
|
284
|
|
|
* @inheritdoc |
|
285
|
|
|
*/ |
|
286
|
24 |
|
public function getMapChainLookupResults($requestKey) |
|
287
|
|
|
{ |
|
288
|
|
|
// first commit any pending requests and then check results |
|
289
|
24 |
|
$this->commitMapRequests(); |
|
290
|
|
|
|
|
291
|
|
|
// temporary while we are dealing with multiple uuid requests as |
|
292
|
|
|
// a set of single map requests |
|
293
|
24 |
|
if (isset(static::$_multiMapRequests[$requestKey])) { |
|
|
|
|
|
|
294
|
|
|
return $this->getMultiMapResults($requestKey); |
|
|
|
|
|
|
295
|
|
|
} |
|
296
|
|
|
|
|
297
|
24 |
|
return (!empty(static::$_mapResults[$requestKey]) |
|
|
|
|
|
|
298
|
22 |
|
? static::$_mapResults[$requestKey] |
|
299
|
24 |
|
: []); |
|
300
|
|
|
} |
|
301
|
|
|
|
|
302
|
|
|
/** |
|
303
|
|
|
* @inheritdoc |
|
304
|
|
|
*/ |
|
305
|
22 |
|
public function clearMapRequests() |
|
306
|
|
|
{ |
|
307
|
22 |
|
$this->resetMapRequests(); |
|
308
|
22 |
|
static::$_mapResults = []; |
|
|
|
|
|
|
309
|
22 |
|
} |
|
310
|
|
|
|
|
311
|
|
|
|
|
312
|
|
|
// ---------------------- |
|
313
|
|
|
// Implementation Methods |
|
314
|
|
|
|
|
315
|
|
|
|
|
316
|
|
|
/** |
|
317
|
|
|
* This makes a query for all of the maps at a particular request level and |
|
318
|
|
|
* pushes the replies back into the requests themselves ready for extraction |
|
319
|
|
|
* later into the results |
|
320
|
|
|
* |
|
321
|
|
|
* @param array &$requests |
|
322
|
|
|
*/ |
|
323
|
22 |
|
private function getMapLookupByLevel(&$requests) |
|
324
|
|
|
{ |
|
325
|
22 |
|
if (empty($requests)) |
|
326
|
|
|
return; |
|
327
|
|
|
|
|
328
|
22 |
|
$endpoints = []; |
|
329
|
22 |
|
$requestsByUuid = []; |
|
330
|
22 |
|
foreach ($requests as $r) { |
|
331
|
22 |
|
$endpoints[] = $r['uuid']; |
|
332
|
22 |
|
$requestsByUuid[$r['uuid']][] = $r; |
|
333
|
|
|
} |
|
334
|
22 |
|
$objectRows = DdsObject::find() |
|
335
|
22 |
|
->select(['_uuid','_class_type']) |
|
336
|
22 |
|
->where(['_uuid'=>$endpoints]) |
|
337
|
22 |
|
->andWhere(['_deleted'=>0]) |
|
338
|
22 |
|
->asArray() |
|
339
|
22 |
|
->all(); |
|
340
|
22 |
|
$maps = []; |
|
341
|
22 |
|
$hackySeparator = '§|§@§|§'; // use a horrible unlikely separator to manage multiple map field requests |
|
342
|
22 |
|
if (!empty($objectRows)) { |
|
343
|
20 |
|
$classTypes = []; // get a list of the required class types |
|
344
|
20 |
|
$objectsByClassMapsAndChain = []; // get all ids for the same class combo |
|
345
|
20 |
|
foreach ($objectRows as $o) { |
|
346
|
|
|
// get the chain field on the object |
|
347
|
20 |
|
foreach ($requestsByUuid[$o['_uuid']] as $r) { |
|
348
|
20 |
|
$objectsByClassMapsAndChain[$o['_class_type']][$r['chain']][$r['mapFields']][$o['_uuid']] = $o['_uuid']; |
|
349
|
|
|
} |
|
350
|
20 |
|
$classTypes[$o['_class_type']] = $o['_class_type']; |
|
351
|
|
|
} |
|
352
|
|
|
|
|
353
|
|
|
// now find all the maps members for the required classes |
|
354
|
20 |
|
$mapFields = DdsMember::find() |
|
355
|
20 |
|
->select(['class_type','member_ref']) |
|
356
|
20 |
|
->where(['class_type'=>$classTypes, 'deleted'=>0, 'map_field'=>1]) |
|
357
|
20 |
|
->asArray() |
|
358
|
20 |
|
->all(); |
|
359
|
20 |
|
$mapsByClass = []; |
|
360
|
20 |
|
foreach ($mapFields as $m) { |
|
361
|
20 |
|
$mapsByClass[$m['class_type']] = $m['member_ref']; |
|
362
|
|
|
} |
|
363
|
|
|
|
|
364
|
|
|
// now find all of the objects for the level up in the chain |
|
365
|
20 |
|
$subQuery = []; |
|
366
|
20 |
|
$chainValues = []; |
|
367
|
20 |
|
$count = 0; |
|
368
|
20 |
|
foreach ($objectsByClassMapsAndChain as $class => $chainMapObjects) { |
|
369
|
20 |
|
foreach ($chainMapObjects as $chain => $mapObjects) { |
|
370
|
20 |
|
foreach ($mapObjects as $mapField => $objectUuids) { |
|
371
|
20 |
|
$objs = "'".implode("','",$objectUuids)."'"; |
|
372
|
|
|
// make sure a map field can be created |
|
373
|
20 |
|
$mapFieldsClause = ''; |
|
374
|
20 |
|
if ($mapField === self::$_defaultMapChain) { |
|
375
|
20 |
|
if (!isset($mapsByClass[$class])) |
|
376
|
|
|
continue; |
|
377
|
20 |
|
$mapFieldsClause = "CAST(`$mapsByClass[$class]` AS CHAR)"; |
|
378
|
|
|
} else { |
|
379
|
10 |
|
$mapFieldsClause = "CONCAT_WS('$hackySeparator'"; |
|
380
|
10 |
|
foreach (explode(',',$mapField) as $m) { |
|
381
|
10 |
|
$mapFieldsClause .= ",CAST(`$m` AS CHAR)"; |
|
382
|
|
|
} |
|
383
|
10 |
|
$mapFieldsClause .= ')'; |
|
384
|
|
|
} |
|
385
|
20 |
|
$nextUuidClause = ($chain == '__FINAL__' ? "null" : "`$chain`").' AS `next_uuid`'; |
|
386
|
20 |
|
$subQuery[] = <<<EOQ |
|
387
|
20 |
|
SELECT `_uuid` as `uuid`, '$mapField' AS `mapFields`, $mapFieldsClause AS `map`, '{$chain}' AS `chain`, $nextUuidClause FROM `ddt_$class` WHERE `_uuid` IN ($objs) |
|
388
|
|
|
EOQ; |
|
389
|
20 |
|
$chainValues[":CHAIN$count"] = $chain; |
|
390
|
20 |
|
$count++; |
|
391
|
|
|
} |
|
392
|
|
|
} |
|
393
|
|
|
} |
|
394
|
20 |
|
$query = implode(' UNION ', $subQuery); |
|
395
|
20 |
|
$command = neon()->db->createCommand($query); |
|
396
|
|
|
|
|
397
|
|
|
// get the maps |
|
398
|
20 |
|
$maps = $command->queryAll(); |
|
399
|
|
|
} |
|
400
|
|
|
|
|
401
|
|
|
// sort the maps by chain and uuid |
|
402
|
22 |
|
$mapsByChainAndUuid = []; |
|
403
|
22 |
|
foreach ($maps as $m) { |
|
404
|
20 |
|
if (strpos($m['map'], $hackySeparator) !== false) { |
|
405
|
10 |
|
$m['map'] = explode($hackySeparator,$m['map']); |
|
406
|
|
|
} |
|
407
|
20 |
|
$mapsByChainAndUuid[$m['chain']][$m['mapFields']][$m['uuid']] = $m; |
|
408
|
|
|
} |
|
409
|
|
|
|
|
410
|
|
|
// now backfill the results |
|
411
|
22 |
|
foreach ($requests as &$r) { |
|
412
|
22 |
|
if (isset($mapsByChainAndUuid[$r['chain']][$r['mapFields']][$r['uuid']])) { |
|
413
|
20 |
|
$map = $mapsByChainAndUuid[$r['chain']][$r['mapFields']][$r['uuid']]; |
|
414
|
20 |
|
$r['map'] = $map['map']; |
|
415
|
20 |
|
$r['next_uuid'] = $map['next_uuid']; |
|
416
|
|
|
} else { |
|
417
|
2 |
|
$r['next_uuid'] = null; |
|
418
|
|
|
} |
|
419
|
|
|
} |
|
420
|
22 |
|
} |
|
421
|
|
|
|
|
422
|
|
|
/** |
|
423
|
|
|
* Add a request to the set of map requests |
|
424
|
|
|
* |
|
425
|
|
|
* Requests are split into a series of requests at each chain level so |
|
426
|
|
|
* that we can make all the requests for one level at the same time. This |
|
427
|
|
|
* way the total number of requests is limited to the maximum number of |
|
428
|
|
|
* levels in the chain rather than a sum of (request[i]*length[i]) across |
|
429
|
|
|
* each request |
|
430
|
|
|
* |
|
431
|
|
|
* @param string $requestKey |
|
432
|
|
|
* @param string|array $objectUuids string for single object and array for multiple |
|
433
|
|
|
* @param null|array $chainMembers null to mean not a chain |
|
434
|
|
|
* @param array $chainMapFields |
|
435
|
|
|
* @param boolean $inReverse |
|
436
|
|
|
*/ |
|
437
|
22 |
|
private function addRequest($requestKey, $objectUuid, $chainMembers, $chainMapFields, $inReverse) |
|
438
|
|
|
{ |
|
439
|
22 |
|
if (empty($objectUuid)) |
|
440
|
|
|
return; |
|
441
|
|
|
|
|
442
|
|
|
// Treat a request for objectUuids as multiple single object map requests |
|
443
|
|
|
// TODO NJ 20190415 - make this work properly with multiple uuids without needing multiple requests |
|
444
|
22 |
|
if (is_array($objectUuid)) { |
|
445
|
20 |
|
foreach ($objectUuid as $uuid) { |
|
446
|
20 |
|
$subRequestKey = $requestKey.$uuid; |
|
447
|
20 |
|
$this->addRequest( |
|
448
|
20 |
|
$subRequestKey, |
|
449
|
20 |
|
$uuid, |
|
450
|
20 |
|
$chainMembers, |
|
451
|
20 |
|
$chainMapFields, |
|
452
|
20 |
|
$inReverse |
|
453
|
|
|
); |
|
454
|
20 |
|
static::$_multiMapRequests[$requestKey][] = $subRequestKey; |
|
|
|
|
|
|
455
|
|
|
} |
|
456
|
20 |
|
return; |
|
457
|
|
|
} |
|
458
|
|
|
|
|
459
|
|
|
// see if this is a chain or not |
|
460
|
22 |
|
$chainable = true; |
|
461
|
22 |
|
if ($chainMembers === null) { |
|
462
|
22 |
|
$chainMembers = []; |
|
463
|
22 |
|
$chainable = false; |
|
464
|
|
|
} |
|
465
|
|
|
|
|
466
|
22 |
|
$requests = &static::$_mapRequests; // to make the code slightly shorter |
|
|
|
|
|
|
467
|
|
|
|
|
468
|
|
|
// check to see if the request has already added |
|
469
|
22 |
|
if (isset($requests['keys'][$requestKey])) |
|
470
|
|
|
return; |
|
471
|
|
|
|
|
472
|
|
|
// calculate how many chains we are going down |
|
473
|
22 |
|
$chainCount = count($chainMembers); |
|
474
|
|
|
// split the chain up into requests per chain level if there are any |
|
475
|
22 |
|
foreach ($chainMembers as $i=>$mc) { |
|
476
|
8 |
|
$mapFieldKey = ($i == 0 ? 0 : $chainMembers[$i-1]); |
|
477
|
8 |
|
$mapFields = !empty($chainMapFields[$mapFieldKey]) ? implode(',',$chainMapFields[$mapFieldKey]) : self::$_defaultMapChain; |
|
478
|
8 |
|
$requests['chains'][$i][$requestKey] = ['uuid'=>($i===0?$objectUuid:null), 'map'=>null, 'mapFields'=>$mapFields, 'chain'=>$mc, 'key'=>$requestKey]; |
|
479
|
|
|
} |
|
480
|
|
|
// add a final chain for the last item |
|
481
|
22 |
|
$mapFieldKey = ($chainCount == 0 ? 0 : $chainMembers[$chainCount-1]); |
|
482
|
22 |
|
$mapFields = !empty($chainMapFields[$mapFieldKey]) ? implode(',',$chainMapFields[$mapFieldKey]) : self::$_defaultMapChain; |
|
483
|
22 |
|
$requests['chains'][$chainCount][$requestKey] = ['uuid'=>($chainCount===0?$objectUuid:null), 'map'=>null, 'mapFields'=>$mapFields, 'chain'=>'__FINAL__', 'key'=>$requestKey]; |
|
484
|
|
|
|
|
485
|
|
|
// and add the requestKey so we don't calculate this again |
|
486
|
22 |
|
$requests['keys'][$requestKey] = [ |
|
487
|
22 |
|
'objectUuid' => $objectUuid, |
|
488
|
22 |
|
'inChains'=>$chainable, |
|
489
|
22 |
|
'inReverse'=>($chainable && $inReverse), |
|
490
|
|
|
]; |
|
491
|
22 |
|
} |
|
492
|
|
|
|
|
493
|
|
|
/** |
|
494
|
|
|
* Reset the map requests so new requests can be added |
|
495
|
|
|
*/ |
|
496
|
22 |
|
private function resetMapRequests() |
|
497
|
|
|
{ |
|
498
|
22 |
|
static::$_mapRequests = ['keys'=>[], 'chains'=>[], 'depth'=>0]; |
|
|
|
|
|
|
499
|
22 |
|
static::$_multiMapRequests = []; |
|
|
|
|
|
|
500
|
22 |
|
} |
|
501
|
|
|
|
|
502
|
|
|
/** |
|
503
|
|
|
* Convert requests results into results expected for mappings |
|
504
|
|
|
*/ |
|
505
|
22 |
|
private function convertMapRequestResults() |
|
506
|
|
|
{ |
|
507
|
22 |
|
$keys = &static::$_mapRequests['keys']; |
|
|
|
|
|
|
508
|
22 |
|
$chains = &static::$_mapRequests['chains']; |
|
509
|
22 |
|
$results = &static::$_mapResults; |
|
|
|
|
|
|
510
|
|
|
|
|
511
|
22 |
|
$tempResults = []; |
|
512
|
22 |
|
if (count($chains)===0) |
|
513
|
|
|
return; |
|
514
|
22 |
|
foreach ($chains as $level => $maps) { |
|
515
|
22 |
|
foreach ($maps as $key=>$map) { |
|
516
|
22 |
|
$tempResults[$key][$level][$map['uuid']] = $map['map']; |
|
517
|
|
|
} |
|
518
|
|
|
} |
|
519
|
|
|
|
|
520
|
|
|
// make any conversions to the results according to the request settings |
|
521
|
22 |
|
foreach ($keys as $k=>$settings) { |
|
522
|
22 |
|
if ($settings['inChains'] == true) { |
|
523
|
10 |
|
$results[$k] = ($settings['inReverse']) ? array_reverse($tempResults[$k]) : $tempResults[$k]; |
|
524
|
|
|
} else { |
|
525
|
|
|
// in this case we just need the lowest level map within the chain |
|
526
|
22 |
|
$results[$k] = $tempResults[$k][0]; |
|
527
|
|
|
} |
|
528
|
|
|
} |
|
529
|
|
|
|
|
530
|
|
|
// sort out any multimaps that need to be converted |
|
531
|
|
|
// TODO NJ 20190415 Remove the need for this once all converted into |
|
532
|
|
|
// initial query |
|
533
|
22 |
|
foreach (static::$_multiMapRequests as $requestKey=>$subRequestKeys) { |
|
|
|
|
|
|
534
|
20 |
|
$multiMapResult = []; |
|
535
|
20 |
|
foreach ($subRequestKeys as $subRequestKey) { |
|
536
|
20 |
|
if (isset($results[$subRequestKey])) { |
|
537
|
20 |
|
$r = $results[$subRequestKey]; |
|
538
|
20 |
|
$settings = $keys[$subRequestKey]; |
|
539
|
20 |
|
if ($settings['inChains']) { |
|
540
|
|
|
// set results as [['uuid'] => chain,...] |
|
541
|
10 |
|
$multiMapResult[$settings['objectUuid']] = $r; |
|
542
|
|
|
} else { |
|
543
|
|
|
// set results as [uuid=>map,...] |
|
544
|
20 |
|
$multiMapResult[key($r)] = current($r); |
|
545
|
|
|
} |
|
546
|
|
|
} |
|
547
|
|
|
} |
|
548
|
|
|
// and store these results onto the total results |
|
549
|
20 |
|
$results[$requestKey] = $multiMapResult; |
|
550
|
|
|
} |
|
551
|
22 |
|
} |
|
552
|
|
|
} |
|
553
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)or! empty(...)instead.