Completed
Push — master ( ae5e03...0447ee )
by Jeroen
10:35 queued 04:37
created

PagePartAdmin::getPage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
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 string|null                        $context      The context
69
     * @param ContainerInterface|null            $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
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...
85
            $this->context = $context;
86
        } elseif ($this->configurator->getContext()) {
87
            $this->context = $this->configurator->getContext();
88
        } else {
89
            $this->context = 'main';
90
        }
91
92
        $this->initializePageParts();
93
    }
94
95
    /**
96
     * Get all pageparts from the database, and store them.
97
     */
98
    private function initializePageParts()
99
    {
100
        // Get all the pagepartrefs
101
        /** @var PagePartRefRepository $ppRefRepo */
102
        $ppRefRepo = $this->em->getRepository(PagePartRef::class);
103
        $ppRefs = $ppRefRepo->getPagePartRefs($this->page, $this->context);
104
105
        // Group pagepartrefs per type
106
        $types = array();
107
        foreach ($ppRefs as $pagePartRef) {
108
            $types[$pagePartRef->getPagePartEntityname()][] = $pagePartRef->getPagePartId();
109
            $this->pagePartRefs[$pagePartRef->getId()] = $pagePartRef;
110
        }
111
112
        // Fetch all the pageparts (only one query per pagepart type)
113
        /** @var EntityInterface[] $pageParts */
114
        $pageParts = array();
115 View Code Duplication
        foreach ($types as $classname => $ids) {
0 ignored issues
show
Duplication introduced by
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...
116
            $result = $this->em->getRepository($classname)->findBy(array('id' => $ids));
117
            $pageParts = array_merge($pageParts, $result);
118
        }
119
120
        // Link the pagepartref to the pagepart
121
        foreach ($this->pagePartRefs as $pagePartRef) {
122
            foreach ($pageParts as $key => $pagePart) {
123
                if (ClassLookup::getClass($pagePart) == $pagePartRef->getPagePartEntityname()
124
                    && $pagePart->getId() == $pagePartRef->getPagePartId()
125
                ) {
126
                    $this->pageParts[$pagePartRef->getId()] = $pagePart;
127
                    unset($pageParts[$key]);
128
129
                    break;
130
                }
131
            }
132
        }
133
    }
134
135
    /**
136
     * @return EntityInterface
0 ignored issues
show
Documentation introduced by
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...
137
     */
138
    public function getPage()
139
    {
140
        return $this->page;
141
    }
142
143
    /**
144
     * @param Request $request
145
     */
146
    public function preBindRequest(Request $request)
147
    {
148
        // Fetch all sub-entities that should be removed
149
        $subPagePartsToDelete = array();
150
        foreach (array_keys($request->request->all()) as $key) {
151
            // Example value: delete_pagepartadmin_74_tags_3
152
            if (preg_match('/^delete_pagepartadmin_(\\d+)_(\\w+)_(\\d+)$/i', $key, $matches)) {
153
                $subPagePartsToDelete[$matches[1]][] = array('name' => $matches[2], 'id' => $matches[3]);
154
            }
155
        }
156
157
        $doFlush = false;
158
        foreach ($this->pagePartRefs as $pagePartRef) {
159
            // Remove pageparts
160
            if ('true' == $request->get($pagePartRef->getId().'_deleted')) {
161
                $pagePart = $this->pageParts[$pagePartRef->getId()];
162
                $this->em->remove($pagePart);
163
                $this->em->remove($pagePartRef);
164
165
                unset($this->pageParts[$pagePartRef->getId()], $this->pagePartRefs[$pagePartRef->getId()]);
166
                $doFlush = true;
167
            }
168
169
            // Remove sub-entities from pageparts
170
            if (\array_key_exists($pagePartRef->getId(), $subPagePartsToDelete)) {
171
                $pagePart = $this->pageParts[$pagePartRef->getId()];
172
                foreach ($subPagePartsToDelete[$pagePartRef->getId()] as $deleteInfo) {
173
                    /** @var EntityInterface[] $objects */
174
                    $objects = \call_user_func(array($pagePart, 'get'.ucfirst($deleteInfo['name'])));
175
176
                    foreach ($objects as $object) {
177
                        if ($object->getId() == $deleteInfo['id']) {
178
                            $this->em->remove($object);
179
                            $doFlush = true;
180
                        }
181
                    }
182
                }
183
            }
184
        }
185
186
        if ($doFlush) {
187
            $this->em->flush();
188
        }
189
190
        // Create the objects for the new pageparts
191
        $this->newPageParts = array();
192
        $newRefIds = $request->get($this->context.'_new');
193
194
        if (\is_array($newRefIds)) {
195
            foreach ($newRefIds as $newId) {
196
                $type = $request->get($this->context.'_type_'.$newId);
197
                $this->newPageParts[$newId] = new $type();
198
            }
199
        }
200
201
        // Sort pageparts again
202
        $sequences = $request->get($this->context.'_sequence');
203
        if (!\is_null($sequences)) {
204
            $tempPageparts = $this->pageParts;
205
            $this->pageParts = array();
206
            foreach ($sequences as $sequence) {
0 ignored issues
show
Bug introduced by
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...
207
                if (\array_key_exists($sequence, $this->newPageParts)) {
208
                    $this->pageParts[$sequence] = $this->newPageParts[$sequence];
209
                } elseif (\array_key_exists($sequence, $tempPageparts)) {
210
                    $this->pageParts[$sequence] = $tempPageparts[$sequence];
211
                } else {
212
                    $this->pageParts[$sequence] = $this->getPagePart($sequence, array_search($sequence, $sequences) + 1);
213
                }
214
            }
215
216
            unset($tempPageparts);
217
        }
218
    }
219
220
    /**
221
     * @param Request $request
222
     */
223
    public function bindRequest(Request $request)
224
    {
225
    }
226
227
    /**
228
     * @param FormBuilderInterface $formbuilder
229
     */
230
    public function adaptForm(FormBuilderInterface $formbuilder)
231
    {
232
        $data = $formbuilder->getData();
233
234
        foreach ($this->pageParts as $pagePartRefId => $pagePart) {
235
            $data['pagepartadmin_' . $pagePartRefId] = $pagePart;
236
            $formbuilder->add('pagepartadmin_' . $pagePartRefId, $pagePart->getDefaultAdminType());
237
        }
238
239
        foreach ($this->newPageParts as $newPagePartRefId => $newPagePart) {
240
            $data['pagepartadmin_' . $newPagePartRefId] = $newPagePart;
241
            $formbuilder->add('pagepartadmin_' . $newPagePartRefId, $newPagePart->getDefaultAdminType());
242
        }
243
244
        $formbuilder->setData($data);
245
    }
246
247
    /**
248
     * @param Request $request
249
     */
250
    public function persist(Request $request)
251
    {
252
        /** @var PagePartRefRepository $ppRefRepo */
253
        $ppRefRepo = $this->em->getRepository(PagePartRef::class);
254
255
        // Add new pageparts on the correct position + Re-order and save pageparts if needed
256
        $sequences = $request->get($this->context.'_sequence', []);
257
        $sequencescount = \count($sequences);
258
        for ($i = 0; $i < $sequencescount; ++$i) {
259
            $pagePartRefId = $sequences[$i];
260
261
            if (\array_key_exists($pagePartRefId, $this->newPageParts)) {
262
                $pagePart = $this->newPageParts[$pagePartRefId];
263
                $this->em->persist($pagePart);
264
                $this->em->flush($pagePart);
0 ignored issues
show
Unused Code introduced by
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...
265
266
                $ppRefRepo->addPagePart($this->page, $pagePart, $i + 1, $this->context, false);
267
            } elseif (\array_key_exists($pagePartRefId, $this->pagePartRefs)) {
268
                $pagePartRef = $this->pagePartRefs[$pagePartRefId];
269
                if ($pagePartRef instanceof PagePartRef && $pagePartRef->getSequencenumber() != ($i + 1)) {
270
                    $pagePartRef->setSequencenumber($i + 1);
271
                    $pagePartRef->setContext($this->context);
272
                    $this->em->persist($pagePartRef);
273
                }
274
                $pagePart = $pagePartRef->getPagePart($this->em);
0 ignored issues
show
Bug introduced by
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...
275
            }
276
277
            if (isset($pagePart)) {
278
                $this->container->get('event_dispatcher')->dispatch(
279
                    Events::POST_PERSIST,
280
                    new PagePartEvent($pagePart)
281
                );
282
            }
283
        }
284
    }
285
286
    /**
287
     * @return string|null
288
     */
289
    public function getContext()
290
    {
291
        return $this->context;
292
    }
293
294
    /**
295
     * This getter returns an array holding info on page part types that can be added to the page.
296
     * The types are filtererd here, based on the amount of page parts of a certain type that can be added to the page.
297
     *
298
     * @return array
299
     */
300
    public function getPossiblePagePartTypes()
301
    {
302
        $possiblePPTypes = $this->configurator->getPossiblePagePartTypes();
303
        $result = array();
304
305
        // filter page part types that can only be added x times to the page context.
306
        // to achieve this, provide a 'pagelimit' parameter when adding the pp type in your PagePartAdminConfiguration
307
        if (!empty($possiblePPTypes)) {
308
            foreach ($possiblePPTypes as $possibleTypeData) {
309
                if (\array_key_exists('pagelimit', $possibleTypeData)) {
310
                    $pageLimit = $possibleTypeData['pagelimit'];
311
                    /** @var PagePartRefRepository $entityRepository */
312
                    $entityRepository = $this->em->getRepository(PagePartRef::class);
313
                    $formPPCount = $entityRepository->countPagePartsOfType(
314
                        $this->page,
315
                        $possibleTypeData['class'],
316
                        $this->configurator->getContext()
317
                    );
318
                    if ($formPPCount < $pageLimit) {
319
                        $result[] = $possibleTypeData;
320
                    }
321
                } else {
322
                    $result[] = $possibleTypeData;
323
                }
324
            }
325
        }
326
327
        return $result;
328
    }
329
330
    /**
331
     * @return string
332
     */
333
    public function getName()
334
    {
335
        return $this->configurator->getName();
336
    }
337
338
    /**
339
     * @return array
0 ignored issues
show
Documentation introduced by
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...
340
     */
341
    public function getPagePartMap()
342
    {
343
        return $this->pageParts;
344
    }
345
346
    /**
347
     * @param PagePartInterface $pagepart
348
     *
349
     * @return string
350
     */
351
    public function getType(PagePartInterface $pagepart)
352
    {
353
        $possiblePagePartTypes = $this->configurator->getPossiblePagePartTypes();
354
        foreach ($possiblePagePartTypes as &$pageparttype) {
355
            if ($pageparttype['class'] == ClassLookup::getClass($pagepart)) {
356
                return $pageparttype['name'];
357
            }
358
        }
359
360
        return 'no name';
361
    }
362
363
    /**
364
     * @param int $id
365
     * @param int $sequenceNumber
366
     *
367
     * @return PagePartInterface
0 ignored issues
show
Documentation introduced by
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...
368
     */
369
    public function getPagePart($id, $sequenceNumber)
370
    {
371
        /** @var PagePartRefRepository $ppRefRepo */
372
        $ppRefRepo = $this->em->getRepository(PagePartRef::class);
373
374
        return $ppRefRepo->getPagePart($id, $this->context, $sequenceNumber);
375
    }
376
377
    /**
378
     * @param object $pagepart
379
     *
380
     * @return string
381
     */
382
    public function getClassName($pagepart)
383
    {
384
        return \get_class($pagepart);
385
    }
386
}
387