Completed
Push — master ( 3914f7...10fa97 )
by Leny
11:30
created

WidgetManager::cloneEntity()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 32
rs 8.439
cc 6
eloc 19
nc 7
nop 1
1
<?php
2
3
namespace Victoire\Bundle\WidgetBundle\Model;
4
5
use Doctrine\ORM\EntityManager;
6
use Doctrine\ORM\Mapping\ClassMetadataInfo;
7
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
8
use Symfony\Component\HttpFoundation\Request;
9
use Symfony\Component\PropertyAccess\PropertyAccess;
10
use Victoire\Bundle\BusinessEntityBundle\Entity\BusinessEntity;
11
use Victoire\Bundle\BusinessEntityBundle\Reader\BusinessEntityCacheReader;
12
use Victoire\Bundle\BusinessPageBundle\Entity\VirtualBusinessPage;
13
use Victoire\Bundle\BusinessPageBundle\Transformer\VirtualToBusinessPageTransformer;
14
use Victoire\Bundle\CoreBundle\Entity\View;
15
use Victoire\Bundle\CoreBundle\Template\TemplateMapper;
16
use Victoire\Bundle\FormBundle\Helper\FormErrorHelper;
17
use Victoire\Bundle\PageBundle\Entity\WidgetMap;
18
use Victoire\Bundle\PageBundle\Helper\PageHelper;
19
use Victoire\Bundle\WidgetBundle\Builder\WidgetFormBuilder;
20
use Victoire\Bundle\WidgetBundle\Helper\WidgetHelper;
21
use Victoire\Bundle\WidgetBundle\Renderer\WidgetRenderer;
22
use Victoire\Bundle\WidgetBundle\Resolver\WidgetContentResolver;
23
use Victoire\Bundle\WidgetMapBundle\Builder\WidgetMapBuilder;
24
use Victoire\Bundle\WidgetMapBundle\Builder\WidgetMapPositionBuilder;
25
use Victoire\Bundle\WidgetMapBundle\Helper\WidgetMapHelper;
26
use Victoire\Bundle\WidgetMapBundle\Manager\WidgetMapManager;
27
28
/**
29
 * This manager handles crud operations on a Widget.
30
 */
31
class WidgetManager
32
{
33
    protected $widgetFormBuilder;
34
    protected $widgetHelper;
35
    protected $widgetContentResolver;
36
    protected $entityManager;
37
    protected $formErrorHelper; // @victoire_form.error_helper
38
    protected $request; // @request
39
    protected $widgetMapManager;
40
    protected $widgetMapPositionBuilder;
41
    protected $cacheReader; // @victoire_business_entity.cache_reader
42
    protected $victoireTemplating;
43
    protected $pageHelper;
44
    protected $slots; // %victoire_core.slots%
45
    protected $virtualToBpTransformer; // %victoire_core.slots%
46
47
    /**
48
     * construct.
49
     *
50
     * @param WidgetHelper              $widgetHelper
51
     * @param WidgetFormBuilder         $widgetFormBuilder
52
     * @param WidgetContentResolver     $widgetContentResolver
53
     * @param WidgetRenderer            $widgetRenderer
54
     * @param EntityManager             $entityManager
55
     * @param FormErrorHelper           $formErrorHelper
56
     * @param Request                   $request
57
     * @param WidgetMapManager          $widgetMapManager
58
     * @param WidgetMapHelper           $widgetMapHelper
59
     * @param WidgetMapBuilder          $widgetMapBuilder
60
     * @param WidgetMapPositionBuilder  $widgetMapPositionBuilder
61
     * @param BusinessEntityCacheReader $cacheReader
62
     * @param TemplateMapper            $victoireTemplating
63
     * @param PageHelper                $pageHelper
64
     * @param array                     $slots
65
     */
66
    public function __construct(
67
        WidgetHelper $widgetHelper,
68
        WidgetFormBuilder $widgetFormBuilder,
69
        WidgetContentResolver $widgetContentResolver,
70
        WidgetRenderer $widgetRenderer,
71
        EntityManager $entityManager,
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $entityManager. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

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:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
72
        FormErrorHelper $formErrorHelper,
73
        Request $request,
0 ignored issues
show
Bug introduced by
You have injected the Request via parameter $request. This is generally not recommended as there might be multiple instances during a request cycle (f.e. when using sub-requests). Instead, it is recommended to inject the RequestStack and retrieve the current request each time you need it via getCurrentRequest().
Loading history...
74
        WidgetMapManager $widgetMapManager,
75
        WidgetMapHelper $widgetMapHelper,
76
        WidgetMapBuilder $widgetMapBuilder,
77
        WidgetMapPositionBuilder $widgetMapPositionBuilder,
78
        BusinessEntityCacheReader $cacheReader,
79
        TemplateMapper $victoireTemplating,
80
        PageHelper $pageHelper,
81
        $slots,
82
        VirtualToBusinessPageTransformer $virtualToBpTransformer
83
    ) {
84
        $this->widgetFormBuilder = $widgetFormBuilder;
85
        $this->widgetHelper = $widgetHelper;
86
        $this->widgetContentResolver = $widgetContentResolver;
87
        $this->widgetRenderer = $widgetRenderer;
0 ignored issues
show
Bug introduced by
The property widgetRenderer does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
88
        $this->entityManager = $entityManager;
89
        $this->formErrorHelper = $formErrorHelper;
90
        $this->request = $request;
91
        $this->widgetMapManager = $widgetMapManager;
92
        $this->widgetMapHelper = $widgetMapHelper;
0 ignored issues
show
Bug introduced by
The property widgetMapHelper does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
93
        $this->widgetMapBuilder = $widgetMapBuilder;
0 ignored issues
show
Bug introduced by
The property widgetMapBuilder does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
94
        $this->widgetMapPositionBuilder = $widgetMapPositionBuilder;
95
        $this->cacheReader = $cacheReader;
96
        $this->victoireTemplating = $victoireTemplating;
97
        $this->pageHelper = $pageHelper;
98
        $this->slots = $slots;
99
        $this->virtualToBpTransformer = $virtualToBpTransformer;
100
    }
101
102
    /**
103
     * new widget.
104
     *
105
     * @param string $type
106
     * @param string $slot
107
     * @param View   $view
108
     * @param int    $position
109
     *
110
     * @return template
111
     */
112
    public function newWidget($mode, $type, $slot, $view, $position)
113
    {
114
        $widget = $this->widgetHelper->newWidgetInstance($type, $view, $slot, $mode);
115
116
        /** @var BusinessEntity[] $classes */
117
        $classes = $this->cacheReader->getBusinessClassesForWidget($widget);
118
        $forms = $this->widgetFormBuilder->renderNewWidgetForms($slot, $view, $widget, $classes, $position);
119
120
        return [
121
            'html' => $this->victoireTemplating->render(
122
                'VictoireCoreBundle:Widget:Form/new.html.twig',
123
                [
124
                    'view'    => $view,
125
                    'classes' => $classes,
126
                    'widget'  => $widget,
127
                    'forms'   => $forms,
128
                ]
129
            ),
130
        ];
131
    }
132
133
    /**
134
     * Create a widget.
135
     *
136
     * @param string $mode
137
     * @param string $type
138
     * @param string $slotId
139
     * @param View   $view
140
     * @param string $entity
141
     * @param int    $positionReference
142
     * @param string $type
143
     *
144
     * @throws \Exception
145
     *
146
     * @return Template
147
     */
148
    public function createWidget($mode, $type, $slotId, View $view, $entity, $positionReference)
149
    {
150
        //services
151
        $formErrorHelper = $this->formErrorHelper;
152
        $request = $this->request;
153
154
        if ($view instanceof VirtualBusinessPage) {
155
            $this->virtualToBpTransformer->transform($view);
156
        }
157
        //create a new widget
158
        $widget = $this->widgetHelper->newWidgetInstance($type, $view, $slotId, $mode);
159
160
        $form = $this->widgetFormBuilder->callBuildFormSwitchParameters($widget, $view, $entity, $positionReference);
161
162
        $noValidate = $request->query->get('novalidate', false);
163
164
        $form->handleRequest($request);
165
        if ($noValidate === false && $form->isValid()) {
166
            if (!$view->getId()) {
167
                //create a view for the business entity instance if we are currently on a virtual one
168
                $this->entityManager->persist($view);
169
            }
170
171
            //get the widget from the form
172
            $widget = $form->getData();
173
174
            //update fields of the widget
175
            $widget->setBusinessEntityId($entity);
176
177
            $widget->positionReference = $positionReference;
178
            $widget->slotId = $slotId;
179
            //persist the widget
180
            $this->entityManager->persist($widget);
181
            $this->entityManager->flush();
182
183
            //create the new widget map
184
            $widgetMapEntry = new WidgetMap();
185
            $widgetMapEntry->setAction(WidgetMap::ACTION_CREATE);
186
            $widgetMapEntry->setWidgetId($widget->getId());
187
188
            $widgetMap = $this->widgetMapBuilder->build($view, false);
189
190
            $widgetMapEntry = $this->widgetMapPositionBuilder->generateWidgetPosition($this->entityManager, $widgetMapEntry, $widget, $widgetMap, $positionReference);
191
            $this->widgetMapHelper->insertWidgetMapInSlot($slotId, $widgetMapEntry, $view);
192
193
            $this->entityManager->persist($view);
194
            $this->entityManager->flush();
195
196
            $widget->setCurrentView($view);
197
198
            //get the html for the widget
199
            $htmlWidget = $this->widgetRenderer->renderContainer($widget, $view);
200
201
            $response = [
202
                'success'  => true,
203
                'widgetId' => $widget->getId(),
204
                'html'     => $htmlWidget,
205
            ];
206
        } else {
207
            //get the errors as a string
208
            $response = [
209
                'success' => false,
210
                'message' => $noValidate === false ? $formErrorHelper->getRecursiveReadableErrors($form) : null,
0 ignored issues
show
Documentation introduced by
$form is of type object<Symfony\Component\Form\Form>, but the function expects a object<Victoire\Bundle\FormBundle\Helper\Form>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
211
                'html'    => $this->widgetFormBuilder->renderNewForm($form, $widget, $slotId, $view, $entity),
212
            ];
213
        }
214
215
        return $response;
216
    }
217
218
    /**
219
     * edit a widget.
220
     *
221
     * @param Request $request
222
     * @param Widget  $widget
223
     * @param View    $currentView
224
     * @param string  $businessEntityId The entity name is used to know which form to submit
225
     *
226
     * @return template
227
     */
228
    public function editWidget(Request $request, Widget $widget, View $currentView, $businessEntityId = null, $widgetMode = Widget::MODE_STATIC)
229
    {
230
        /** @var BusinessEntity[] $classes */
231
        $classes = $this->cacheReader->getBusinessClassesForWidget($widget);
232
233
        $widget->setCurrentView($currentView);
234
235
        //the id of the edited widget
236
        //a new widget might be created in the case of a legacy
237
        $initialWidgetId = $widget->getId();
238
239
        //the type of method used
240
        $requestMethod = $request->getMethod();
241
242
        //if the form is posted
243
        if ($requestMethod === 'POST') {
244
            //the widget view
245
            $widgetView = $widget->getView();
246
247
            //we only copy the widget if the view of the widget is not the current view
248
            if ($widgetView !== $currentView) {
249
                $widget = $this->overwriteWidget($currentView, $widget);
250
            }
251
            if ($businessEntityId !== null) {
252
                $form = $this->widgetFormBuilder->buildForm($widget, $currentView, $businessEntityId, $classes[$businessEntityId]->getClass(), $widgetMode);
253
            } else {
254
                $form = $this->widgetFormBuilder->buildForm($widget, $currentView);
255
            }
256
257
            $noValidate = $request->query->get('novalidate', false);
258
            $form->handleRequest($request);
259
            if ($noValidate === false && $form->isValid()) {
260
                $widget->setBusinessEntityId($businessEntityId);
261
262
                $this->entityManager->persist($widget);
263
264
                //update the widget map by the slots
265
                $currentView->updateWidgetMapBySlots();
266
                $this->entityManager->persist($currentView);
267
                $this->entityManager->flush();
268
269
                $response = [
270
                    'view'        => $currentView,
271
                    'success'     => true,
272
                    'html'        => $this->widgetRenderer->render($widget, $currentView),
273
                    'widgetId'    => $initialWidgetId,
274
                    'viewCssHash' => $currentView->getCssHash(),
275
                ];
276
            } else {
277
                $formErrorHelper = $this->formErrorHelper;
278
                //Return a message for developer in console and form view in order to refresh view and show form errors
279
                $response = [
280
                    'success' => false,
281
                    'message' => $noValidate === false ? $formErrorHelper->getRecursiveReadableErrors($form) : null,
282
                    'html'    => $this->widgetFormBuilder->renderForm($form, $widget, $businessEntityId),
0 ignored issues
show
Bug introduced by
It seems like $businessEntityId defined by parameter $businessEntityId on line 228 can also be of type string; however, Victoire\Bundle\WidgetBu...rmBuilder::renderForm() does only seem to accept object|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
283
                ];
284
            }
285
        } else {
286
            $forms = $this->widgetFormBuilder->renderNewWidgetForms($widget->getSlot(), $currentView, $widget, $classes);
287
288
            $response = [
289
                'success'  => true,
290
                'html'     => $this->victoireTemplating->render(
291
                    'VictoireCoreBundle:Widget:Form/edit.html.twig',
292
                    [
293
                        'view'    => $currentView,
294
                        'classes' => $classes,
295
                        'forms'   => $forms,
296
                        'widget'  => $widget,
297
                    ]
298
                ),
299
            ];
300
        }
301
302
        return $response;
303
    }
304
305
    /**
306
     * Remove a widget.
307
     *
308
     * @param Widget $widget
309
     *
310
     * @return array The parameter for the view
311
     */
312
    public function deleteWidget(Widget $widget, View $view)
313
    {
314
        //update the view deleting the widget
315
        $this->widgetMapManager->deleteWidgetFromView($view, $widget);
316
317
        //we update the widget map of the view
318
        $view->updateWidgetMapBySlots();
319
        //Used to update view in callback (we do it before delete it else it'll not exists anymore)
320
        $widgetId = $widget->getId();
321
        //the widget is removed only if the current view is the view of the widget
322
        if ($view === $widget->getView()) {
323
            //we remove the widget
324
            $this->entityManager->remove($widget);
325
        }
326
327
        //we update the view
328
        $this->entityManager->persist($view);
329
        $this->entityManager->flush();
330
331
        return [
332
            'success'     => true,
333
            'widgetId'    => $widgetId,
334
            'viewCssHash' => $view->getCssHash(),
335
        ];
336
    }
337
338
    /**
339
     * Overwrite the widget for the current view because the widget is not linked to the current view, a copy is created.
340
     *
341
     * @param View   $view
342
     * @param Widget $widget
343
     *
344
     * @throws \Exception The slot does not exists
345
     *
346
     * @return Widget The widget
347
     */
348
    public function overwriteWidget(View $view, Widget $widget)
349
    {
350
        $widgetCopy = $this->cloneEntity($widget);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->cloneEntity($widget); of type object|array adds the type array to the return on line 359 which is incompatible with the return type documented by Victoire\Bundle\WidgetBu...anager::overwriteWidget of type Victoire\Bundle\WidgetBundle\Model\Widget.
Loading history...
351
        $widgetCopy->setView($view);
352
353
        //we have to persist the widget to get its id
354
        $this->entityManager->persist($view);
355
        $this->entityManager->flush();
356
357
        $this->widgetMapManager->overwriteWidgetMap($widgetCopy, $widget, $view);
0 ignored issues
show
Documentation introduced by
$widgetCopy is of type object|array, but the function expects a object<Victoire\Bundle\WidgetBundle\Model\Widget>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
358
359
        return $widgetCopy;
360
    }
361
362
    public function cloneEntity($entity)
363
    {
364
        $entityCopy = clone $entity;
365
        //Look for on_to_many relations, if found, duplicate related entities.
366
        //It is necessary for 'list' widgets, this algo duplicates and persists list items.
367
        $associations = $this->entityManager->getClassMetadata(get_class($entity))->getAssociationMappings();
368
        $accessor = PropertyAccess::createPropertyAccessor();
369
        foreach ($associations as $name => $values) {
370
            if ($values['type'] === ClassMetadataInfo::ONE_TO_MANY) {
371
                $relatedEntities = $accessor->getValue($entity, $values['fieldName']);
372
                $relatedEntitiesCopies = [];
373
                foreach ($relatedEntities as $relatedEntity) {
374
                    $relatedEntityCopy = $this->cloneEntity($relatedEntity);
375
                    $relatedEntitiesCopies[] = $relatedEntityCopy;
376
                }
377
                $accessor->setValue($entityCopy, $name, $relatedEntitiesCopies);
378
            }
379
380
            //Clone OneToOne relation objects
381
            if ($values['type'] === ClassMetadataInfo::ONE_TO_ONE) {
382
                $relatedEntity = $accessor->getValue($entity, $values['fieldName']);
383
                if ($relatedEntity) {
384
                    $relatedEntityCopy = $this->cloneEntity($relatedEntity);
385
                    $accessor->setValue($entityCopy, $name, $relatedEntityCopy);
386
                }
387
            }
388
        }
389
390
        $this->entityManager->persist($entityCopy);
0 ignored issues
show
Bug introduced by
It seems like $entityCopy can also be of type array; however, Doctrine\ORM\EntityManager::persist() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
391
392
        return $entityCopy;
393
    }
394
}
395