Completed
Pull Request — master (#325)
by Paul
09:55
created

WidgetManager::editWidget()   C

Complexity

Conditions 7
Paths 13

Size

Total Lines 72
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 72
rs 6.7427
cc 7
eloc 42
nc 13
nop 5

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Victoire\Bundle\WidgetBundle\Model;
4
5
use Victoire\Bundle\WidgetMapBundle\Helper\WidgetMapHelper;
6
use Doctrine\ORM\EntityManager;
7
use Doctrine\ORM\Mapping\ClassMetadataInfo;
8
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
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\CoreBundle\Template\TemplateMapper;
17
use Victoire\Bundle\FormBundle\Helper\FormErrorHelper;
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\Entity\WidgetMap;
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 $victoireTemplating;
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 TemplateMapper            $victoireTemplating
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
        TemplateMapper $victoireTemplating,
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->victoireTemplating = $victoireTemplating;
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 template
103
     */
104
    public function newWidget($mode, $type, $slot, $view, $position, $parentWidgetMap)
105
    {
106
        $widget = $this->widgetHelper->newWidgetInstance($type, $view, $slot, $mode);
107
108
        /** @var BusinessEntity[] $classes */
109
        $classes = $this->cacheReader->getBusinessClassesForWidget($widget);
110
        $forms = $this->widgetFormBuilder->renderNewWidgetForms($slot, $view, $widget, $classes, $position, $parentWidgetMap);
111
112
        return [
113
            'html' => $this->victoireTemplating->render(
114
                'VictoireCoreBundle:Widget:Form/new.html.twig',
115
                [
116
                    'view'    => $view,
117
                    'classes' => $classes,
118
                    'widget'  => $widget,
119
                    'forms'   => $forms,
120
                ]
121
            ),
122
        ];
123
    }
124
125
    /**
126
     * Create a widget.
127
     *
128
     * @param string $mode
129
     * @param string $type
130
     * @param string $slotId
131
     * @param View   $view
132
     * @param string $entity
133
     * @param string $type
134
     *
135
     * @throws \Exception
136
     *
137
     * @return Template
138
     */
139
    public function createWidget($mode, $type, $slotId, View $view, $entity, $position, $widgetReference)
140
    {
141
        //services
142
        $formErrorHelper = $this->formErrorHelper;
143
        $request = $this->request;
144
145
        if ($view instanceof VirtualBusinessPage) {
146
            $this->virtualToBpTransformer->transform($view);
147
        }
148
        //create a new widget
149
        $widget = $this->widgetHelper->newWidgetInstance($type, $view, $slotId, $mode);
150
151
        $form = $this->widgetFormBuilder->callBuildFormSwitchParameters($widget, $view, $entity, $position, $widgetReference, $slotId);
152
153
        $noValidate = $request->query->get('novalidate', false);
154
155
        $form->handleRequest($request);
156
        if ($noValidate === false && $form->isValid()) {
157
            if (!$view->getId()) {
158
                //create a view for the business entity instance if we are currently on a virtual one
159
                $this->entityManager->persist($view);
160
            }
161
162
            //get the widget from the form
163
            $widget = $form->getData();
164
165
            //update fields of the widget
166
            $widget->setBusinessEntityId($entity);
167
168
            //persist the widget
169
            $this->entityManager->persist($widget);
170
            $this->entityManager->flush();
171
172
            $this->widgetMapManager->insert($widget, $view, $slotId, $position, $widgetReference);
173
174
            $this->entityManager->persist($view);
175
            $this->entityManager->flush();
176
177
            $widget->setCurrentView($view);
178
179
            $this->widgetMapBuilder->build($view);
180
181
            //get the html for the widget
182
            $htmlWidget = $this->widgetRenderer->renderContainer($widget, $view);
183
184
            $response = [
185
                'success'  => true,
186
                'widgetId' => $widget->getId(),
187
                'html'     => $htmlWidget,
188
            ];
189
        } else {
190
            //get the errors as a string
191
            $response = [
192
                'success' => false,
193
                '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...
194
                'html'    => $this->widgetFormBuilder->renderNewForm($form, $widget, $slotId, $view, $entity),
195
            ];
196
        }
197
198
        return $response;
199
    }
200
201
    /**
202
     * edit a widget.
203
     *
204
     * @param Request $request
205
     * @param Widget  $widget
206
     * @param View    $currentView
207
     * @param string  $businessEntityId The entity name is used to know which form to submit
208
     *
209
     * @return template
210
     */
211
    public function editWidget(Request $request, Widget $widget, View $currentView, $businessEntityId = null, $widgetMode = Widget::MODE_STATIC)
212
    {
213
        /** @var BusinessEntity[] $classes */
214
        $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...
215
216
        //the id of the edited widget
217
        //a new widget might be created in the case of a legacy
218
        $initialWidgetId = $widget->getId();
219
220
        //the type of method used
221
        $requestMethod = $request->getMethod();
222
223
        //if the form is posted
224
        if ($requestMethod === 'POST') {
225
            //the widget view
226
            $widgetView = WidgetMapHelper::getWidgetMapByWidgetAndView($widget, $currentView)->getView();
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...
227
228
            //we only copy the widget if the view of the widget is not the current view
229
            if ($widgetView !== $currentView) {
230
                $widget = $this->overwriteWidget($currentView, $widget);
231
            }
232
            if ($businessEntityId !== null) {
233
                $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...
234
            } else {
235
                $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...
236
            }
237
238
            $noValidate = $request->query->get('novalidate', false);
239
            $form->handleRequest($request);
240
            if ($noValidate === false && $form->isValid()) {
241
                $widget->setBusinessEntityId($businessEntityId);
242
243
                $this->entityManager->persist($widget);
244
245
                $this->entityManager->persist($currentView);
246
                $this->entityManager->flush();
247
248
                $response = [
249
                    'view'        => $currentView,
250
                    'success'     => true,
251
                    'html'        => $this->widgetRenderer->render($widget, $currentView),
252
                    'widgetId'    => $initialWidgetId,
253
                    'viewCssHash' => $currentView->getCssHash(),
254
                ];
255
            } else {
256
                $formErrorHelper = $this->formErrorHelper;
257
                //Return a message for developer in console and form view in order to refresh view and show form errors
258
                $response = [
259
                    'success' => false,
260
                    'message' => $noValidate === false ? $formErrorHelper->getRecursiveReadableErrors($form) : null,
261
                    '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 211 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...
262
                ];
263
            }
264
        } else {
265
            $forms = $this->widgetFormBuilder->renderNewWidgetForms($widget->getSlot(), $currentView, $widget, $classes);
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...
266
267
            $response = [
268
                'success'  => true,
269
                'html'     => $this->victoireTemplating->render(
270
                    'VictoireCoreBundle:Widget:Form/edit.html.twig',
271
                    [
272
                        'view'    => $currentView,
273
                        'classes' => $classes,
274
                        'forms'   => $forms,
275
                        'widget'  => $widget,
276
                    ]
277
                ),
278
            ];
279
        }
280
281
        return $response;
282
    }
283
284
    /**
285
     * Remove a widget.
286
     *
287
     * @param Widget $widget
288
     *
289
     * @return array The parameter for the view
290
     */
291
    public function deleteWidget(Widget $widget, View $view)
292
    {
293
        //update the view deleting the widget
294
        $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...
295
296
        //we update the widget map of the view
297
        $this->widgetMapBuilder->build($view);
298
        //Used to update view in callback (we do it before delete it else it'll not exists anymore)
299
        $widgetId = $widget->getId();
300
        $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...
301
        //the widget is removed only if the current view is the view of the widget
302
        if (null !== $widgetMap
303
        && $widgetMap->getView() == $view
304
        && $widgetMap->getAction() != WidgetMap::ACTION_DELETE) {
305
            //we remove the widget
306
            $this->entityManager->remove($widget);
307
        }
308
309
        //we update the view
310
        $this->entityManager->persist($view);
311
        $this->entityManager->flush();
312
313
        return [
314
            'success'     => true,
315
            'widgetId'    => $widgetId,
316
            'viewCssHash' => $view->getCssHash(),
317
        ];
318
    }
319
320
    /**
321
     * Overwrite the widget for the current view because the widget is not linked to the current view, a copy is created.
322
     *
323
     * @param View   $view
324
     * @param Widget $widget
325
     *
326
     * @throws \Exception The slot does not exists
327
     *
328
     * @return Widget The widget
329
     */
330
    public function overwriteWidget(View $view, Widget $widget)
331
    {
332
        $widgetCopy = $this->cloneEntity($widget);
333
334
        //we have to persist the widget to get its id
335
        $this->entityManager->persist($view);
336
        $this->entityManager->flush();
337
338
        $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...
339
340
        $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...
341
342
        $this->widgetMapBuilder->build($view);
343
344
        return $widgetCopy;
345
    }
346
347
    /**
348
     * @param Widget $entity
349
     */
350
    public function cloneEntity($entity)
351
    {
352
        $entityCopy = clone $entity;
353
354
        //Look for on_to_many relations, if found, duplicate related entities.
355
        //It is necessary for 'list' widgets, this algo duplicates and persists list items.
356
        $associations = $this->entityManager->getClassMetadata(get_class($entityCopy))->getAssociationMappings();
357
        $accessor = PropertyAccess::createPropertyAccessor();
358
        foreach ($associations as $name => $values) {
359
            if ($values['type'] === ClassMetadataInfo::ONE_TO_MANY && $values['fieldName'] != 'widgetMaps') {
360
                $relatedEntities = $accessor->getValue($entityCopy, $values['fieldName']);
361
                $relatedEntitiesCopies = [];
362
                foreach ($relatedEntities as $relatedEntity) {
363
                    $relatedEntityCopy = clone $relatedEntity;
364
                    $this->entityManager->persist($relatedEntity);
365
                    $relatedEntitiesCopies[] = $relatedEntityCopy;
366
                }
367
                $accessor->setValue($widgetCopy, $name, $relatedEntitiesCopies);
368
            }
369
370
            //Clone OneToOne relation objects
371
            if ($values['type'] === ClassMetadataInfo::ONE_TO_ONE) {
372
                $relatedEntity = $accessor->getValue($entityCopy, $values['fieldName']);
373
                if ($relatedEntity) {
374
                    $relatedEntityCopy = clone $relatedEntity;
375
                    $this->entityManager->persist($relatedEntity);
376
                    $accessor->setValue($widgetCopy, $name, $relatedEntityCopy);
377
                }
378
            }
379
        }
380
381
        $this->entityManager->persist($entityCopy);
382
383
        return $entityCopy;
384
    }
385
}
386