Completed
Push — master ( 1dbefb...c77374 )
by Jeroen
32s queued 11s
created

PagePartAdmin   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 380
Duplicated Lines 3.95 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 54
lcom 1
cbo 12
dl 15
loc 380
ccs 0
cts 211
cp 0
rs 6.4799
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 21 4
B initializePageParts() 4 36 7
A getPage() 0 4 1
F preBindRequest() 0 73 16
A bindRequest() 0 3 1
A adaptForm() 0 16 3
B persist() 0 32 7
A getContext() 0 4 1
A getPossiblePagePartTypes() 0 29 5
A getName() 0 4 1
A getPagePartMap() 0 4 1
A getType() 0 11 3
A getPagePart() 0 7 1
A getClassName() 0 4 1
A dispatch() 11 11 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like PagePartAdmin often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PagePartAdmin, and based on these observations, apply Extract Interface, too.

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\EventDispatcher\LegacyEventDispatcherProxy;
17
use Symfony\Component\Form\FormBuilderInterface;
18
use Symfony\Component\HttpFoundation\Request;
19
20
/**
21
 * PagePartAdmin
22
 */
23
class PagePartAdmin
24
{
25
    /**
26
     * @var PagePartAdminConfiguratorInterface
27
     */
28
    protected $configurator;
29
30
    /**
31
     * @var EntityManager|EntityManagerInterface
32
     */
33
    protected $em;
34
35
    /**
36
     * @var HasPagePartsInterface
37
     */
38
    protected $page;
39
40
    /**
41
     * @var string
42
     */
43
    protected $context;
44
45
    /**
46
     * @var ContainerInterface
47
     */
48
    protected $container;
49
50
    /**
51
     * @var PagePartInterface[]
52
     */
53
    protected $pageParts = array();
54
55
    /**
56
     * @var PagePartRef[]
57
     */
58
    protected $pagePartRefs = array();
59
60
    /**
61
     * @var PagePartInterface[]
62
     */
63
    protected $newPageParts = array();
64
65
    /**
66
     * @param PagePartAdminConfiguratorInterface $configurator The configurator
67
     * @param EntityManagerInterface             $em           The entity manager
68
     * @param HasPagePartsInterface              $page         The page
69
     * @param string|null                        $context      The context
70
     * @param ContainerInterface|null            $container    The container
71
     *
72
     * @throws \InvalidArgumentException
73
     */
74
    public function __construct(PagePartAdminConfiguratorInterface $configurator, EntityManagerInterface $em, HasPagePartsInterface $page, $context = null, ContainerInterface $container = null)
75
    {
76
        if (!($page instanceof EntityInterface)) {
77
            throw new \InvalidArgumentException('Page must be an instance of EntityInterface.');
78
        }
79
80
        $this->configurator = $configurator;
81
        $this->em = $em;
82
        $this->page = $page;
83
        $this->container = $container;
84
85
        if ($context) {
0 ignored issues
show
Bug Best Practice introduced by Roderik van der Veer
The expression $context of type string|null 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...
86
            $this->context = $context;
87
        } elseif ($this->configurator->getContext()) {
88
            $this->context = $this->configurator->getContext();
89
        } else {
90
            $this->context = 'main';
91
        }
92
93
        $this->initializePageParts();
94
    }
95
96
    /**
97
     * Get all pageparts from the database, and store them.
98
     */
99
    private function initializePageParts()
100
    {
101
        // Get all the pagepartrefs
102
        /** @var PagePartRefRepository $ppRefRepo */
103
        $ppRefRepo = $this->em->getRepository(PagePartRef::class);
104
        $ppRefs = $ppRefRepo->getPagePartRefs($this->page, $this->context);
105
106
        // Group pagepartrefs per type
107
        $types = array();
108
        foreach ($ppRefs as $pagePartRef) {
109
            $types[$pagePartRef->getPagePartEntityname()][] = $pagePartRef->getPagePartId();
110
            $this->pagePartRefs[$pagePartRef->getId()] = $pagePartRef;
111
        }
112
113
        // Fetch all the pageparts (only one query per pagepart type)
114
        /** @var EntityInterface[] $pageParts */
115
        $pageParts = array();
116 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...
117
            $result = $this->em->getRepository($classname)->findBy(array('id' => $ids));
118
            $pageParts = array_merge($pageParts, $result);
119
        }
120
121
        // Link the pagepartref to the pagepart
122
        foreach ($this->pagePartRefs as $pagePartRef) {
123
            foreach ($pageParts as $key => $pagePart) {
124
                if (ClassLookup::getClass($pagePart) == $pagePartRef->getPagePartEntityname()
125
                    && $pagePart->getId() == $pagePartRef->getPagePartId()
126
                ) {
127
                    $this->pageParts[$pagePartRef->getId()] = $pagePart;
128
                    unset($pageParts[$key]);
129
130
                    break;
131
                }
132
            }
133
        }
134
    }
135
136
    /**
137
     * @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...
138
     */
139
    public function getPage()
140
    {
141
        return $this->page;
142
    }
143
144
    /**
145
     * @param Request $request
146
     */
147
    public function preBindRequest(Request $request)
148
    {
149
        // Fetch all sub-entities that should be removed
150
        $subPagePartsToDelete = array();
151
        foreach (array_keys($request->request->all()) as $key) {
152
            // Example value: delete_pagepartadmin_74_tags_3
153
            if (preg_match('/^delete_pagepartadmin_(\\d+)_(\\w+)_(\\d+)$/i', $key, $matches)) {
154
                $subPagePartsToDelete[$matches[1]][] = array('name' => $matches[2], 'id' => $matches[3]);
155
            }
156
        }
157
158
        $doFlush = false;
159
        foreach ($this->pagePartRefs as $pagePartRef) {
160
            // Remove pageparts
161
            if ('true' == $request->get($pagePartRef->getId().'_deleted')) {
162
                $pagePart = $this->pageParts[$pagePartRef->getId()];
163
                $this->em->remove($pagePart);
164
                $this->em->remove($pagePartRef);
165
166
                unset($this->pageParts[$pagePartRef->getId()], $this->pagePartRefs[$pagePartRef->getId()]);
167
                $doFlush = true;
168
            }
169
170
            // Remove sub-entities from pageparts
171
            if (\array_key_exists($pagePartRef->getId(), $subPagePartsToDelete)) {
172
                $pagePart = $this->pageParts[$pagePartRef->getId()];
173
                foreach ($subPagePartsToDelete[$pagePartRef->getId()] as $deleteInfo) {
174
                    /** @var EntityInterface[] $objects */
175
                    $objects = \call_user_func(array($pagePart, 'get'.ucfirst($deleteInfo['name'])));
176
177
                    foreach ($objects as $object) {
178
                        if ($object->getId() == $deleteInfo['id']) {
179
                            $this->em->remove($object);
180
                            $doFlush = true;
181
                        }
182
                    }
183
                }
184
            }
185
        }
186
187
        if ($doFlush) {
188
            $this->em->flush();
189
        }
190
191
        // Create the objects for the new pageparts
192
        $this->newPageParts = array();
193
        $newRefIds = $request->get($this->context.'_new');
194
195
        if (\is_array($newRefIds)) {
196
            foreach ($newRefIds as $newId) {
197
                $type = $request->get($this->context.'_type_'.$newId);
198
                $this->newPageParts[$newId] = new $type();
199
            }
200
        }
201
202
        // Sort pageparts again
203
        $sequences = $request->get($this->context.'_sequence');
204
        if (!\is_null($sequences)) {
205
            $tempPageparts = $this->pageParts;
206
            $this->pageParts = array();
207
            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...
208
                if (\array_key_exists($sequence, $this->newPageParts)) {
209
                    $this->pageParts[$sequence] = $this->newPageParts[$sequence];
210
                } elseif (\array_key_exists($sequence, $tempPageparts)) {
211
                    $this->pageParts[$sequence] = $tempPageparts[$sequence];
212
                } else {
213
                    $this->pageParts[$sequence] = $this->getPagePart($sequence, array_search($sequence, $sequences) + 1);
214
                }
215
            }
216
217
            unset($tempPageparts);
218
        }
219
    }
220
221
    /**
222
     * @param Request $request
223
     */
224
    public function bindRequest(Request $request)
225
    {
226
    }
227
228
    /**
229
     * @param FormBuilderInterface $formbuilder
230
     */
231
    public function adaptForm(FormBuilderInterface $formbuilder)
232
    {
233
        $data = $formbuilder->getData();
234
235
        foreach ($this->pageParts as $pagePartRefId => $pagePart) {
236
            $data['pagepartadmin_' . $pagePartRefId] = $pagePart;
237
            $formbuilder->add('pagepartadmin_' . $pagePartRefId, $pagePart->getDefaultAdminType());
238
        }
239
240
        foreach ($this->newPageParts as $newPagePartRefId => $newPagePart) {
241
            $data['pagepartadmin_' . $newPagePartRefId] = $newPagePart;
242
            $formbuilder->add('pagepartadmin_' . $newPagePartRefId, $newPagePart->getDefaultAdminType());
243
        }
244
245
        $formbuilder->setData($data);
246
    }
247
248
    /**
249
     * @param Request $request
250
     */
251
    public function persist(Request $request)
252
    {
253
        /** @var PagePartRefRepository $ppRefRepo */
254
        $ppRefRepo = $this->em->getRepository(PagePartRef::class);
255
256
        // Add new pageparts on the correct position + Re-order and save pageparts if needed
257
        $sequences = $request->get($this->context.'_sequence', []);
258
        $sequencescount = \count($sequences);
259
        for ($i = 0; $i < $sequencescount; ++$i) {
260
            $pagePartRefId = $sequences[$i];
261
262
            if (\array_key_exists($pagePartRefId, $this->newPageParts)) {
263
                $pagePart = $this->newPageParts[$pagePartRefId];
264
                $this->em->persist($pagePart);
265
                $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...
266
267
                $ppRefRepo->addPagePart($this->page, $pagePart, $i + 1, $this->context, false);
268
            } elseif (\array_key_exists($pagePartRefId, $this->pagePartRefs)) {
269
                $pagePartRef = $this->pagePartRefs[$pagePartRefId];
270
                if ($pagePartRef instanceof PagePartRef && $pagePartRef->getSequencenumber() != ($i + 1)) {
271
                    $pagePartRef->setSequencenumber($i + 1);
272
                    $pagePartRef->setContext($this->context);
273
                    $this->em->persist($pagePartRef);
274
                }
275
                $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...
276
            }
277
278
            if (isset($pagePart)) {
279
                $this->dispatch(new PagePartEvent($pagePart), Events::POST_PERSIST);
280
            }
281
        }
282
    }
283
284
    /**
285
     * @return string|null
286
     */
287
    public function getContext()
288
    {
289
        return $this->context;
290
    }
291
292
    /**
293
     * This getter returns an array holding info on page part types that can be added to the page.
294
     * The types are filtererd here, based on the amount of page parts of a certain type that can be added to the page.
295
     *
296
     * @return array
297
     */
298
    public function getPossiblePagePartTypes()
299
    {
300
        $possiblePPTypes = $this->configurator->getPossiblePagePartTypes();
301
        $result = array();
302
303
        // filter page part types that can only be added x times to the page context.
304
        // to achieve this, provide a 'pagelimit' parameter when adding the pp type in your PagePartAdminConfiguration
305
        if (!empty($possiblePPTypes)) {
306
            foreach ($possiblePPTypes as $possibleTypeData) {
307
                if (\array_key_exists('pagelimit', $possibleTypeData)) {
308
                    $pageLimit = $possibleTypeData['pagelimit'];
309
                    /** @var PagePartRefRepository $entityRepository */
310
                    $entityRepository = $this->em->getRepository(PagePartRef::class);
311
                    $formPPCount = $entityRepository->countPagePartsOfType(
312
                        $this->page,
313
                        $possibleTypeData['class'],
314
                        $this->configurator->getContext()
315
                    );
316
                    if ($formPPCount < $pageLimit) {
317
                        $result[] = $possibleTypeData;
318
                    }
319
                } else {
320
                    $result[] = $possibleTypeData;
321
                }
322
            }
323
        }
324
325
        return $result;
326
    }
327
328
    /**
329
     * @return string
330
     */
331
    public function getName()
332
    {
333
        return $this->configurator->getName();
334
    }
335
336
    /**
337
     * @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...
338
     */
339
    public function getPagePartMap()
340
    {
341
        return $this->pageParts;
342
    }
343
344
    /**
345
     * @param PagePartInterface $pagepart
346
     *
347
     * @return string
348
     */
349
    public function getType(PagePartInterface $pagepart)
350
    {
351
        $possiblePagePartTypes = $this->configurator->getPossiblePagePartTypes();
352
        foreach ($possiblePagePartTypes as &$pageparttype) {
353
            if ($pageparttype['class'] == ClassLookup::getClass($pagepart)) {
354
                return $pageparttype['name'];
355
            }
356
        }
357
358
        return 'no name';
359
    }
360
361
    /**
362
     * @param int $id
363
     * @param int $sequenceNumber
364
     *
365
     * @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...
366
     */
367
    public function getPagePart($id, $sequenceNumber)
368
    {
369
        /** @var PagePartRefRepository $ppRefRepo */
370
        $ppRefRepo = $this->em->getRepository(PagePartRef::class);
371
372
        return $ppRefRepo->getPagePart($id, $this->context, $sequenceNumber);
373
    }
374
375
    /**
376
     * @param object $pagepart
377
     *
378
     * @return string
379
     */
380
    public function getClassName($pagepart)
381
    {
382
        return \get_class($pagepart);
383
    }
384
385
    /**
386
     * @param object $event
387
     * @param string $eventName
388
     *
389
     * @return object
390
     */
391 View Code Duplication
    private function dispatch($event, string $eventName)
0 ignored issues
show
Duplication introduced by Jeroen Thora
This method seems to be duplicated in 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...
392
    {
393
        $eventDispatcher = $this->container->get('event_dispatcher');
394
        if (class_exists(LegacyEventDispatcherProxy::class)) {
395
            $eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);
396
397
            return $eventDispatcher->dispatch($event, $eventName);
398
        }
399
400
        return $eventDispatcher->dispatch($eventName, $event);
401
    }
402
}
403