1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Victoire\Bundle\WidgetMapBundle\Warmer; |
4
|
|
|
|
5
|
|
|
use Doctrine\ORM\EntityManager; |
6
|
|
|
use Symfony\Component\PropertyAccess\PropertyAccess; |
7
|
|
|
use Victoire\Bundle\BusinessEntityBundle\Resolver\BusinessEntityResolver; |
8
|
|
|
use Victoire\Bundle\BusinessPageBundle\Entity\BusinessTemplate; |
9
|
|
|
use Victoire\Bundle\CoreBundle\Entity\Link; |
10
|
|
|
use Victoire\Bundle\CoreBundle\Entity\View; |
11
|
|
|
use Victoire\Bundle\CriteriaBundle\Entity\Criteria; |
12
|
|
|
use Victoire\Bundle\PageBundle\Entity\Page; |
13
|
|
|
use Victoire\Bundle\ViewReferenceBundle\Connector\ViewReferenceRepository; |
14
|
|
|
use Victoire\Bundle\ViewReferenceBundle\ViewReference\ViewReference; |
15
|
|
|
use Victoire\Bundle\WidgetBundle\Entity\Traits\LinkTrait; |
16
|
|
|
use Victoire\Bundle\WidgetBundle\Entity\Widget; |
17
|
|
|
use Victoire\Bundle\WidgetBundle\Helper\WidgetHelper; |
18
|
|
|
use Victoire\Bundle\WidgetBundle\Repository\WidgetRepository; |
19
|
|
|
use Victoire\Bundle\WidgetMapBundle\Entity\WidgetMap; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* WidgetDataWarmer. |
23
|
|
|
* |
24
|
|
|
* This class prepare all widgets with their associated entities for the current View |
25
|
|
|
* to reduce queries during page rendering. |
26
|
|
|
* Only OneToMany and ManyToOne associations are handled because no OneToOne or ManyToMany |
27
|
|
|
* associations have been used in Widgets. |
28
|
|
|
* |
29
|
|
|
* Ref: victoire_widget_map.widget_data_warmer |
30
|
|
|
*/ |
31
|
|
|
class WidgetDataWarmer |
32
|
|
|
{ |
33
|
|
|
/* @var $em EntityManager */ |
34
|
|
|
protected $em; |
35
|
|
|
protected $viewReferenceRepository; |
36
|
|
|
protected $widgetHelper; |
37
|
|
|
protected $accessor; |
38
|
|
|
protected $manyToOneAssociations; |
39
|
|
|
/** |
40
|
|
|
* @var BusinessEntityResolver |
41
|
|
|
*/ |
42
|
|
|
private $businessEntityResolver; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* Constructor. |
46
|
|
|
* |
47
|
|
|
* @param ViewReferenceRepository $viewReferenceRepository |
48
|
|
|
* @param WidgetHelper $widgetHelper |
49
|
|
|
* @param BusinessEntityResolver $businessEntityResolver |
50
|
|
|
*/ |
51
|
|
|
public function __construct( |
52
|
|
|
ViewReferenceRepository $viewReferenceRepository, |
53
|
|
|
WidgetHelper $widgetHelper, |
54
|
|
|
BusinessEntityResolver $businessEntityResolver |
55
|
|
|
) { |
56
|
|
|
$this->viewReferenceRepository = $viewReferenceRepository; |
57
|
|
|
$this->widgetHelper = $widgetHelper; |
58
|
|
|
$this->accessor = PropertyAccess::createPropertyAccessor(); |
59
|
|
|
$this->businessEntityResolver = $businessEntityResolver; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Find all Widgets for current View, inject them in WidgetMap and warm associated entities. |
64
|
|
|
* |
65
|
|
|
* @param EntityManager $em |
66
|
|
|
* @param View $view |
67
|
|
|
*/ |
68
|
|
|
public function warm(EntityManager $em, View $view) |
|
|
|
|
69
|
|
|
{ |
70
|
|
|
$this->em = $em; |
71
|
|
|
|
72
|
|
|
/* @var WidgetRepository $widgetRepo */ |
73
|
|
|
$widgetRepo = $this->em->getRepository('Victoire\Bundle\WidgetBundle\Entity\Widget'); |
74
|
|
|
$viewWidgets = $widgetRepo->findAllWidgetsForView($view); |
75
|
|
|
|
76
|
|
|
$this->injectWidgets($view, $viewWidgets); |
77
|
|
|
|
78
|
|
|
$this->extractAssociatedEntities($viewWidgets); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Inject Widgets in View's builtWidgetMap. |
83
|
|
|
* |
84
|
|
|
* @param View $view |
85
|
|
|
* @param $viewWidgets |
86
|
|
|
*/ |
87
|
|
|
private function injectWidgets(View $view, $viewWidgets) |
88
|
|
|
{ |
89
|
|
|
$builtWidgetMap = $view->getBuiltWidgetMap(); |
90
|
|
|
|
91
|
|
|
foreach ($builtWidgetMap as $slot => $widgetMaps) { |
92
|
|
|
foreach ($widgetMaps as $i => $widgetMap) { |
93
|
|
|
foreach ($viewWidgets as $widget) { |
94
|
|
|
if ($widget->getWidgetMap() == $widgetMap) { |
95
|
|
|
$builtWidgetMap[$slot][$i]->addWidget($widget); |
96
|
|
|
|
97
|
|
|
//Override Collection default behaviour to avoid useless query |
98
|
|
|
$builtWidgetMap[$slot][$i]->getWidgets()->setDirty(false); |
99
|
|
|
$builtWidgetMap[$slot][$i]->getWidgets()->setInitialized(true); |
100
|
|
|
continue; |
101
|
|
|
} |
102
|
|
|
} |
103
|
|
|
} |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
$view->setBuiltWidgetMap($builtWidgetMap); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Pass through all widgets and associated entities to extract all missing associations, |
111
|
|
|
* store it by repository to group queries by entity type. |
112
|
|
|
* |
113
|
|
|
* @param Widget[] $entities Widgets and associated entities |
114
|
|
|
*/ |
115
|
|
|
private function extractAssociatedEntities(array $entities) |
116
|
|
|
{ |
117
|
|
|
$linkIds = $associatedEntities = []; |
118
|
|
|
|
119
|
|
|
foreach ($entities as $entity) { |
120
|
|
|
$reflect = new \ReflectionClass($entity); |
121
|
|
|
|
122
|
|
|
//If Widget is already in cache, extract only its Criterias (used outside Widget rendering) |
123
|
|
|
$widgetCached = ($entity instanceof Widget && $this->widgetHelper->isCacheEnabled($entity)); |
124
|
|
|
|
125
|
|
|
//If Widget has LinkTrait, store the entity link id |
126
|
|
|
if (!$widgetCached && $this->hasLinkTrait($reflect) && $entity->getLink()) { |
|
|
|
|
127
|
|
|
$linkIds[] = $entity->getLink()->getId(); |
|
|
|
|
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
//Pass through all entity associations |
131
|
|
|
$metaData = $this->em->getClassMetadata(get_class($entity)); |
132
|
|
|
foreach ($metaData->getAssociationMappings() as $association) { |
133
|
|
|
$targetClass = $association['targetEntity']; |
134
|
|
|
|
135
|
|
|
//Skip already set WidgetMap association |
136
|
|
|
if ($targetClass == WidgetMap::class) { |
137
|
|
|
continue; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
//If Widget has OneToOne or ManyToOne association, store target entity id to construct |
141
|
|
|
//a single query for this entity type |
142
|
|
|
if ($metaData->isSingleValuedAssociation($association['fieldName']) |
143
|
|
|
&& !$widgetCached |
144
|
|
|
) { |
145
|
|
|
//If target Entity is not null, treat it |
146
|
|
|
if ($targetEntity = $this->accessor->getValue($entity, $association['fieldName'])) { |
147
|
|
|
$associatedEntities[$targetClass]['id'][] = new AssociatedEntityToWarm( |
148
|
|
|
AssociatedEntityToWarm::TYPE_MANY_TO_ONE, |
149
|
|
|
$entity, |
150
|
|
|
$association['fieldName'], |
151
|
|
|
$targetEntity->getId() |
152
|
|
|
); |
153
|
|
|
} |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
//If Widget has OneToMany association, store owner entity id and mappedBy value |
157
|
|
|
//to construct a single query for this entity type |
158
|
|
|
elseif ($metaData->isCollectionValuedAssociation($association['fieldName'])) { |
159
|
|
|
|
160
|
|
|
//Even if Widget is cached, we need its Criterias used before cache call |
161
|
|
|
if (!$widgetCached || $targetClass == Criteria::class) { |
162
|
|
|
|
163
|
|
|
//If Collection is not null, treat it |
164
|
|
|
if ($this->accessor->getValue($entity, $association['fieldName'])) { |
165
|
|
|
|
166
|
|
|
//Don't use Collection getter directly and override Collection |
167
|
|
|
//default behaviour to avoid useless query |
168
|
|
|
$getter = 'get'.ucwords($association['fieldName']); |
169
|
|
|
$entity->$getter()->setDirty(false); |
170
|
|
|
$entity->$getter()->setInitialized(true); |
171
|
|
|
|
172
|
|
|
$associatedEntities[$targetClass][$association['mappedBy']][] = new AssociatedEntityToWarm( |
173
|
|
|
AssociatedEntityToWarm::TYPE_ONE_TO_MANY, |
174
|
|
|
$entity, |
175
|
|
|
$association['fieldName'], |
176
|
|
|
$entity->getId() |
177
|
|
|
); |
178
|
|
|
} |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
if ($entity instanceof Widget && $proxy = $entity->getEntityProxy()) { |
183
|
|
|
$entity->setEntity($this->businessEntityResolver->getBusinessEntity($proxy)); |
184
|
|
|
} |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
$newEntities = $this->setAssociatedEntities($associatedEntities); |
188
|
|
|
$this->setPagesForLinks($linkIds); |
189
|
|
|
|
190
|
|
|
//Recursive call if previous has return new entities to warm |
191
|
|
|
if ($newEntities) { |
|
|
|
|
192
|
|
|
$this->extractAssociatedEntities($newEntities); |
193
|
|
|
} |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* Set all missing associated entities. |
198
|
|
|
* |
199
|
|
|
* @param array $repositories |
200
|
|
|
* |
201
|
|
|
* @throws \Throwable |
202
|
|
|
* @throws \TypeError |
203
|
|
|
* |
204
|
|
|
* @return array |
205
|
|
|
*/ |
206
|
|
|
private function setAssociatedEntities(array $repositories) |
207
|
|
|
{ |
208
|
|
|
$newEntities = []; |
209
|
|
|
|
210
|
|
|
foreach ($repositories as $repositoryName => $findMethods) { |
211
|
|
|
foreach ($findMethods as $findMethod => $associatedEntitiesToWarm) { |
212
|
|
|
|
213
|
|
|
//Extract ids to search |
214
|
|
|
$idsToSearch = array_map(function ($associatedEntityToWarm) { |
215
|
|
|
return $associatedEntityToWarm->getEntityId(); |
216
|
|
|
}, $associatedEntitiesToWarm); |
217
|
|
|
|
218
|
|
|
//Find by id for ManyToOne associations based on target entity id |
219
|
|
|
//Find by mappedBy value for OneToMany associations based on owner entity id |
220
|
|
|
$foundEntities = $this->em->getRepository($repositoryName)->findBy([ |
221
|
|
|
$findMethod => array_values($idsToSearch), |
222
|
|
|
]); |
223
|
|
|
|
224
|
|
|
/* @var AssociatedEntityToWarm[] $associatedEntitiesToWarm */ |
225
|
|
|
foreach ($associatedEntitiesToWarm as $associatedEntityToWarm) { |
226
|
|
|
foreach ($foundEntities as $foundEntity) { |
227
|
|
|
if ($associatedEntityToWarm->getType() == AssociatedEntityToWarm::TYPE_MANY_TO_ONE |
228
|
|
|
&& $foundEntity->getId() == $associatedEntityToWarm->getEntityId() |
229
|
|
|
) { |
230
|
|
|
$inheritorEntity = $associatedEntityToWarm->getInheritorEntity(); |
231
|
|
|
$inheritorPropertyName = $associatedEntityToWarm->getInheritorPropertyName(); |
232
|
|
|
$this->accessor->setValue($inheritorEntity, $inheritorPropertyName, $foundEntity); |
233
|
|
|
continue; |
234
|
|
|
} elseif ($associatedEntityToWarm->getType() == AssociatedEntityToWarm::TYPE_ONE_TO_MANY |
235
|
|
|
&& $this->accessor->getValue($foundEntity, $findMethod) == $associatedEntityToWarm->getInheritorEntity() |
236
|
|
|
) { |
237
|
|
|
$inheritorEntity = $associatedEntityToWarm->getInheritorEntity(); |
238
|
|
|
$inheritorPropertyName = $associatedEntityToWarm->getInheritorPropertyName(); |
239
|
|
|
|
240
|
|
|
//Don't use Collection getter directly and override Collection |
241
|
|
|
//default behaviour to avoid useless query |
242
|
|
|
$getter = 'get'.ucwords($inheritorPropertyName); |
243
|
|
|
$inheritorEntity->$getter()->add($foundEntity); |
244
|
|
|
$inheritorEntity->$getter()->setDirty(false); |
245
|
|
|
$inheritorEntity->$getter()->setInitialized(true); |
246
|
|
|
|
247
|
|
|
//Store new entities to warm if necessary |
248
|
|
|
$newEntities[] = $foundEntity; |
249
|
|
|
continue; |
250
|
|
|
} |
251
|
|
|
} |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
return $newEntities; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Set viewReferencePage for each link. |
261
|
|
|
* |
262
|
|
|
* @param array $linkIds |
263
|
|
|
*/ |
264
|
|
|
private function setPagesForLinks(array $linkIds) |
265
|
|
|
{ |
266
|
|
|
$viewReferences = []; |
267
|
|
|
|
268
|
|
|
/* @var Link[] $links */ |
269
|
|
|
$links = $this->em->getRepository('VictoireCoreBundle:Link')->findById($linkIds); |
270
|
|
|
|
271
|
|
|
foreach ($links as $link) { |
272
|
|
|
if ($link->getParameters()['linkType'] == 'viewReference') { |
273
|
|
|
$viewReference = $this->viewReferenceRepository->getOneReferenceByParameters([ |
274
|
|
|
'id' => $link->getParameters()['viewReference'], |
275
|
|
|
'locale' => $link->getParameters()['locale'], |
276
|
|
|
]); |
277
|
|
|
|
278
|
|
|
if ($viewReference instanceof ViewReference) { |
279
|
|
|
$viewReferences[$link->getId()] = $viewReference; |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
/* @var Page[] $pages */ |
285
|
|
|
$pages = $this->em->getRepository('VictoireCoreBundle:View')->findByViewReferences($viewReferences); |
286
|
|
|
|
287
|
|
|
foreach ($links as $link) { |
288
|
|
|
foreach ($pages as $page) { |
289
|
|
|
if (!($page instanceof BusinessTemplate) && $page->getReference() && $link->getViewReference() == $page->getReference()->getId()) { |
290
|
|
|
$link->setViewReferencePage($page); |
291
|
|
|
} |
292
|
|
|
} |
293
|
|
|
} |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* Check if reflection class has LinkTrait. |
298
|
|
|
* |
299
|
|
|
* @param \ReflectionClass $reflect |
300
|
|
|
* |
301
|
|
|
* @return bool |
302
|
|
|
*/ |
303
|
|
|
private function hasLinkTrait(\ReflectionClass $reflect) |
304
|
|
|
{ |
305
|
|
|
$traits = $reflect->getTraits(); |
306
|
|
|
foreach ($traits as $trait) { |
307
|
|
|
if ($trait->getName() == LinkTrait::class) { |
|
|
|
|
308
|
|
|
return true; |
309
|
|
|
} |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
if ($parentClass = $reflect->getParentClass()) { |
313
|
|
|
if ($this->hasLinkTrait($parentClass)) { |
314
|
|
|
return true; |
315
|
|
|
} |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
return false; |
319
|
|
|
} |
320
|
|
|
} |
321
|
|
|
|
The
EntityManager
might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:If that code throws an exception and the
EntityManager
is closed. Any other code which depends on the same instance of theEntityManager
during this request will fail.On the other hand, if you instead inject the
ManagerRegistry
, thegetManager()
method guarantees that you will always get a usable manager instance.