Completed
Pull Request — master (#90)
by Arnaud
17:45
created

Admin::getAction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.2559

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
ccs 3
cts 5
cp 0.6
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
crap 2.2559
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\DataProvider\DataProviderInterface;
13
use LAG\AdminBundle\DataProvider\Loader\EntityLoaderInterface;
14
use LAG\AdminBundle\LAGAdminBundle;
15
use LAG\AdminBundle\Message\MessageHandlerInterface;
16
use LogicException;
17
use Pagerfanta\Pagerfanta;
18
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
19
use Symfony\Component\Form\Test\FormInterface;
20
use Symfony\Component\HttpFoundation\Request;
21
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
22
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
23
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
24
use Symfony\Component\Security\Core\User\UserInterface;
25
26
class Admin implements AdminInterface
27
{
28
    use AdminTrait;
29
    
30
    /**
31
     * Entities collection.
32
     *
33
     * @var ArrayCollection
34
     */
35
    protected $entities;
36
    
37
    /**
38
     * @var MessageHandlerInterface
39
     */
40
    protected $messageHandler;
41
    
42
    /**
43
     * @var DataProviderInterface
44
     */
45
    protected $dataProvider;
46
    
47
    /**
48
     * Admin configuration object
49
     *
50
     * @var AdminConfiguration
51
     */
52
    protected $configuration;
53
    
54
    /**
55
     * Admin configured actions
56
     *
57
     * @var ActionInterface[]
58
     */
59
    protected $actions = [];
60
    
61
    /**
62
     * Admin current action. It will be set after calling the handleRequest()
63
     *
64
     * @var ActionInterface
65
     */
66
    protected $currentAction;
67
    
68
    /**
69
     * Admin name
70
     *
71
     * @var string
72
     */
73
    protected $name;
74
    
75
    /**
76
     * @var EventDispatcherInterface
77
     */
78
    protected $eventDispatcher;
79
    
80
    /**
81
     * @var AuthorizationCheckerInterface
82
     */
83
    protected $authorizationChecker;
84
    
85
    /**
86
     * @var TokenStorageInterface
87
     */
88
    protected $tokenStorage;
89
    
90
    /**
91
     * @var FormInterface|null
92
     */
93
    protected $filterForm;
94
    
95
    /**
96
     * @var EntityLoaderInterface
97
     */
98
    protected $entityLoader;
99
    
100
    /**
101
     * Admin constructor.
102
     *
103
     * @param string                        $name
104
     * @param EntityLoaderInterface         $entityLoader
105
     * @param AdminConfiguration            $configuration
106
     * @param MessageHandlerInterface       $messageHandler
107
     * @param EventDispatcherInterface      $eventDispatcher
108
     * @param AuthorizationCheckerInterface $authorizationChecker
109
     * @param TokenStorageInterface         $tokenStorage
110
     * @param array                         $actions
111
     */
112 41
    public function __construct(
113
        $name,
114
        EntityLoaderInterface $entityLoader,
115
        AdminConfiguration $configuration,
116
        MessageHandlerInterface $messageHandler,
117
        EventDispatcherInterface $eventDispatcher,
118
        AuthorizationCheckerInterface $authorizationChecker,
119
        TokenStorageInterface $tokenStorage,
120
        $actions = []
121
    ) {
122 41
        $this->name = $name;
123 41
        $this->entities = new ArrayCollection();
124 41
        $this->configuration = $configuration;
125 41
        $this->messageHandler = $messageHandler;
126 41
        $this->eventDispatcher = $eventDispatcher;
127 41
        $this->authorizationChecker = $authorizationChecker;
128 41
        $this->tokenStorage = $tokenStorage;
129 41
        $this->actions = $actions;
130 41
        $this->entityLoader = $entityLoader;
131 41
        $this->dataProvider = $entityLoader->getDataProvider();
132 41
    }
133
    
134
    /**
135
     * Load entities and set current action according to request and the optional filters.
136
     *
137
     * @param Request $request
138
     * @param array   $filters
139
     */
140 20
    public function handleRequest(Request $request, array $filters = [])
141
    {
142
        // set current action
143 20
        $this->currentAction = $this->getAction(
144 20
            $request->get('_route_params')[LAGAdminBundle::REQUEST_PARAMETER_ACTION]
145
        );
146
        
147
        // check if user is logged have required permissions to get current action
148 20
        $this->checkPermissions();
149
        
150
        // get the current action configuration bag
151
        $actionConfiguration = $this
152 8
            ->currentAction
153 8
            ->getConfiguration()
154
        ;
155
    
156
        // if no loading is required, no more thing to do. Some actions do not require to load entities from
157
        // the DataProvider
158 8
        if (true !== $this->currentAction->isLoadingRequired()) {
159 4
            return;
160
        }
161
        
162
        // retrieve the criteria to find one or more entities (from the request for sorting, pagination... and from
163
        // the filter form
164 4
        $loader = new LoadParameterExtractor($actionConfiguration, $filters);
165 4
        $loader->load($request);
166
    
167
        // load entities according to action and request
168
        $this
169 4
            ->entityLoader
170 4
            ->configure($actionConfiguration)
171
        ;
172
        
173
        $this
174 4
            ->load(
175 4
                $loader->getCriteria(),
176 4
                $loader->getOrder(),
177 4
                $loader->getMaxPerPage(),
178 4
                $loader->getPage()
179
            )
180
        ;
181
    }
182
    
183
    /**
184
     * Check if user is allowed to be here.
185
     *
186
     * @throws LogicException|AccessDeniedException
187
     */
188 24
    public function checkPermissions()
189
    {
190
        $user = $this
191 24
            ->tokenStorage
192 24
            ->getToken()
193 24
            ->getUser()
194
        ;
195
        
196
        // must be authenticated to access to an Admin
197 24
        if (!($user instanceof UserInterface)) {
198 4
            throw new AccessDeniedException();
199
        }
200
        
201
        // the current Action has to be defined
202 20
        if (null === $this->currentAction) {
203 4
            throw new LogicException(
204 4
                'Current action should be set before checking the permissions. Maybe you forget to call handleRequest()'
205
            );
206
        }
207
        // check if the current User is granted in Symfony security configuration
208 16
        if (!$this->authorizationChecker->isGranted($user->getRoles(), $user)) {
209 4
            throw new AccessDeniedException();
210
        }
211
        $permissions = $this
212 12
            ->currentAction
213 12
            ->getConfiguration()
214 12
            ->getParameter('permissions')
215
        ;
216
        
217
        // check if the User is granted according to Admin configuration
218 12
        if (!$this->authorizationChecker->isGranted($permissions, $user)) {
219 4
            throw new AccessDeniedException();
220
        }
221 8
    }
222
    
223
    /**
224
     * Create and return a new entity.
225
     *
226
     * @return object
227
     */
228 12
    public function create()
229
    {
230
        // create an entity from the data provider
231
        $entity = $this
232 12
            ->dataProvider
233 12
            ->create();
234
        
235
        // add it to the collection
236
        $this
237 12
            ->entities
238 12
            ->add($entity);
239
        
240 12
        return $entity;
241
    }
242
    
243
    /**
244
     * Save entity via admin manager.
245
     */
246 4
    public function save()
247
    {
248 4
        foreach ($this->entities as $entity) {
249
            $this
250 4
                ->dataProvider
251 4
                ->save($entity)
252
            ;
253
        }
254
        // inform the user that the entity is saved
255
        $this
256 4
            ->messageHandler
257 4
            ->handleSuccess($this->generateMessageTranslationKey('saved'))
258
        ;
259 4
    }
260
    
261
    /**
262
     * Remove an entity with data provider.
263
     */
264 4
    public function remove()
265
    {
266 4
        foreach ($this->entities as $entity) {
267
            $this
268 4
                ->dataProvider
269 4
                ->remove($entity);
270
        }
271
        // inform the user that the entity is removed
272
        $this
273 4
            ->messageHandler
274 4
            ->handleSuccess($this->generateMessageTranslationKey('deleted'))
275
        ;
276 4
    }
277
    
278
    /**
279
     * Return the number of entities managed by the Admin.
280
     *
281
     * @return int
282
     */
283
    public function count()
284
    {
285
        $count = $this
286
            ->dataProvider
287
            ->count()
288
        ;
289
    
290
        if (!is_integer($count)) {
291
            throw new LogicException(
292
                'The data provider should return an integer for the "count()" method, given : '.gettype($count)
293
            );
294
        }
295
    
296
        return $count;
297
    }
298
    
299
    /**
300
     * Generate a route for admin and action name (like lag.admin.my_admin)
301
     *
302
     * @param $actionName
303
     *
304
     * @return string
305
     *
306
     * @throws Exception
307
     */
308 4
    public function generateRouteName($actionName)
309
    {
310
        $actions = $this
311 4
            ->configuration
312 4
            ->getParameter('actions')
313
        ;
314
        
315 4
        if (!array_key_exists($actionName, $actions)) {
316 4
            throw new Exception(
317 4
                sprintf('Invalid action name %s for admin %s (available action are: %s)',
318 4
                    $actionName,
319 4
                    $this->getName(),
320 4
                    implode(', ', $this->getActionNames()))
321
            );
322
        }
323
        // get routing name pattern
324
        $routingPattern = $this
325 4
            ->configuration
326 4
            ->getParameter('routing_name_pattern')
327
        ;
328
        
329
        // replace admin and action name in pattern
330 4
        $routeName = str_replace('{admin}', strtolower($this->name), $routingPattern);
331 4
        $routeName = str_replace('{action}', $actionName, $routeName);
332
        
333 4
        return $routeName;
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 8
    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 8
            ->entityLoader
349 8
            ->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 8
        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 = $entities->getCurrentPageResults();
0 ignored issues
show
Documentation Bug introduced by
It seems like $entities->getCurrentPageResults() of type array or object<Traversable> is incompatible with the declared type object<Doctrine\Common\C...ctions\ArrayCollection> of property $entities.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
356
            $this->pager = $entities;
357
        } else {
358
            // the data provider should return an array or a collection of entities.
359 8
            if (!is_array($entities) && !$entities instanceof Collection) {
360 4
                throw new LogicException(
361 4
                    '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 4
            if (is_array($entities)) {
367 4
                $entities = new ArrayCollection($entities);
368
            }
369 4
            $this->entities = $entities;
370
        }
371 4
    }
372
    
373
    /**
374
     * Return loaded entities
375
     *
376
     * @return Collection
377
     */
378 4
    public function getEntities()
379
    {
380 4
        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 4
    public function getName()
412
    {
413 4
        return $this->name;
414
    }
415
    
416
    /**
417
     * @return ActionInterface[]
418
     */
419 20
    public function getActions()
420
    {
421 20
        return $this->actions;
422
    }
423
    
424
    /**
425
     * @return integer[]
426
     */
427 4
    public function getActionNames()
428
    {
429 4
        return array_keys($this->actions);
430
    }
431
    
432
    /**
433
     * @param $name
434
     * @return ActionInterface
435
     * @throws Exception
436
     */
437 20
    public function getAction($name)
438
    {
439 20
        if (!array_key_exists($name, $this->getActions())) {
440
            throw new Exception(
441
                "Invalid action name \"{$name}\" for admin '{$this->getName()}'. Check your configuration"
442
            );
443
        }
444
        
445 20
        return $this->actions[$name];
446
    }
447
    
448
    /**
449
     * Return true if the Action with name $name exists in the Admin. If the method return true, it does not necessarily
450
     * means that the action is allowed in the current context.
451
     *
452
     * @param string $name
453
     *
454
     * @return boolean
455
     */
456
    public function hasAction($name)
457
    {
458
        return array_key_exists($name, $this->actions);
459
    }
460
    
461
    /**
462
     * @param ActionInterface $action
463
     * @return void
464
     */
465 24
    public function addAction(ActionInterface $action)
466
    {
467 24
        $this->actions[$action->getName()] = $action;
468 24
    }
469
    
470
    /**
471
     * Return the current action or an exception if it is not set.
472
     *
473
     * @return ActionInterface
474
     * @throws Exception
475
     */
476
    public function getCurrentAction()
477
    {
478
        if ($this->currentAction === null) {
479
            // current action should be defined
480
            throw new Exception(
481
                'Current action is null. You should initialize it (with handleRequest method for example)'
482
            );
483
        }
484
        
485
        return $this->currentAction;
486
    }
487
    
488
    /**
489
     * Return if the current action has been initialized and set.
490
     *
491
     * @return boolean
492
     */
493
    public function isCurrentActionDefined()
494
    {
495
        return ($this->currentAction instanceof ActionInterface);
496
    }
497
    
498
    /**
499
     * Return admin configuration object.
500
     *
501
     * @return AdminConfiguration
502
     */
503
    public function getConfiguration()
504
    {
505
        return $this->configuration;
506
    }
507
    
508
    /**
509
     * Return the filter form if it was initialized.
510
     *
511
     * @return FormInterface
512
     *
513
     * @throws Exception
514
     */
515
    public function getFilterForm()
516
    {
517
        if (null === $this->filterForm) {
518
            throw new Exception(
519
                'The filter form is null. Check you have configured filters. You should initialize the filter form 
520
                (with $admin->handleRequest() method for example)'
521
            );
522
        }
523
        
524
        return $this->filterForm;
525
    }
526
    
527
    /**
528
     * Return true if the filter form has been set.
529
     *
530
     * @return bool
531
     */
532
    public function hasFilterForm()
533
    {
534
        return null !== $this->filterForm;
535
    }
536
    
537
    /**
538
     * Return a translation key for a message according to the Admin's translation pattern.
539
     *
540
     * @param string $message
541
     * @return string
542
     */
543 8
    protected function generateMessageTranslationKey($message)
544
    {
545 8
        return $this->getTranslationKey(
546 8
            $this->configuration->getParameter('translation_pattern'),
547 8
            $message,
548 8
            $this->name
549
        );
550
    }
551
}
552