1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* tubee |
7
|
|
|
* |
8
|
|
|
* @copyright Copryright (c) 2017-2019 gyselroth GmbH (https://gyselroth.com) |
9
|
|
|
* @license GPL-3.0 https://opensource.org/licenses/GPL-3.0 |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Tubee\Workflow; |
13
|
|
|
|
14
|
|
|
use MongoDB\BSON\UTCDateTimeInterface; |
15
|
|
|
use Tubee\Collection\CollectionInterface; |
16
|
|
|
use Tubee\DataObject\DataObjectInterface; |
17
|
|
|
use Tubee\DataObject\Exception as DataObjectException; |
18
|
|
|
use Tubee\EndpointObject\EndpointObjectInterface; |
19
|
|
|
use Tubee\Helper; |
20
|
|
|
use Tubee\Workflow; |
21
|
|
|
|
22
|
|
|
class ImportWorkflow extends Workflow |
23
|
|
|
{ |
24
|
|
|
/** |
25
|
|
|
* {@inheritdoc} |
26
|
|
|
*/ |
27
|
|
|
public function cleanup(DataObjectInterface $object, UTCDateTimeInterface $ts, bool $simulate = false): bool |
28
|
|
|
{ |
29
|
|
|
$attributes = $object->toArray(); |
30
|
|
|
if ($this->checkCondition($attributes) === false) { |
31
|
|
|
return false; |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
$attributes = Helper::associativeArrayToPath($attributes); |
35
|
|
|
$map = $this->attribute_map->map($attributes, $ts); |
|
|
|
|
36
|
|
|
$this->logger->info('mapped object attributes [{map}] for cleanup', [ |
37
|
|
|
'category' => get_class($this), |
38
|
|
|
'map' => array_keys($map), |
39
|
|
|
]); |
40
|
|
|
|
41
|
|
|
switch ($this->ensure) { |
42
|
|
|
case WorkflowInterface::ENSURE_ABSENT: |
43
|
|
|
return $this->endpoint->getCollection()->deleteObject($object->getId(), $simulate); |
44
|
|
|
|
45
|
|
|
break; |
|
|
|
|
46
|
|
|
default: |
47
|
|
|
case WorkflowInterface::ENSURE_LAST: |
|
|
|
|
48
|
|
|
$resource = Map::map($this->attribute_map, $map, ['data' => $object->getData()], $ts); |
49
|
|
|
$object->getCollection()->changeObject($object, $resource, $simulate); |
50
|
|
|
$this->importRelations($object, $map, $simulate); |
51
|
|
|
|
52
|
|
|
return true; |
53
|
|
|
|
54
|
|
|
break; |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
return false; |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* {@inheritdoc} |
62
|
|
|
*/ |
63
|
|
|
public function import(CollectionInterface $collection, EndpointObjectInterface $object, UTCDateTimeInterface $ts, bool $simulate = false): bool |
64
|
|
|
{ |
65
|
|
|
$object = $object->getData(); |
66
|
|
|
if ($this->checkCondition($object) === false) { |
67
|
|
|
return false; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
$map = $this->attribute_map->map($object, $ts); |
|
|
|
|
71
|
|
|
$this->logger->info('mapped object attributes [{map}] for import', [ |
72
|
|
|
'category' => get_class($this), |
73
|
|
|
'map' => array_keys($map), |
74
|
|
|
]); |
75
|
|
|
|
76
|
|
|
$exists = $this->getImportObject($collection, $map, $object, $ts); |
77
|
|
|
|
78
|
|
|
if ($exists === null) { |
79
|
|
|
$this->logger->info('found no existing data object for given import attributes', [ |
80
|
|
|
'category' => get_class($this), |
81
|
|
|
]); |
82
|
|
|
} else { |
83
|
|
|
$this->logger->info('identified existing data object ['.$exists->getId().'] for import', [ |
84
|
|
|
'category' => get_class($this), |
85
|
|
|
]); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
$ensure = $this->ensure; |
89
|
|
|
if ($exists !== null && $this->ensure === WorkflowInterface::ENSURE_EXISTS) { |
90
|
|
|
return false; |
91
|
|
|
} |
92
|
|
|
if ($exists === null && $this->ensure === WorkflowInterface::ENSURE_LAST) { |
93
|
|
|
$ensure = WorkflowInterface::ENSURE_EXISTS; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
switch ($ensure) { |
97
|
|
|
case WorkflowInterface::ENSURE_ABSENT: |
98
|
|
|
$collection->deleteObject($exists->getId(), $simulate); |
99
|
|
|
|
100
|
|
|
return true; |
101
|
|
|
|
102
|
|
|
break; |
|
|
|
|
103
|
|
|
case WorkflowInterface::ENSURE_EXISTS: |
104
|
|
|
$endpoints = [ |
105
|
|
|
$this->endpoint->getName() => [ |
106
|
|
|
'last_sync' => $ts, |
107
|
|
|
'garbage' => false, |
108
|
|
|
], |
109
|
|
|
]; |
110
|
|
|
|
111
|
|
|
$id = $collection->createObject(Helper::pathArrayToAssociative($map), $simulate, $endpoints); |
112
|
|
|
$this->importRelations($collection->getObject(['_id' => $id]), $map, $simulate, $endpoints); |
113
|
|
|
|
114
|
|
|
return true; |
115
|
|
|
|
116
|
|
|
break; |
|
|
|
|
117
|
|
|
default: |
118
|
|
|
case WorkflowInterface::ENSURE_LAST: |
|
|
|
|
119
|
|
|
$object = Map::map($this->attribute_map, $map, ['data' => $exists->getData()], $ts); |
120
|
|
|
$endpoints = [ |
121
|
|
|
$this->endpoint->getName() => [ |
122
|
|
|
'last_sync' => $ts, |
123
|
|
|
'garbage' => false, |
124
|
|
|
], |
125
|
|
|
]; |
126
|
|
|
|
127
|
|
|
$collection->changeObject($exists, $object, $simulate, $endpoints); |
|
|
|
|
128
|
|
|
$this->importRelations($exists, $map, $simulate, $endpoints); |
|
|
|
|
129
|
|
|
|
130
|
|
|
return true; |
131
|
|
|
|
132
|
|
|
break; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
return false; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Create object relations. |
140
|
|
|
*/ |
141
|
|
|
protected function importRelations(DataObjectInterface $object, array $data, bool $simulate, array $endpoints = []): bool |
142
|
|
|
{ |
143
|
|
|
$this->logger->debug('find relationships to be imported for object ['.$object->getId().']', [ |
144
|
|
|
'category' => get_class($this), |
145
|
|
|
]); |
146
|
|
|
|
147
|
|
|
foreach ($this->attribute_map->getMap() as $definition) { |
148
|
|
|
if (!isset($definition['map'])) { |
149
|
|
|
continue; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
if (!isset($data[$definition['name']])) { |
153
|
|
|
$this->logger->debug('relation attribute ['.$definition['map']['collection'].':'.$definition['map']['to'].'] not found in mapped data object', [ |
154
|
|
|
'category' => get_class($this), |
155
|
|
|
]); |
156
|
|
|
|
157
|
|
|
continue; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
$this->logger->debug('find related object from ['.$object->getId().'] to ['.$definition['map']['collection'].':'.$definition['map']['to'].'] => ['.$data[$definition['name']].']', [ |
161
|
|
|
'category' => get_class($this), |
162
|
|
|
]); |
163
|
|
|
|
164
|
|
|
$namespace = $this->endpoint->getCollection()->getResourceNamespace(); |
165
|
|
|
$collection = $namespace->getCollection($definition['map']['collection']); |
166
|
|
|
$relative = $collection->getObject([ |
167
|
|
|
$definition['map']['to'] => $data[$definition['name']], |
168
|
|
|
]); |
169
|
|
|
|
170
|
|
|
$this->logger->debug('ensure relation state ['.$definition['map']['ensure'].'] for relation to ['.$relative->getId().']', [ |
171
|
|
|
'category' => get_class($this), |
172
|
|
|
]); |
173
|
|
|
|
174
|
|
|
switch ($definition['map']['ensure']) { |
175
|
|
|
case WorkflowInterface::ENSURE_EXISTS: |
176
|
|
|
case WorkflowInterface::ENSURE_LAST: |
177
|
|
|
$namespace = $this->endpoint->getCollection()->getResourceNamespace()->getName(); |
178
|
|
|
$collection = $this->endpoint->getCollection()->getName(); |
179
|
|
|
$ep = $this->endpoint->getName(); |
180
|
|
|
|
181
|
|
|
$endpoints = [ |
182
|
|
|
join('/', [$namespace, $collection, $ep]) => $endpoints[$ep], |
183
|
|
|
]; |
184
|
|
|
|
185
|
|
|
$object->createOrUpdateRelation($relative, [], $simulate, $endpoints); |
186
|
|
|
|
187
|
|
|
break; |
188
|
|
|
default: |
189
|
|
|
} |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
return true; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* Get import object. |
197
|
|
|
*/ |
198
|
|
|
protected function getImportObject(CollectionInterface $collection, array $map, array $object, UTCDateTimeInterface $ts): ?DataObjectInterface |
|
|
|
|
199
|
|
|
{ |
200
|
|
|
$filter = array_intersect_key($map, array_flip($this->endpoint->getImport())); |
201
|
|
|
//TODO: debug import line here |
202
|
|
|
|
203
|
|
|
if (empty($filter) || count($filter) !== count($this->endpoint->getImport())) { |
204
|
|
|
throw new Exception\ImportConditionNotMet('import condition attributes are not available from mapping'); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
try { |
208
|
|
|
$exists = $collection->getObject($filter, false); |
209
|
|
|
} catch (DataObjectException\MultipleFound $e) { |
210
|
|
|
throw $e; |
211
|
|
|
} catch (DataObjectException\NotFound $e) { |
212
|
|
|
return null; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/*$endpoints = $exists->getEndpoints(); |
216
|
|
|
|
217
|
|
|
if ($exists !== false && isset($endpoints[$this->endpoint->getName()]) |
218
|
|
|
&& $endpoints[$this->endpoint->getName()]['last_sync']->toDateTime() >= $ts->toDateTime()) { |
219
|
|
|
throw new Exception\ImportConditionNotMet('import filter matched multiple source objects'); |
220
|
|
|
}*/ |
221
|
|
|
|
222
|
|
|
return $exists; |
223
|
|
|
} |
224
|
|
|
} |
225
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.