Completed
Pull Request — master (#90)
by Arnaud
19:49
created

Admin::getEntities()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
3
namespace LAG\AdminBundle\Admin;
4
5
use Doctrine\Common\Collections\Collection;
6
use LAG\AdminBundle\Action\ActionInterface;
7
use LAG\AdminBundle\Admin\Behaviors\AdminTrait;
8
use LAG\AdminBundle\Admin\Configuration\AdminConfiguration;
9
use Doctrine\Common\Collections\ArrayCollection;
10
use Exception;
11
use LAG\AdminBundle\Admin\Request\LoadParameterExtractor;
12
use LAG\AdminBundle\Admin\Request\RequestHandler;
13
use LAG\AdminBundle\DataProvider\DataProviderInterface;
14
use LAG\AdminBundle\DataProvider\Loader\EntityLoaderInterface;
15
use LAG\AdminBundle\LAGAdminBundle;
16
use LAG\AdminBundle\Message\MessageHandlerInterface;
17
use LAG\AdminBundle\View\Factory\ViewFactory;
18
use LAG\AdminBundle\View\ViewInterface;
19
use LogicException;
20
use Pagerfanta\Pagerfanta;
21
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
22
use Symfony\Component\Form\Test\FormInterface;
23
use Symfony\Component\HttpFoundation\Request;
24
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
25
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
26
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
27
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
28
use Symfony\Component\Security\Core\User\UserInterface;
29
30
class Admin implements AdminInterface
31
{
32
    use AdminTrait;
33
    
34
    /**
35
     * Entities collection.
36
     *
37
     * @var ArrayCollection
38
     */
39
    protected $entities;
40
    
41
    /**
42
     * @var MessageHandlerInterface
43
     */
44
    protected $messageHandler;
45
    
46
    /**
47
     * @var DataProviderInterface
48
     */
49
    protected $dataProvider;
50
    
51
    /**
52
     * Admin configuration object
53
     *
54
     * @var AdminConfiguration
55
     */
56
    protected $configuration;
57
    
58
    /**
59
     * Admin configured actions
60
     *
61
     * @var ActionInterface[]
62
     */
63
    protected $actions = [];
64
    
65
    /**
66
     * Admin current action. It will be set after calling the handleRequest()
67
     *
68
     * @var ActionInterface
69
     */
70
    protected $currentAction;
71
    
72
    /**
73
     * Admin name
74
     *
75
     * @var string
76
     */
77
    protected $name;
78
    
79
    /**
80
     * @var EventDispatcherInterface
81
     */
82
    protected $eventDispatcher;
83
    
84
    /**
85
     * @var AuthorizationCheckerInterface
86
     */
87
    protected $authorizationChecker;
88
    
89
    /**
90
     * @var TokenStorageInterface
91
     */
92
    protected $tokenStorage;
93
    
94
    /**
95
     * @var FormInterface|null
96
     */
97
    protected $filterForm;
98
    
99
    /**
100
     * @var EntityLoaderInterface
101
     */
102
    protected $entityLoader;
103
    
104
    /**
105
     * @var ViewFactory
106
     */
107
    private $viewFactory;
108
    
109
    /**
110
     * @var ViewInterface
111
     */
112
    private $view;
113
    /**
114
     * @var RequestHandler
115
     */
116
    private $requestHandler;
117
    
118
    /**
119
     * Admin constructor.
120
     *
121
     * @param string                        $name
122
     * @param EntityLoaderInterface         $entityLoader
123
     * @param AdminConfiguration            $configuration
124
     * @param MessageHandlerInterface       $messageHandler
125
     * @param EventDispatcherInterface      $eventDispatcher
126
     * @param AuthorizationCheckerInterface $authorizationChecker
127
     * @param TokenStorageInterface         $tokenStorage
128
     * @param RequestHandler                $requestHandler
129
     * @param ViewFactory                   $viewFactory
130
     * @param array                         $actions
131
     */
132 View Code Duplication
    public function __construct(
1 ignored issue
show
Duplication introduced by
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...
133
        $name,
134
        EntityLoaderInterface $entityLoader,
135
        AdminConfiguration $configuration,
136
        MessageHandlerInterface $messageHandler,
137
        EventDispatcherInterface $eventDispatcher,
138
        AuthorizationCheckerInterface $authorizationChecker,
139
        TokenStorageInterface $tokenStorage,
140
        RequestHandler $requestHandler,
141
        ViewFactory $viewFactory,
142
        $actions = []
143
    ) {
144
        $this->name = $name;
145
        $this->entities = new ArrayCollection();
146
        $this->configuration = $configuration;
147
        $this->messageHandler = $messageHandler;
148
        $this->eventDispatcher = $eventDispatcher;
149
        $this->authorizationChecker = $authorizationChecker;
150
        $this->tokenStorage = $tokenStorage;
151
        $this->actions = $actions;
152
        $this->entityLoader = $entityLoader;
153
        $this->dataProvider = $entityLoader->getDataProvider();
154
        $this->viewFactory = $viewFactory;
155
        $this->requestHandler = $requestHandler;
156
    }
157
    
158
    /**
159
     * Load entities and set current action according to request and the optional filters.
160
     *
161
     * @param Request $request
162
     * @param array   $filters
163
     */
164
    public function handleRequest(Request $request, array $filters = [])
165
    {
166
        if (!$this->requestHandler->supports($request)) {
167
            throw new BadRequestHttpException(
168
                'The given request can be processed. The route parameters "_admin" and "_action" probably missing'
169
            );
170
        }
171
        $actionName = $request->get('_route_params')[LAGAdminBundle::REQUEST_PARAMETER_ACTION];
172
    
173
        $this->view = $this
174
            ->viewFactory
175
            ->create($actionName, $this->name, $this->configuration, $this->configuration->getParameter('actions')[$actionName])
176
        ;
177
        
178
        // set current action
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
179
//        $this->currentAction = $this->getAction(
180
//            $request->get('_route_params')[LAGAdminBundle::REQUEST_PARAMETER_ACTION]
181
//        );
182
        
183
        // check if user is logged have required permissions to get current action
184
        $this->checkPermissions();
185
        
186
        // get the current action configuration bag
187
        $actionConfiguration = $this
188
            ->view
189
            ->getConfiguration()
190
        ;
191
    
192
        // if no loading is required, no more thing to do. Some actions do not require to load entities from
193
        // the DataProvider
194
        if (Admin::LOAD_STRATEGY_NONE === $actionConfiguration->getParameter('load_strategy')) {
195
            return;
196
        }
197
        
198
        // retrieve the criteria to find one or more entities (from the request for sorting, pagination... and from
199
        // the filter form
200
        $loader = new LoadParameterExtractor($actionConfiguration, $filters);
201
        $loader->load($request);
202
    
203
        // load entities according to action and request
204
        $this
205
            ->entityLoader
206
            ->configure($actionConfiguration)
207
        ;
208
        
209
        $this
210
            ->load(
211
                $loader->getCriteria(),
212
                $loader->getOrder(),
213
                $loader->getMaxPerPage(),
214
                $loader->getPage()
215
            )
216
        ;
217
        $this->view->setEntities($this->entities);
218
    }
219
    
220
    /**
221
     * Check if user is allowed to be here.
222
     *
223
     * @throws LogicException|AccessDeniedException
224
     */
225
    public function checkPermissions()
226
    {
227
        $user = $this
228
            ->tokenStorage
229
            ->getToken()
230
            ->getUser()
231
        ;
232
        
233
        // must be authenticated to access to an admin
234
        if (!($user instanceof UserInterface)) {
235
            throw new AccessDeniedException();
236
        }
237
        
238
        // a view must have been defined
239
        if (null === $this->view) {
240
            throw new LogicException(
241
                'A view must be defined before checking the permissions. Maybe you forget to call handleRequest()'
242
            );
243
        }
244
        // check if the current User is granted in Symfony security configuration
245
        if (!$this->authorizationChecker->isGranted($user->getRoles(), $user)) {
246
            throw new AccessDeniedException();
247
        }
248
        $permissions = $this
249
            ->view
250
            ->getConfiguration()
251
            ->getParameter('permissions')
252
        ;
253
        
254
        // check if the User is granted according to Admin configuration
255
        if (!$this->authorizationChecker->isGranted($permissions, $user)) {
256
            throw new AccessDeniedException();
257
        }
258
    }
259
    
260
    /**
261
     * Create and return a new entity.
262
     *
263
     * @return object
264
     */
265
    public function create()
266
    {
267
        // create an entity from the data provider
268
        $entity = $this
269
            ->dataProvider
270
            ->create();
271
        
272
        // add it to the collection
273
        $this
274
            ->entities
275
            ->add($entity);
276
        
277
        return $entity;
278
    }
279
    
280
    /**
281
     * Save entity via admin manager.
282
     */
283
    public function save()
284
    {
285
        foreach ($this->entities as $entity) {
286
            $this
287
                ->dataProvider
288
                ->save($entity)
289
            ;
290
        }
291
        // inform the user that the entity is saved
292
        $this
293
            ->messageHandler
294
            ->handleSuccess($this->generateMessageTranslationKey('saved'))
295
        ;
296
    }
297
    
298
    /**
299
     * Remove an entity with data provider.
300
     */
301
    public function remove()
302
    {
303
        foreach ($this->entities as $entity) {
304
            $this
305
                ->dataProvider
306
                ->remove($entity);
307
        }
308
        // inform the user that the entity is removed
309
        $this
310
            ->messageHandler
311
            ->handleSuccess($this->generateMessageTranslationKey('deleted'))
312
        ;
313
    }
314
    
315
    /**
316
     * Return the number of entities managed by the Admin.
317
     *
318
     * @return int
319
     */
320
    public function count()
321
    {
322
        $count = $this
323
            ->dataProvider
324
            ->count()
325
        ;
326
    
327
        if (!is_integer($count)) {
328
            throw new LogicException(
329
                'The data provider should return an integer for the "count()" method, given : '.gettype($count)
330
            );
331
        }
332
    
333
        return $count;
334
    }
335
    
336
    /**
337
     * Load entities according to the given criteria and the current action configuration.
338
     *
339
     * @param array $criteria
340
     * @param array $orderBy
341
     * @param int   $limit
342
     * @param int   $offset
343
     */
344
    public function load(array $criteria, array $orderBy = [], $limit = 25, $offset = 1)
345
    {
346
        // retrieve the data using the data provider via the entity loader
347
        $entities = $this
348
            ->entityLoader
349
            ->load($criteria, $orderBy, $limit, $offset)
350
        ;
351
    
352
        // either, we have an instance of Pagerfanta, either we should have an array or a collection
353
        if ($entities instanceof Pagerfanta) {
354
            // if the entities are inside a pager, we get the result and we set the pager for the view
355
            $this->entities = new ArrayCollection($entities->getCurrentPageResults());
0 ignored issues
show
Bug introduced by
It seems like $entities->getCurrentPageResults() targeting Pagerfanta\Pagerfanta::getCurrentPageResults() can also be of type object<Traversable>; however, Doctrine\Common\Collecti...llection::__construct() does only seem to accept array, maybe add an additional type check?

This check looks at variables that 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...
356
            $this->pager = $entities;
357
        } else {
358
            // the data provider should return an array or a collection of entities.
359
            if (!is_array($entities) && !$entities instanceof Collection) {
360
                throw new LogicException(
361
                    'The data provider should return either a collection or an array. Got '.gettype($entities).' instead'
362
                );
363
            }
364
    
365
            // if an array is provided, transform it to a collection to be more convenient
366
            if (is_array($entities)) {
367
                $entities = new ArrayCollection($entities);
368
            }
369
            $this->entities = $entities;
370
        }
371
    }
372
    
373
    /**
374
     * Return loaded entities
375
     *
376
     * @return Collection
377
     */
378
    public function getEntities()
379
    {
380
        return $this->entities;
381
    }
382
    
383
    /**
384
     * Return entity for current admin. If entity does not exist, it throws an exception.
385
     *
386
     * @return mixed
387
     *
388
     * @throws Exception
389
     */
390
    public function getUniqueEntity()
391
    {
392
        if ($this->entities->count() == 0) {
393
            throw new Exception('Entity not found in admin "'.$this->getName().'""');
394
        }
395
        
396
        if ($this->entities->count() > 1) {
397
            throw new Exception(
398
                'Too much entities found in admin '.$this->getName().' ('.$this->entities->count().' entities found, '
399
                .'expected one). Check the load strategy configuration'
400
            );
401
        }
402
        
403
        return $this->entities->first();
404
    }
405
    
406
    /**
407
     * Return admin name
408
     *
409
     * @return string
410
     */
411
    public function getName()
412
    {
413
        return $this->name;
414
    }
415
    
416
    /**
417
     * Return true if the Action with name $name exists in the Admin. If the method return true, it does not necessarily
418
     * means that the action is allowed in the current context.
419
     *
420
     * @param string $name
421
     *
422
     * @return boolean
423
     */
424
    public function hasAction($name)
425
    {
426
        $actions = $this->configuration->getParameter('actions');
427
        
428
        if (!key_exists($name, $actions)) {
429
            return false;
430
        }
431
    
432
        return null !== $actions[$name] && false !== $actions[$name];
433
    }
434
    
435
    public function getView()
436
    {
437
        if (!$this->hasView()) {
438
            throw new LogicException('The view is not defined. Maybe you forgot to call handleRequest() ?');
439
        }
440
    
441
        return $this->view;
442
    }
443
    
444
    public function hasView()
445
    {
446
        return null !== $this->view;
447
    }
448
    
449
    /**
450
     * Return if the current action has been initialized and set.
451
     *
452
     * @return boolean
453
     */
454
    public function isCurrentActionDefined()
455
    {
456
        return ($this->currentAction instanceof ActionInterface);
457
    }
458
    
459
    /**
460
     * Return admin configuration object.
461
     *
462
     * @return AdminConfiguration
463
     */
464
    public function getConfiguration()
465
    {
466
        return $this->configuration;
467
    }
468
    
469
    /**
470
     * Return the filter form if it was initialized.
471
     *
472
     * @return FormInterface
473
     *
474
     * @throws Exception
475
     */
476
    public function getFilterForm()
477
    {
478
        if (null === $this->filterForm) {
479
            throw new Exception(
480
                'The filter form is null. Check you have configured filters. You should initialize the filter form 
481
                (with $admin->handleRequest() method for example)'
482
            );
483
        }
484
        
485
        return $this->filterForm;
486
    }
487
    
488
    /**
489
     * Return true if the filter form has been set.
490
     *
491
     * @return bool
492
     */
493
    public function hasFilterForm()
494
    {
495
        return null !== $this->filterForm;
496
    }
497
    
498
    /**
499
     * Return a translation key for a message according to the Admin's translation pattern.
500
     *
501
     * @param string $message
502
     * @return string
503
     */
504
    protected function generateMessageTranslationKey($message)
505
    {
506
        return $this->getTranslationKey(
507
            $this->configuration->getParameter('translation_pattern'),
508
            $message,
509
            $this->name
510
        );
511
    }
512
}
513