Completed
Pull Request — master (#325)
by Paul
08:40
created

WidgetManager::cloneEntity()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 35
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 2 Features 0
Metric Value
c 6
b 2
f 0
dl 0
loc 35
rs 6.7272
cc 7
eloc 21
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\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\Manager\WidgetMapManager;
25
26
/**
27
 * This manager handles crud operations on a Widget.
28
 */
29
class WidgetManager
30
{
31
    protected $widgetFormBuilder;
32
    protected $widgetHelper;
33
    protected $widgetContentResolver;
34
    protected $entityManager;
35
    protected $formErrorHelper; // @victoire_form.error_helper
36
    protected $request; // @request
37
    protected $widgetMapManager;
38
    protected $cacheReader; // @victoire_business_entity.cache_reader
39
    protected $victoireTemplating;
40
    protected $pageHelper;
41
    protected $slots; // %victoire_core.slots%
42
    protected $virtualToBpTransformer; // %victoire_core.slots%
43
44
    /**
45
     * construct.
46
     *
47
     * @param WidgetHelper              $widgetHelper
48
     * @param WidgetFormBuilder         $widgetFormBuilder
49
     * @param WidgetContentResolver     $widgetContentResolver
50
     * @param WidgetRenderer            $widgetRenderer
51
     * @param EntityManager             $entityManager
52
     * @param FormErrorHelper           $formErrorHelper
53
     * @param Request                   $request
54
     * @param WidgetMapManager          $widgetMapManager
55
     * @param WidgetMapBuilder          $widgetMapBuilder
56
     * @param BusinessEntityCacheReader $cacheReader
57
     * @param TemplateMapper            $victoireTemplating
58
     * @param PageHelper                $pageHelper
59
     * @param array                     $slots
60
     */
61
    public function __construct(
62
        WidgetHelper $widgetHelper,
63
        WidgetFormBuilder $widgetFormBuilder,
64
        WidgetContentResolver $widgetContentResolver,
65
        WidgetRenderer $widgetRenderer,
66
        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...
67
        FormErrorHelper $formErrorHelper,
68
        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...
69
        WidgetMapManager $widgetMapManager,
70
        WidgetMapBuilder $widgetMapBuilder,
71
        BusinessEntityCacheReader $cacheReader,
72
        TemplateMapper $victoireTemplating,
73
        PageHelper $pageHelper,
74
        $slots,
75
        VirtualToBusinessPageTransformer $virtualToBpTransformer
76
    ) {
77
        $this->widgetFormBuilder = $widgetFormBuilder;
78
        $this->widgetHelper = $widgetHelper;
79
        $this->widgetContentResolver = $widgetContentResolver;
80
        $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...
81
        $this->entityManager = $entityManager;
82
        $this->formErrorHelper = $formErrorHelper;
83
        $this->request = $request;
84
        $this->widgetMapManager = $widgetMapManager;
85
        $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...
86
        $this->cacheReader = $cacheReader;
87
        $this->victoireTemplating = $victoireTemplating;
88
        $this->pageHelper = $pageHelper;
89
        $this->slots = $slots;
90
        $this->virtualToBpTransformer = $virtualToBpTransformer;
91
    }
92
93
    /**
94
     * Create a widget.
95
     *
96
     * @param string $mode
97
     * @param string $type
98
     * @param string $slotId
99
     * @param View   $view
100
     * @param string $entity
101
     * @param string $type
102
     *
103
     * @throws \Exception
104
     *
105
     * @return Template
106
     */
107
    public function createWidget($mode, $type, $slotId, View $view, $entity, $position, $widgetReference)
108
    {
109
        //services
110
        $formErrorHelper = $this->formErrorHelper;
111
        $request = $this->request;
112
113
        if ($view instanceof VirtualBusinessPage) {
114
            $this->virtualToBpTransformer->transform($view);
115
        }
116
        //create a new widget
117
        $widget = $this->widgetHelper->newWidgetInstance($type, $view, $slotId, $mode);
118
119
        $form = $this->widgetFormBuilder->callBuildFormSwitchParameters($widget, $view, $entity, $position, $widgetReference, $slotId);
120
121
        $noValidate = $request->query->get('novalidate', false);
122
123
        $form->handleRequest($request);
124
        if ($noValidate === false && $form->isValid()) {
125
            if (!$view->getId()) {
126
                //create a view for the business entity instance if we are currently on a virtual one
127
                $this->entityManager->persist($view);
128
            }
129
130
            //get the widget from the form
131
            $widget = $form->getData();
132
133
            //update fields of the widget
134
            $widget->setBusinessEntityId($entity);
135
136
            //persist the widget
137
            $this->entityManager->persist($widget);
138
            $this->entityManager->flush();
139
140
            $this->widgetMapManager->insert($widget, $view, $slotId, $position, $widgetReference);
141
142
            $this->entityManager->persist($view);
143
            $this->entityManager->flush();
144
145
            $widget->setCurrentView($view);
146
147
            $this->widgetMapBuilder->build($view);
148
149
            //get the html for the widget
150
            $htmlWidget = $this->widgetRenderer->renderContainer($widget, $view);
151
152
            $response = [
153
                'success'  => true,
154
                'widgetId' => $widget->getId(),
155
                'html'     => $htmlWidget,
156
            ];
157
        } else {
158
            //get the errors as a string
159
            $response = [
160
                'success' => false,
161
                '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...
162
                'html'    => $this->widgetFormBuilder->renderNewForm($form, $widget, $slotId, $view, $entity),
163
            ];
164
        }
165
166
        return $response;
167
    }
168
169
    /**
170
     * edit a widget.
171
     *
172
     * @param Request $request
173
     * @param Widget  $widget
174
     * @param View    $currentView
175
     * @param string  $businessEntityId The entity name is used to know which form to submit
176
     *
177
     * @return template
178
     */
179
    public function editWidget(Request $request, Widget $widget, View $currentView, $businessEntityId = null, $widgetMode = Widget::MODE_STATIC)
180
    {
181
        /** @var BusinessEntity[] $classes */
182
        $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...
183
184
        //the id of the edited widget
185
        //a new widget might be created in the case of a legacy
186
        $initialWidgetId = $widget->getId();
187
188
        //the type of method used
189
        $requestMethod = $request->getMethod();
190
191
        //if the form is posted
192
        if ($requestMethod === 'POST') {
193
            //the widget view
194
            $widgetView = $currentView->getWidgetMapByWidget($widget)->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...
195
196
            //we only copy the widget if the view of the widget is not the current view
197
            if ($widgetView !== $currentView) {
198
                $widget = $this->overwriteWidget($currentView, $widget);
199
            }
200
            if ($businessEntityId !== null) {
201
                $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...
202
            } else {
203
                $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...
204
            }
205
206
            $noValidate = $request->query->get('novalidate', false);
207
            $form->handleRequest($request);
208
            if ($noValidate === false && $form->isValid()) {
209
                $widget->setBusinessEntityId($businessEntityId);
210
211
                $this->entityManager->persist($widget);
212
213
                $this->entityManager->persist($currentView);
214
                $this->entityManager->flush();
215
216
                $response = [
217
                    'view'        => $currentView,
218
                    'success'     => true,
219
                    'html'        => $this->widgetRenderer->render($widget, $currentView),
220
                    'widgetId'    => $initialWidgetId,
221
                    'viewCssHash' => $currentView->getCssHash(),
222
                ];
223
            } else {
224
                $formErrorHelper = $this->formErrorHelper;
225
                //Return a message for developer in console and form view in order to refresh view and show form errors
226
                $response = [
227
                    'success' => false,
228
                    'message' => $noValidate === false ? $formErrorHelper->getRecursiveReadableErrors($form) : null,
229
                    '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 179 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...
230
                ];
231
            }
232
        } else {
233
            $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...
234
235
            $response = [
236
                'success'  => true,
237
                'html'     => $this->victoireTemplating->render(
238
                    'VictoireCoreBundle:Widget:Form/edit.html.twig',
239
                    [
240
                        'view'    => $currentView,
241
                        'classes' => $classes,
242
                        'forms'   => $forms,
243
                        'widget'  => $widget,
244
                    ]
245
                ),
246
            ];
247
        }
248
249
        return $response;
250
    }
251
252
    /**
253
     * Remove a widget.
254
     *
255
     * @param Widget $widget
256
     *
257
     * @return array The parameter for the view
258
     */
259
    public function deleteWidget(Widget $widget, View $view)
260
    {
261
        //update the view deleting the widget
262
        $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...
263
264
        //we update the widget map of the view
265
        $this->widgetMapBuilder->build($view);
266
        //Used to update view in callback (we do it before delete it else it'll not exists anymore)
267
        $widgetId = $widget->getId();
268
        $widgetMap = $view->getWidgetMapByWidget($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...
269
        //the widget is removed only if the current view is the view of the widget
270
        if (null !== $widgetMap
271
        && $widgetMap->getView() == $view
272
        && $widgetMap->getAction() != WidgetMap::ACTION_DELETE) {
273
            //we remove the widget
274
            $this->entityManager->remove($widget);
275
        }
276
277
        //we update the view
278
        $this->entityManager->persist($view);
279
        $this->entityManager->flush();
280
281
        return [
282
            'success'     => true,
283
            'widgetId'    => $widgetId,
284
            'viewCssHash' => $view->getCssHash(),
285
        ];
286
    }
287
288
    /**
289
     * Overwrite the widget for the current view because the widget is not linked to the current view, a copy is created.
290
     *
291
     * @param View   $view
292
     * @param Widget $widget
293
     *
294
     * @throws \Exception The slot does not exists
295
     *
296
     * @return Widget The widget
297
     */
298
    public function overwriteWidget(View $view, Widget $widget)
299
    {
300
        $widgetCopy = $this->cloneEntity($widget);
301
302
        //we have to persist the widget to get its id
303
        $this->entityManager->persist($view);
304
        $this->entityManager->flush();
305
306
        $originalWidgetMap = $view->getWidgetMapByWidget($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...
307
308
        $this->widgetMapManager->overwrite($view, $originalWidgetMap, $widgetCopy);
0 ignored issues
show
Bug introduced by
It seems like $originalWidgetMap defined by $view->getWidgetMapByWidget($widget) on line 306 can be null; however, Victoire\Bundle\WidgetMa...MapManager::overwrite() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
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...
309
310
        $this->widgetMapBuilder->build($view);
311
312
        return $widgetCopy;
313
    }
314
315
    /**
316
     * @param Widget $entity
317
     */
318
    public function cloneEntity($entity)
319
    {
320
        $entityCopy = clone $entity;
321
322
        //Look for on_to_many relations, if found, duplicate related entities.
323
        //It is necessary for 'list' widgets, this algo duplicates and persists list items.
324
        $associations = $this->entityManager->getClassMetadata(get_class($widget))->getAssociationMappings();
0 ignored issues
show
Bug introduced by
The variable $widget does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
325
        $accessor = PropertyAccess::createPropertyAccessor();
326
        foreach ($associations as $name => $values) {
327
            if ($values['type'] === ClassMetadataInfo::ONE_TO_MANY && $values['fieldName'] != 'widgetMaps') {
328
                $relatedEntities = $accessor->getValue($widget, $values['fieldName']);
329
                $relatedEntitiesCopies = [];
330
                foreach ($relatedEntities as $relatedEntity) {
331
                    $relatedEntityCopy = clone $relatedEntity;
332
                    $this->entityManager->persist($relatedEntity);
333
                    $relatedEntitiesCopies[] = $relatedEntityCopy;
334
                }
335
                $accessor->setValue($widgetCopy, $name, $relatedEntitiesCopies);
336
            }
337
338
            //Clone OneToOne relation objects
339
            if ($values['type'] === ClassMetadataInfo::ONE_TO_ONE) {
340
                $relatedEntity = $accessor->getValue($widget, $values['fieldName']);
341
                if ($relatedEntity) {
342
                    $relatedEntityCopy = clone $relatedEntity;
343
                    $this->entityManager->persist($relatedEntity);
344
                    $accessor->setValue($widgetCopy, $name, $relatedEntityCopy);
345
                }
346
            }
347
        }
348
349
        $this->entityManager->persist($entityCopy);
350
351
        return $entityCopy;
352
    }
353
}
354