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.