Completed
Pull Request — master (#375)
by Paul
06:22
created

WidgetManager::newWidget()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 27
rs 8.8571
cc 1
eloc 18
nc 1
nop 6
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\Bundle\FrameworkBundle\Templating\EngineInterface;
9
use Symfony\Component\HttpFoundation\Request;
10
use Symfony\Component\PropertyAccess\PropertyAccess;
11
use Victoire\Bundle\BusinessEntityBundle\Entity\BusinessEntity;
12
use Victoire\Bundle\BusinessEntityBundle\Reader\BusinessEntityCacheReader;
13
use Victoire\Bundle\BusinessPageBundle\Entity\VirtualBusinessPage;
14
use Victoire\Bundle\BusinessPageBundle\Transformer\VirtualToBusinessPageTransformer;
15
use Victoire\Bundle\CoreBundle\Entity\View;
16
use Victoire\Bundle\FormBundle\Helper\FormErrorHelper;
17
use Victoire\Bundle\PageBundle\Helper\PageHelper;
18
use Victoire\Bundle\WidgetBundle\Builder\WidgetFormBuilder;
19
use Victoire\Bundle\WidgetBundle\Helper\WidgetHelper;
20
use Victoire\Bundle\WidgetBundle\Renderer\WidgetRenderer;
21
use Victoire\Bundle\WidgetBundle\Resolver\WidgetContentResolver;
22
use Victoire\Bundle\WidgetMapBundle\Builder\WidgetMapBuilder;
23
use Victoire\Bundle\WidgetMapBundle\Entity\WidgetMap;
24
use Victoire\Bundle\WidgetMapBundle\Helper\WidgetMapHelper;
25
use Victoire\Bundle\WidgetMapBundle\Manager\WidgetMapManager;
26
27
/**
28
 * This manager handles crud operations on a Widget.
29
 */
30
class WidgetManager
31
{
32
    protected $widgetFormBuilder;
33
    protected $widgetHelper;
34
    protected $widgetContentResolver;
35
    protected $entityManager;
36
    protected $formErrorHelper; // @victoire_form.error_helper
37
    protected $request; // @request
38
    protected $widgetMapManager;
39
    protected $cacheReader; // @victoire_business_entity.cache_reader
40
    protected $templating;
41
    protected $pageHelper;
42
    protected $slots; // %victoire_core.slots%
43
    protected $virtualToBpTransformer; // %victoire_core.slots%
44
45
    /**
46
     * construct.
47
     *
48
     * @param WidgetHelper              $widgetHelper
49
     * @param WidgetFormBuilder         $widgetFormBuilder
50
     * @param WidgetContentResolver     $widgetContentResolver
51
     * @param WidgetRenderer            $widgetRenderer
52
     * @param EntityManager             $entityManager
53
     * @param FormErrorHelper           $formErrorHelper
54
     * @param Request                   $request
55
     * @param WidgetMapManager          $widgetMapManager
56
     * @param WidgetMapBuilder          $widgetMapBuilder
57
     * @param BusinessEntityCacheReader $cacheReader
58
     * @param EngineInterface           $templating
59
     * @param PageHelper                $pageHelper
60
     * @param array                     $slots
61
     */
62
    public function __construct(
63
        WidgetHelper $widgetHelper,
64
        WidgetFormBuilder $widgetFormBuilder,
65
        WidgetContentResolver $widgetContentResolver,
66
        WidgetRenderer $widgetRenderer,
67
        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...
68
        FormErrorHelper $formErrorHelper,
69
        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...
70
        WidgetMapManager $widgetMapManager,
71
        WidgetMapBuilder $widgetMapBuilder,
72
        BusinessEntityCacheReader $cacheReader,
73
        EngineInterface $templating,
74
        PageHelper $pageHelper,
75
        $slots,
76
        VirtualToBusinessPageTransformer $virtualToBpTransformer
77
    ) {
78
        $this->widgetFormBuilder = $widgetFormBuilder;
79
        $this->widgetHelper = $widgetHelper;
80
        $this->widgetContentResolver = $widgetContentResolver;
81
        $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...
82
        $this->entityManager = $entityManager;
83
        $this->formErrorHelper = $formErrorHelper;
84
        $this->request = $request;
85
        $this->widgetMapManager = $widgetMapManager;
86
        $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...
87
        $this->cacheReader = $cacheReader;
88
        $this->templating = $templating;
89
        $this->pageHelper = $pageHelper;
90
        $this->slots = $slots;
91
        $this->virtualToBpTransformer = $virtualToBpTransformer;
92
    }
93
94
    /**
95
     * new widget.
96
     *
97
     * @param string $type
98
     * @param string $slot
99
     * @param View   $view
100
     * @param int    $position
101
     *
102
     * @return array
103
     */
104
    public function newWidget($mode, $type, $slot, $view, $position, $parentWidgetMap)
105
    {
106
        $widget = $this->widgetHelper->newWidgetInstance($type, $view, $slot, $mode);
107
        $widgets = ['static' => $widget];
108
109
        /** @var BusinessEntity[] $classes */
110
        $classes = $this->cacheReader->getBusinessClassesForWidget($widget);
111
        $forms = $this->widgetFormBuilder->renderNewQuantumForms($slot, $view, $widgets, $widget, $classes, $position, $parentWidgetMap);
112
113
        return [
114
            'widget' => $widget,
115
            'html' => $this->templating->render(
116
                'VictoireCoreBundle:Widget:Form/new.html.twig',
117
                [
118
                    'id'    => time(),
119
                    'view'    => $view,
120
                    'slot'    => $slot,
121
                    'position'    => $position,
122
                    'parentWidgetMap'    => $parentWidgetMap,
123
                    'classes' => $classes,
124
                    'widgets'  => $widgets,
125
                    'widget'  => $widget,
126
                    'forms'   => $forms,
127
                ]
128
            ),
129
        ];
130
    }
131
132
    /**
133
     * Create a widget.
134
     *
135
     * @param string $mode
136
     * @param string $type
137
     * @param string $slotId
138
     * @param View   $view
139
     * @param string $entity
140
     * @param string $type
141
     *
142
     * @throws \Exception
143
     *
144
     * @return Template
145
     */
146
    public function createWidget($mode, $type, $slotId, View $view, $entity, $position, $widgetReference)
147
    {
148
        //services
149
        $formErrorHelper = $this->formErrorHelper;
150
        $request = $this->request;
151
152
        if ($view instanceof VirtualBusinessPage) {
153
            $this->virtualToBpTransformer->transform($view);
154
        }
155
        //create a new widget
156
        $widget = $this->widgetHelper->newWidgetInstance($type, $view, $slotId, $mode);
157
158
        $form = $this->widgetFormBuilder->callBuildFormSwitchParameters($widget, $view, $entity, $position, $widgetReference, $slotId);
159
160
        $noValidate = $request->query->get('novalidate', false);
161
162
        $form->handleRequest($request);
163
        if ($noValidate === false && $form->isValid()) {
164
            if (!$view->getId()) {
165
                //create a view for the business entity instance if we are currently on a virtual one
166
                $this->entityManager->persist($view);
167
            }
168
169
            //get the widget from the form
170
            $widget = $form->getData();
171
172
            //update fields of the widget
173
            $widget->setBusinessEntityId($entity);
174
175
            //persist the widget
176
            $this->entityManager->persist($widget);
177
            $this->entityManager->flush();
178
179
            $this->widgetMapManager->insert($widget, $view, $slotId, $position, $widgetReference);
180
181
            $this->entityManager->persist($view);
182
            $this->entityManager->flush();
183
184
            $widget->setCurrentView($view);
185
186
            $this->widgetMapBuilder->build($view);
187
188
            //get the html for the widget
189
            $htmlWidget = $this->widgetRenderer->renderContainer($widget, $view);
190
191
            $response = [
192
                'success'  => true,
193
                'widgetId' => $widget->getId(),
194
                'html'     => $htmlWidget,
195
            ];
196
        } else {
197
            //get the errors as a string
198
            $response = [
199
                'success' => false,
200
                '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...
201
                'html'    => $this->widgetFormBuilder->renderNewForm($form, $widget, $slotId, $view, $entity),
202
            ];
203
        }
204
205
        return $response;
206
    }
207
208
    /**
209
     * edit a widget.
210
     *
211
     * @param Request $request
212
     * @param Widget  $widget
213
     * @param View    $currentView
214
     * @param string  $businessEntityId The entity name is used to know which form to submit
215
     *
216
     * @return template
217
     */
218
    public function editWidget(Request $request, Widget $widget, View $currentView, $businessEntityId = null, $widgetMode = Widget::MODE_STATIC)
219
    {
220
        /** @var BusinessEntity[] $classes */
221
        $classes = $this->cacheReader->getBusinessClassesForWidget($widget);
0 ignored issues
show
Compatibility introduced by
$widget of type object<Victoire\Bundle\WidgetBundle\Model\Widget> is not a sub-type of object<Victoire\Bundle\W...etBundle\Entity\Widget>. It seems like you assume a child class of the class Victoire\Bundle\WidgetBundle\Model\Widget to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
222
223
        //the id of the edited widget
224
        //a new widget might be created in the case of a legacy
225
        $initialWidgetId = $widget->getId();
226
227
        //the type of method used
228
        $requestMethod = $request->getMethod();
229
230
        //if the form is posted
231
        if ($requestMethod === 'POST') {
232
            //the widget view
233
            $widgetView = $widget->getWidgetMap()->getView();
234
235
            //we only copy the widget if the view of the widget is not the current view
236
            if ($widgetView !== $currentView) {
237
                $widget = $this->overwriteWidget($currentView, $widget);
238
            }
239
            if ($businessEntityId !== null) {
240
                $form = $this->widgetFormBuilder->buildForm($widget, $currentView, $businessEntityId, $classes[$businessEntityId]->getClass(), $widgetMode);
0 ignored issues
show
Compatibility introduced by
$widget of type object<Victoire\Bundle\WidgetBundle\Model\Widget> is not a sub-type of object<Victoire\Bundle\W...etBundle\Entity\Widget>. It seems like you assume a child class of the class Victoire\Bundle\WidgetBundle\Model\Widget to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
241
            } else {
242
                $form = $this->widgetFormBuilder->buildForm($widget, $currentView);
0 ignored issues
show
Compatibility introduced by
$widget of type object<Victoire\Bundle\WidgetBundle\Model\Widget> is not a sub-type of object<Victoire\Bundle\W...etBundle\Entity\Widget>. It seems like you assume a child class of the class Victoire\Bundle\WidgetBundle\Model\Widget to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
243
            }
244
245
            $noValidate = $request->query->get('novalidate', false);
246
            $form->handleRequest($request);
247
            if ($noValidate === false && $form->isValid()) {
248
                $widget->setBusinessEntityId($businessEntityId);
249
250
                $this->entityManager->persist($widget);
251
252
                $this->entityManager->persist($currentView);
253
                $this->entityManager->flush();
254
255
                $response = [
256
                    'view'        => $currentView,
257
                    'success'     => true,
258
                    'html'        => $this->widgetRenderer->render($widget, $currentView),
259
                    'widgetId'    => $initialWidgetId,
260
                    'viewCssHash' => $currentView->getCssHash(),
261
                ];
262
            } else {
263
                $formErrorHelper = $this->formErrorHelper;
264
                //Return a message for developer in console and form view in order to refresh view and show form errors
265
                $response = [
266
                    'success' => false,
267
                    'widgetId'    => $initialWidgetId,
268
                    'message' => $noValidate === false ? $formErrorHelper->getRecursiveReadableErrors($form) : null,
269
                    'html'    => $this->widgetFormBuilder->renderForm($form, $widget, $businessEntityId),
0 ignored issues
show
Compatibility introduced by
$widget of type object<Victoire\Bundle\WidgetBundle\Model\Widget> is not a sub-type of object<Victoire\Bundle\W...etBundle\Entity\Widget>. It seems like you assume a child class of the class Victoire\Bundle\WidgetBundle\Model\Widget to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Bug introduced by
It seems like $businessEntityId defined by parameter $businessEntityId on line 218 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...
270
                ];
271
            }
272
        } else {
273
            $widgets = $widget->getWidgetMap()->getWidgets();
274
            $forms = $this->widgetFormBuilder->renderNewQuantumForms($widget->getSlot(), $currentView, $widgets, $widget, $classes);
275
276
            $response = [
277
                'success'  => true,
278
                'html'     => $this->templating->render(
279
                    'VictoireCoreBundle:Widget:Form/edit.html.twig',
280
                    [
281
                        'view'    => $currentView,
282
                        'slot'    => $widget->getWidgetMap()->getSlot(),
283
                        'position'    => $widget->getWidgetMap()->getPosition(),
284
                        'parentWidgetMap'    => $widget->getWidgetMap()->getParent() ? $widget->getWidgetMap()->getParent()->getId() : null,
285
                        'classes' => $classes,
286
                        'forms'   => $forms,
287
                        'widgets'  => $widgets,
288
                        'widget'  => $widget,
289
                    ]
290
                ),
291
            ];
292
        }
293
294
        return $response;
295
    }
296
297
    /**
298
     * Remove a widget.
299
     *
300
     * @param Widget $widget
301
     *
302
     * @return array The parameter for the view
303
     */
304
    public function deleteWidget(Widget $widget, View $view)
305
    {
306
        //Used to update view in callback (we do it before delete it else it'll not exists anymore)
307
        $widgetId = $widget->getId();
308
        //we update the widget map of the view
309
        $this->widgetMapBuilder->build($view);
310
        $widgetMap = WidgetMapHelper::getWidgetMapByWidgetAndView($widget, $view);
0 ignored issues
show
Compatibility introduced by
$widget of type object<Victoire\Bundle\WidgetBundle\Model\Widget> is not a sub-type of object<Victoire\Bundle\W...etBundle\Entity\Widget>. It seems like you assume a child class of the class Victoire\Bundle\WidgetBundle\Model\Widget to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
311
        //the widget is removed only if the current view is the view of the widget
312
        if ($widgetMap->getView() == $view && $widgetMap->getAction() != WidgetMap::ACTION_DELETE) {
313
            //we remove the widget
314
            $this->entityManager->remove($widget);
315
        }
316
317
        //we update the view
318
        $this->entityManager->persist($view);
319
        //update the view deleting the widget
320
        $this->widgetMapManager->delete($view, $widget);
0 ignored issues
show
Compatibility introduced by
$widget of type object<Victoire\Bundle\WidgetBundle\Model\Widget> is not a sub-type of object<Victoire\Bundle\W...etBundle\Entity\Widget>. It seems like you assume a child class of the class Victoire\Bundle\WidgetBundle\Model\Widget to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
321
322
        $this->entityManager->flush();
323
324
        return [
325
            'success'     => true,
326
            'widgetId'    => $widgetId,
327
            'viewCssHash' => $view->getCssHash(),
328
        ];
329
    }
330
331
    /**
332
     * Overwrite the widget for the current view because the widget is not linked to the current view, a copy is created.
333
     *
334
     * @param View   $view
335
     * @param Widget $widget
336
     *
337
     * @throws \Exception The slot does not exists
338
     *
339
     * @return Widget The widget
340
     */
341
    public function overwriteWidget(View $view, Widget $widget)
342
    {
343
        $widgetCopy = $this->cloneEntity($widget);
344
345
        //we have to persist the widget to get its id
346
        $this->entityManager->persist($view);
347
        $this->entityManager->flush();
348
349
        $originalWidgetMap = WidgetMapHelper::getWidgetMapByWidgetAndView($widget, $view);
0 ignored issues
show
Compatibility introduced by
$widget of type object<Victoire\Bundle\WidgetBundle\Model\Widget> is not a sub-type of object<Victoire\Bundle\W...etBundle\Entity\Widget>. It seems like you assume a child class of the class Victoire\Bundle\WidgetBundle\Model\Widget to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
350
351
        $this->widgetMapManager->overwrite($view, $originalWidgetMap, $widgetCopy);
0 ignored issues
show
Compatibility introduced by
$widgetCopy of type object<Victoire\Bundle\WidgetBundle\Model\Widget> is not a sub-type of object<Victoire\Bundle\W...etBundle\Entity\Widget>. It seems like you assume a child class of the class Victoire\Bundle\WidgetBundle\Model\Widget to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
352
353
        $this->widgetMapBuilder->build($view);
354
355
        return $widgetCopy;
356
    }
357
358
    /**
359
     * @param Widget $entity
360
     */
361
    public function cloneEntity($entity)
362
    {
363
        $entityCopy = clone $entity;
364
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($entityCopy))->getAssociationMappings();
368
        $accessor = PropertyAccess::createPropertyAccessor();
369
        foreach ($associations as $name => $values) {
370
            if ($values['type'] === ClassMetadataInfo::ONE_TO_MANY && $values['fieldName'] != 'widgetMaps') {
371
                $relatedEntities = $accessor->getValue($entityCopy, $values['fieldName']);
372
                $relatedEntitiesCopies = [];
373
                foreach ($relatedEntities as $relatedEntity) {
374
                    $relatedEntityCopy = clone $relatedEntity;
375
                    $this->entityManager->persist($relatedEntity);
376
                    $relatedEntitiesCopies[] = $relatedEntityCopy;
377
                }
378
                $accessor->setValue($widgetCopy, $name, $relatedEntitiesCopies);
379
            }
380
381
            //Clone OneToOne relation objects
382
            if ($values['type'] === ClassMetadataInfo::ONE_TO_ONE) {
383
                $relatedEntity = $accessor->getValue($entityCopy, $values['fieldName']);
384
                if ($relatedEntity) {
385
                    $relatedEntityCopy = clone $relatedEntity;
386
                    $this->entityManager->persist($relatedEntity);
387
                    $accessor->setValue($widgetCopy, $name, $relatedEntityCopy);
388
                }
389
            }
390
        }
391
392
        $this->entityManager->persist($entityCopy);
393
394
        return $entityCopy;
395
    }
396
}
397