1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Sulu\Component\DocumentManager\Subscriber\Core; |
4
|
|
|
|
5
|
|
|
use PHPCR\NodeInterface; |
6
|
|
|
use Sulu\Component\DocumentManager\DocumentAccessor; |
7
|
|
|
use Sulu\Component\DocumentManager\DocumentRegistry; |
8
|
|
|
use Sulu\Component\DocumentManager\Event\AbstractMappingEvent; |
9
|
|
|
use Sulu\Component\DocumentManager\Event\PersistEvent; |
10
|
|
|
use Sulu\Component\DocumentManager\Events; |
11
|
|
|
use Sulu\Component\DocumentManager\Exception\InvalidLocaleException; |
12
|
|
|
use Sulu\Component\DocumentManager\MetadataFactoryInterface; |
13
|
|
|
use Sulu\Component\DocumentManager\PropertyEncoder; |
14
|
|
|
use Sulu\Component\DocumentManager\ProxyFactory; |
15
|
|
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* This subscriber uses the field map in the metadata to map fields from |
19
|
|
|
* the PHPCR nodes to the document and vice-versa. |
20
|
|
|
*/ |
21
|
|
|
class MappingSubscriber implements EventSubscriberInterface |
22
|
|
|
{ |
23
|
|
|
/** |
24
|
|
|
* @var MetadataFactoryInterface |
25
|
|
|
*/ |
26
|
|
|
private $factory; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var PropertyEncoder |
30
|
|
|
*/ |
31
|
|
|
private $encoder; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var ProxyFactory |
35
|
|
|
*/ |
36
|
|
|
private $proxyFactory; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var DocumentRegistry |
40
|
|
|
*/ |
41
|
|
|
private $documentRegistry; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @param MetadataFactoryInterface $factory |
45
|
|
|
* @param PropertyEncoder $encoder |
46
|
|
|
* @param ProxyFactory $proxyFactory |
47
|
|
|
* @param DocumentRegistry $documentRegistry |
48
|
|
|
*/ |
49
|
|
|
public function __construct( |
50
|
|
|
MetadataFactoryInterface $factory, |
51
|
|
|
PropertyEncoder $encoder, |
52
|
|
|
ProxyFactory $proxyFactory, |
53
|
|
|
DocumentRegistry $documentRegistry |
54
|
|
|
) { |
55
|
|
|
$this->factory = $factory; |
56
|
|
|
$this->encoder = $encoder; |
57
|
|
|
$this->proxyFactory = $proxyFactory; |
58
|
|
|
$this->documentRegistry = $documentRegistry; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* {@inheritdoc} |
63
|
|
|
*/ |
64
|
|
|
public static function getSubscribedEvents() |
65
|
|
|
{ |
66
|
|
|
return [ |
67
|
|
|
Events::HYDRATE => ['handleHydrate', -100], |
68
|
|
|
Events::PERSIST => ['handlePersist', -100], |
69
|
|
|
]; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* @param PersistEvent $event |
74
|
|
|
*/ |
75
|
|
|
public function handlePersist(PersistEvent $event) |
76
|
|
|
{ |
77
|
|
|
$metadata = $this->factory->getMetadataForClass(get_class($event->getDocument())); |
78
|
|
|
$locale = $event->getLocale(); |
79
|
|
|
$node = $event->getNode(); |
80
|
|
|
$accessor = $event->getAccessor(); |
81
|
|
|
|
82
|
|
View Code Duplication |
foreach ($metadata->getFieldMappings() as $fieldName => $fieldMapping) { |
|
|
|
|
83
|
|
|
if (false === $fieldMapping['mapped']) { |
84
|
|
|
continue; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
switch ($fieldMapping['type']) { |
88
|
|
|
case 'reference': |
89
|
|
|
$this->persistReference($node, $accessor, $fieldName, $locale, $fieldMapping); |
90
|
|
|
break; |
91
|
|
|
default: |
92
|
|
|
$this->persistGeneric($node, $accessor, $fieldName, $locale, $fieldMapping); |
93
|
|
|
} |
94
|
|
|
} |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Persist a reference field type. |
99
|
|
|
* |
100
|
|
|
* @param NodeInterface $node |
101
|
|
|
* @param DocumentAccessor $accessor |
102
|
|
|
* @param mixed $fieldName |
103
|
|
|
* @param mixed $locale |
104
|
|
|
* @param mixed $fieldMapping |
105
|
|
|
*/ |
106
|
|
|
private function persistReference( |
107
|
|
|
NodeInterface $node, |
108
|
|
|
DocumentAccessor $accessor, |
109
|
|
|
$fieldName, |
110
|
|
|
$locale, |
111
|
|
|
$fieldMapping |
112
|
|
|
) { |
113
|
|
|
$referenceDocument = $accessor->get($fieldName); |
114
|
|
|
|
115
|
|
|
if (!$referenceDocument) { |
116
|
|
|
return; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
if ($fieldMapping['multiple']) { |
120
|
|
|
throw new \InvalidArgumentException( |
121
|
|
|
sprintf( |
122
|
|
|
'Mapping references as multiple not currently supported (when mapping "%s")', |
123
|
|
|
$fieldName |
124
|
|
|
) |
125
|
|
|
); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
try { |
129
|
|
|
$referenceNode = $this->documentRegistry->getNodeForDocument($referenceDocument); |
130
|
|
|
$phpcrName = $this->encoder->encode($fieldMapping['encoding'], $fieldMapping['property'], $locale); |
131
|
|
|
$node->setProperty($phpcrName, $referenceNode); |
132
|
|
|
} catch (InvalidLocaleException $ex) { |
133
|
|
|
// arguments unvalid no valid propertyname could be generated (e.g. no locale given for localized encoding) |
134
|
|
|
return; |
135
|
|
|
} |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Persist "scalar" field types. |
140
|
|
|
* |
141
|
|
|
* @param NodeInterface $node |
142
|
|
|
* @param DocumentAccessor $accessor |
143
|
|
|
* @param mixed $fieldName |
144
|
|
|
* @param mixed $locale |
145
|
|
|
* @param array $fieldMapping |
146
|
|
|
*/ |
147
|
|
View Code Duplication |
private function persistGeneric( |
|
|
|
|
148
|
|
|
NodeInterface $node, |
149
|
|
|
DocumentAccessor $accessor, |
150
|
|
|
$fieldName, |
151
|
|
|
$locale, |
152
|
|
|
array $fieldMapping |
153
|
|
|
) { |
154
|
|
|
try { |
155
|
|
|
$phpcrName = $this->encoder->encode($fieldMapping['encoding'], $fieldMapping['property'], $locale); |
156
|
|
|
$value = $accessor->get($fieldName); |
157
|
|
|
$this->validateFieldValue($value, $fieldName, $fieldMapping); |
158
|
|
|
$node->setProperty($phpcrName, $value); |
159
|
|
|
} catch (InvalidLocaleException $ex) { |
160
|
|
|
// arguments unvalid no valid propertyname could be generated (e.g. no locale given for localized encoding) |
161
|
|
|
return; |
162
|
|
|
} |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* @param AbstractMappingEvent $event |
167
|
|
|
*/ |
168
|
|
|
public function handleHydrate(AbstractMappingEvent $event) |
169
|
|
|
{ |
170
|
|
|
$class = get_class($event->getDocument()); |
171
|
|
|
|
172
|
|
|
// TODO: Return false here in case this is for instance an UnknownDocument. |
173
|
|
|
// But we should probably map the UnknownDocument and let an Exception be |
174
|
|
|
// thrown in other cases. |
175
|
|
|
if (false === $this->factory->hasMetadataForClass($class)) { |
176
|
|
|
return; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
$metadata = $this->factory->getMetadataForClass($class); |
180
|
|
|
$locale = $event->getLocale(); |
181
|
|
|
$node = $event->getNode(); |
182
|
|
|
$accessor = $event->getAccessor(); |
183
|
|
|
$document = $event->getDocument(); |
184
|
|
|
|
185
|
|
View Code Duplication |
foreach ($metadata->getFieldMappings() as $fieldName => $fieldMapping) { |
|
|
|
|
186
|
|
|
if (false === $fieldMapping['mapped']) { |
187
|
|
|
continue; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
switch ($fieldMapping['type']) { |
191
|
|
|
case 'reference': |
192
|
|
|
$this->hydrateReferenceField($node, $document, $accessor, $fieldName, $locale, $fieldMapping); |
193
|
|
|
break; |
194
|
|
|
default: |
195
|
|
|
$this->hydrateGenericField($node, $accessor, $fieldName, $locale, $fieldMapping); |
196
|
|
|
} |
197
|
|
|
} |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Hydrate reference field types. |
202
|
|
|
* |
203
|
|
|
* @param NodeInterface $node |
204
|
|
|
* @param mixed $document |
205
|
|
|
* @param DocumentAccessor $accessor |
206
|
|
|
* @param mixed $fieldName |
207
|
|
|
* @param mixed $locale |
208
|
|
|
* @param array $fieldMapping |
209
|
|
|
*/ |
210
|
|
View Code Duplication |
private function hydrateReferenceField( |
|
|
|
|
211
|
|
|
NodeInterface $node, |
212
|
|
|
$document, |
213
|
|
|
DocumentAccessor $accessor, |
214
|
|
|
$fieldName, |
215
|
|
|
$locale, |
216
|
|
|
array $fieldMapping |
217
|
|
|
) { |
218
|
|
|
try { |
219
|
|
|
$phpcrName = $this->encoder->encode($fieldMapping['encoding'], $fieldMapping['property'], $locale); |
220
|
|
|
$referencedNode = $node->getPropertyValueWithDefault( |
221
|
|
|
$phpcrName, |
222
|
|
|
$this->getDefaultValue($fieldMapping) |
223
|
|
|
); |
224
|
|
|
|
225
|
|
|
if ($referencedNode) { |
226
|
|
|
$accessor->set( |
227
|
|
|
$fieldName, |
228
|
|
|
$this->proxyFactory->createProxyForNode($document, $referencedNode) |
229
|
|
|
); |
230
|
|
|
} |
231
|
|
|
} catch (InvalidLocaleException $ex) { |
232
|
|
|
// arguments unvalid no valid propertyname could be generated (e.g. no locale given for localized encoding) |
233
|
|
|
return; |
234
|
|
|
} |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* Hydrate "scalar" field types. |
239
|
|
|
* |
240
|
|
|
* @param NodeInterface $node |
241
|
|
|
* @param DocumentAccessor $accessor |
242
|
|
|
* @param mixed $fieldName |
243
|
|
|
* @param mixed $locale |
244
|
|
|
* @param array $fieldMapping |
245
|
|
|
*/ |
246
|
|
|
private function hydrateGenericField( |
247
|
|
|
NodeInterface $node, |
248
|
|
|
DocumentAccessor $accessor, |
249
|
|
|
$fieldName, |
250
|
|
|
$locale, |
251
|
|
|
array $fieldMapping |
252
|
|
|
) { |
253
|
|
|
try { |
254
|
|
|
$phpcrName = $this->encoder->encode($fieldMapping['encoding'], $fieldMapping['property'], $locale); |
255
|
|
|
$value = $node->getPropertyValueWithDefault( |
256
|
|
|
$phpcrName, |
257
|
|
|
$this->getDefaultValue($fieldMapping) |
258
|
|
|
); |
259
|
|
|
$accessor->set($fieldName, $value); |
260
|
|
|
} catch (InvalidLocaleException $ex) { |
261
|
|
|
// arguments unvalid no valid propertyname could be generated (e.g. no locale given for localized encoding) |
262
|
|
|
return; |
263
|
|
|
} |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
private function getDefaultValue(array $fieldMapping) |
267
|
|
|
{ |
268
|
|
|
if ($fieldMapping['default']) { |
269
|
|
|
return $fieldMapping['default']; |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
return $fieldMapping['multiple'] ? [] : null; |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
private function validateFieldValue($value, $fieldName, $fieldMapping) |
276
|
|
|
{ |
277
|
|
View Code Duplication |
if ($fieldMapping['multiple'] && !is_array($value)) { |
|
|
|
|
278
|
|
|
throw new \InvalidArgumentException( |
279
|
|
|
sprintf( |
280
|
|
|
'Field "%s" is mapped as multiple, and therefore must be an array, got "%s"', |
281
|
|
|
$fieldName, |
282
|
|
|
is_object($value) ? get_class($value) : gettype($value) |
283
|
|
|
) |
284
|
|
|
); |
285
|
|
|
} |
286
|
|
|
} |
287
|
|
|
} |
288
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.