PagePartAdmin::preBindRequest()   F
last analyzed

Complexity

Conditions 16
Paths 660

Size

Total Lines 74

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 74
rs 1.8722
c 0
b 0
f 0
cc 16
nc 660
nop 1

How to fix   Long Method    Complexity   

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 Kunstmaan\PagePartBundle\PagePartAdmin;
4
5
use Doctrine\ORM\EntityManager;
6
use Doctrine\ORM\EntityManagerInterface;
7
use Kunstmaan\AdminBundle\Entity\EntityInterface;
8
use Kunstmaan\PagePartBundle\Entity\PagePartRef;
9
use Kunstmaan\PagePartBundle\Event\Events;
10
use Kunstmaan\PagePartBundle\Event\PagePartEvent;
11
use Kunstmaan\PagePartBundle\Helper\HasPagePartsInterface;
12
use Kunstmaan\PagePartBundle\Helper\PagePartInterface;
13
use Kunstmaan\PagePartBundle\Repository\PagePartRefRepository;
14
use Kunstmaan\UtilitiesBundle\Helper\ClassLookup;
15
use Symfony\Component\DependencyInjection\ContainerInterface;
16
use Symfony\Component\Form\FormBuilderInterface;
17
use Symfony\Component\HttpFoundation\Request;
18
19
/**
20
 * PagePartAdmin
21
 */
22
class PagePartAdmin
23
{
24
    /**
25
     * @var PagePartAdminConfiguratorInterface
26
     */
27
    protected $configurator;
28
29
    /**
30
     * @var EntityManager|EntityManagerInterface
31
     */
32
    protected $em;
33
34
    /**
35
     * @var HasPagePartsInterface
36
     */
37
    protected $page;
38
39
    /**
40
     * @var string
41
     */
42
    protected $context;
43
44
    /**
45
     * @var ContainerInterface
46
     */
47
    protected $container;
48
49
    /**
50
     * @var PagePartInterface[]
51
     */
52
    protected $pageParts = array();
53
54
    /**
55
     * @var PagePartRef[]
56
     */
57
    protected $pagePartRefs = array();
58
59
    /**
60
     * @var PagePartInterface[]
61
     */
62
    protected $newPageParts = array();
63
64
    /**
65
     * @param PagePartAdminConfiguratorInterface $configurator The configurator
66
     * @param EntityManagerInterface             $em           The entity manager
67
     * @param HasPagePartsInterface              $page         The page
68
     * @param null|string                        $context      The context
69
     * @param null|ContainerInterface            $container    The container
70
     *
71
     * @throws \InvalidArgumentException
72
     */
73
    public function __construct(PagePartAdminConfiguratorInterface $configurator, EntityManagerInterface $em, HasPagePartsInterface $page, $context = null, ContainerInterface $container = null)
74
    {
75
        if (!($page instanceof EntityInterface)) {
76
            throw new \InvalidArgumentException('Page must be an instance of EntityInterface.');
77
        }
78
79
        $this->configurator = $configurator;
80
        $this->em = $em;
81
        $this->page = $page;
82
        $this->container = $container;
83
84
        if ($context) {
0 ignored issues
show
Bug Best Practice introduced by Roderik van der Veer
The expression $context of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
85
            $this->context = $context;
86
        } else {
87
            if ($this->configurator->getContext()) {
88
                $this->context = $this->configurator->getContext();
89
            } else {
90
                $this->context = 'main';
91
            }
92
        }
93
94
        $this->initializePageParts();
95
    }
96
97
    /**
98
     * Get all pageparts from the database, and store them.
99
     */
100
    private function initializePageParts()
101
    {
102
        // Get all the pagepartrefs
103
        /** @var PagePartRefRepository $ppRefRepo */
104
        $ppRefRepo = $this->em->getRepository('KunstmaanPagePartBundle:PagePartRef');
105
        $ppRefs = $ppRefRepo->getPagePartRefs($this->page, $this->context);
106
107
        // Group pagepartrefs per type
108
        $types = array();
109
        foreach ($ppRefs as $pagePartRef) {
110
            $types[$pagePartRef->getPagePartEntityname()][] = $pagePartRef->getPagePartId();
111
            $this->pagePartRefs[$pagePartRef->getId()] = $pagePartRef;
112
        }
113
114
        // Fetch all the pageparts (only one query per pagepart type)
115
        /** @var EntityInterface[] $pageParts */
116
        $pageParts = array();
117 View Code Duplication
        foreach ($types as $classname => $ids) {
0 ignored issues
show
Duplication introduced by Kristof Jochmans
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
118
            $result = $this->em->getRepository($classname)->findBy(array('id' => $ids));
119
            $pageParts = array_merge($pageParts, $result);
120
        }
121
122
        // Link the pagepartref to the pagepart
123
        foreach ($this->pagePartRefs as $pagePartRef) {
124
            foreach ($pageParts as $key => $pagePart) {
125
                if (ClassLookup::getClass($pagePart) == $pagePartRef->getPagePartEntityname()
126
                    && $pagePart->getId() == $pagePartRef->getPagePartId()
127
                ) {
128
                    $this->pageParts[$pagePartRef->getId()] = $pagePart;
129
                    unset($pageParts[$key]);
130
131
                    break;
132
                }
133
            }
134
        }
135
    }
136
137
    /**
138
     * @return EntityInterface
0 ignored issues
show
Documentation introduced by Maciej Łebkowski
Should the return type not be HasPagePartsInterface?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
139
     */
140
    public function getPage()
141
    {
142
        return $this->page;
143
    }
144
145
    /**
146
     * @param Request $request
147
     */
148
    public function preBindRequest(Request $request)
149
    {
150
        // Fetch all sub-entities that should be removed
151
        $subPagePartsToDelete = array();
152
        foreach (array_keys($request->request->all()) as $key) {
153
            // Example value: delete_pagepartadmin_74_tags_3
154
            if (preg_match('/^delete_pagepartadmin_(\\d+)_(\\w+)_(\\d+)$/i', $key, $matches)) {
155
                $subPagePartsToDelete[$matches[1]][] = array('name' => $matches[2], 'id' => $matches[3]);
156
            }
157
        }
158
159
        $doFlush = false;
160
        foreach ($this->pagePartRefs as $pagePartRef) {
161
            // Remove pageparts
162
            if ('true' == $request->get($pagePartRef->getId().'_deleted')) {
163
                $pagePart = $this->pageParts[$pagePartRef->getId()];
164
                $this->em->remove($pagePart);
165
                $this->em->remove($pagePartRef);
166
167
                unset($this->pageParts[$pagePartRef->getId()]);
168
                unset($this->pagePartRefs[$pagePartRef->getId()]);
169
                $doFlush = true;
170
            }
171
172
            // Remove sub-entities from pageparts
173
            if (array_key_exists($pagePartRef->getId(), $subPagePartsToDelete)) {
174
                $pagePart = $this->pageParts[$pagePartRef->getId()];
175
                foreach ($subPagePartsToDelete[$pagePartRef->getId()] as $deleteInfo) {
176
                    /** @var EntityInterface[] $objects */
177
                    $objects = call_user_func(array($pagePart, 'get'.ucfirst($deleteInfo['name'])));
178
179
                    foreach ($objects as $object) {
180
                        if ($object->getId() == $deleteInfo['id']) {
181
                            $this->em->remove($object);
182
                            $doFlush = true;
183
                        }
184
                    }
185
                }
186
            }
187
        }
188
189
        if ($doFlush) {
190
            $this->em->flush();
191
        }
192
193
        // Create the objects for the new pageparts
194
        $this->newPageParts = array();
195
        $newRefIds = $request->get($this->context.'_new');
196
197
        if (is_array($newRefIds)) {
198
            foreach ($newRefIds as $newId) {
199
                $type = $request->get($this->context.'_type_'.$newId);
200
                $this->newPageParts[$newId] = new $type();
201
            }
202
        }
203
204
        // Sort pageparts again
205
        $sequences = $request->get($this->context.'_sequence');
206
        if (!is_null($sequences)) {
207
            $tempPageparts = $this->pageParts;
208
            $this->pageParts = array();
209
            foreach ($sequences as $sequence) {
0 ignored issues
show
Bug introduced by Kristof Jochmans
The expression $sequences of type object|integer|double|string|array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
210
                if (array_key_exists($sequence, $this->newPageParts)) {
211
                    $this->pageParts[$sequence] = $this->newPageParts[$sequence];
212
                } elseif (array_key_exists($sequence, $tempPageparts)) {
213
                    $this->pageParts[$sequence] = $tempPageparts[$sequence];
214
                } else {
215
                    $this->pageParts[$sequence] = $this->getPagePart($sequence, array_search($sequence, $sequences) + 1);
216
                }
217
            }
218
219
            unset($tempPageparts);
220
        }
221
    }
222
223
    /**
224
     * @param Request $request
225
     */
226
    public function bindRequest(Request $request)
227
    {
228
    }
229
230
    /**
231
     * @param FormBuilderInterface $formbuilder
232
     */
233
    public function adaptForm(FormBuilderInterface $formbuilder)
234
    {
235
        $data = $formbuilder->getData();
236
237
        foreach ($this->pageParts as $pagePartRefId => $pagePart) {
238
            $data['pagepartadmin_' . $pagePartRefId] = $pagePart;
239
            $formbuilder->add('pagepartadmin_' . $pagePartRefId, $pagePart->getDefaultAdminType());
240
        }
241
242
        foreach ($this->newPageParts as $newPagePartRefId => $newPagePart) {
243
            $data['pagepartadmin_' . $newPagePartRefId] = $newPagePart;
244
            $formbuilder->add('pagepartadmin_' . $newPagePartRefId, $newPagePart->getDefaultAdminType());
245
        }
246
247
        $formbuilder->setData($data);
248
    }
249
250
    /**
251
     * @param Request $request
252
     */
253
    public function persist(Request $request)
254
    {
255
        /** @var PagePartRefRepository $ppRefRepo */
256
        $ppRefRepo = $this->em->getRepository('KunstmaanPagePartBundle:PagePartRef');
257
258
        // Add new pageparts on the correct position + Re-order and save pageparts if needed
259
        $sequences = $request->get($this->context.'_sequence', []);
260
        $sequencescount = count($sequences);
261
        for ($i = 0; $i < $sequencescount; ++$i) {
262
            $pagePartRefId = $sequences[$i];
263
264
            if (array_key_exists($pagePartRefId, $this->newPageParts)) {
265
                $pagePart = $this->newPageParts[$pagePartRefId];
266
                $this->em->persist($pagePart);
267
                $this->em->flush($pagePart);
0 ignored issues
show
Unused Code introduced by Sander Goossens
The call to EntityManagerInterface::flush() has too many arguments starting with $pagePart.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
268
269
                $ppRefRepo->addPagePart($this->page, $pagePart, ($i + 1), $this->context, false);
270
            } elseif (array_key_exists($pagePartRefId, $this->pagePartRefs)) {
271
                $pagePartRef = $this->pagePartRefs[$pagePartRefId];
272
                if ($pagePartRef instanceof PagePartRef && $pagePartRef->getSequencenumber() != ($i + 1)) {
273
                    $pagePartRef->setSequencenumber($i + 1);
274
                    $pagePartRef->setContext($this->context);
275
                    $this->em->persist($pagePartRef);
276
                }
277
                $pagePart = $pagePartRef->getPagePart($this->em);
0 ignored issues
show
Bug introduced by Sander Goossens
It seems like $this->em can also be of type object<Doctrine\ORM\EntityManagerInterface>; however, Kunstmaan\PagePartBundle...ePartRef::getPagePart() does only seem to accept object<Doctrine\ORM\EntityManager>, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
278
            }
279
280
            if (isset($pagePart)) {
281
                $this->container->get('event_dispatcher')->dispatch(
282
                    Events::POST_PERSIST,
283
                    new PagePartEvent($pagePart)
284
                );
285
            }
286
        }
287
    }
288
289
    /**
290
     * @return null|string
291
     */
292
    public function getContext()
293
    {
294
        return $this->context;
295
    }
296
297
    /**
298
     * This getter returns an array holding info on page part types that can be added to the page.
299
     * The types are filtererd here, based on the amount of page parts of a certain type that can be added to the page.
300
     *
301
     * @return array
302
     */
303
    public function getPossiblePagePartTypes()
304
    {
305
        $possiblePPTypes = $this->configurator->getPossiblePagePartTypes();
306
        $result = array();
307
308
        // filter page part types that can only be added x times to the page context.
309
        // to achieve this, provide a 'pagelimit' parameter when adding the pp type in your PagePartAdminConfiguration
310
        if (!empty($possiblePPTypes)) {
311
            foreach ($possiblePPTypes as $possibleTypeData) {
312
                if (array_key_exists('pagelimit', $possibleTypeData)) {
313
                    $pageLimit = $possibleTypeData['pagelimit'];
314
                    /** @var PagePartRefRepository $entityRepository */
315
                    $entityRepository = $this->em->getRepository('KunstmaanPagePartBundle:PagePartRef');
316
                    $formPPCount = $entityRepository->countPagePartsOfType(
317
                        $this->page,
318
                        $possibleTypeData['class'],
319
                        $this->configurator->getContext()
320
                    );
321
                    if ($formPPCount < $pageLimit) {
322
                        $result[] = $possibleTypeData;
323
                    }
324
                } else {
325
                    $result[] = $possibleTypeData;
326
                }
327
            }
328
        }
329
330
        return $result;
331
    }
332
333
    /**
334
     * @return string
335
     */
336
    public function getName()
337
    {
338
        return $this->configurator->getName();
339
    }
340
341
    /**
342
     * @return array
0 ignored issues
show
Documentation introduced by Kris Pypen
Consider making the return type a bit more specific; maybe use PagePartInterface[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
343
     */
344
    public function getPagePartMap()
345
    {
346
        return $this->pageParts;
347
    }
348
349
    /**
350
     * @param PagePartInterface $pagepart
351
     *
352
     * @return string
353
     */
354
    public function getType(PagePartInterface $pagepart)
355
    {
356
        $possiblePagePartTypes = $this->configurator->getPossiblePagePartTypes();
357
        foreach ($possiblePagePartTypes as &$pageparttype) {
358
            if ($pageparttype['class'] == ClassLookup::getClass($pagepart)) {
359
                return $pageparttype['name'];
360
            }
361
        }
362
363
        return 'no name';
364
    }
365
366
    /**
367
     * @param int $id
368
     * @param int $sequenceNumber
369
     *
370
     * @return PagePartInterface
0 ignored issues
show
Documentation introduced by Maciej Łebkowski
Should the return type not be PagePartInterface|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
371
     */
372
    public function getPagePart($id, $sequenceNumber)
373
    {
374
        /** @var PagePartRefRepository $ppRefRepo */
375
        $ppRefRepo = $this->em->getRepository('KunstmaanPagePartBundle:PagePartRef');
376
377
        return $ppRefRepo->getPagePart($id, $this->context, $sequenceNumber);
378
    }
379
380
    /**
381
     * @param object $pagepart
382
     *
383
     * @return string
384
     */
385
    public function getClassName($pagepart)
386
    {
387
        return get_class($pagepart);
388
    }
389
}
390