Completed
Pull Request — master (#325)
by Paul
08:12 queued 49s
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 int    $positionReference
0 ignored issues
show
Documentation introduced by
There is no parameter named $positionReference. Did you maybe mean $position?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

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