Completed
Push — 0.3-dev ( 29e489...87cbea )
by Arnaud
03:16
created

Admin   C

Complexity

Total Complexity 37

Size/Duplication

Total Lines 435
Duplicated Lines 11.03 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 26
Bugs 8 Features 4
Metric Value
wmc 37
c 26
b 8
f 4
lcom 1
cbo 17
dl 48
loc 435
rs 6.7571

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
B handleRequest() 0 33 3
A checkPermissions() 0 18 3
A create() 0 14 1
B save() 24 24 3
B remove() 24 24 3
A generateRouteName() 0 14 2
B load() 0 28 3
A getEntities() 0 4 1
A getUniqueEntity() 0 10 3
A getName() 0 4 1
B isActionGranted() 0 23 5
A getActions() 0 4 1
A getActionNames() 0 4 1
A getAction() 0 8 2
A hasAction() 0 4 1
A addAction() 0 4 1
A getCurrentAction() 0 4 1
A getConfiguration() 0 4 1

How to fix   Duplicated Code   

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:

1
<?php
2
3
namespace LAG\AdminBundle\Admin;
4
5
use ArrayIterator;
6
use Doctrine\ORM\EntityManagerInterface;
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\DataProvider\DataProviderInterface;
12
use LAG\AdminBundle\Exception\AdminException;
13
use LAG\AdminBundle\Filter\PagerfantaFilter;
14
use LAG\AdminBundle\Filter\RequestFilter;
15
use LAG\AdminBundle\Message\MessageHandlerInterface;
16
use LAG\AdminBundle\Pager\PagerFantaAdminAdapter;
17
use Pagerfanta\Pagerfanta;
18
use Symfony\Component\DependencyInjection\Container;
19
use Symfony\Component\HttpFoundation\ParameterBag;
20
use Symfony\Component\HttpFoundation\Request;
21
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
22
use Symfony\Component\Security\Core\Role\Role;
23
use Symfony\Component\Security\Core\User\UserInterface;
24
25
class Admin implements AdminInterface
26
{
27
    use AdminTrait;
28
29
    /**
30
     * Do not load entities on handleRequest (for create method for example)
31
     */
32
    const LOAD_STRATEGY_NONE = 'strategy_none';
33
34
    /**
35
     * Load one entity on handleRequest (edit method for example)
36
     */
37
    const LOAD_STRATEGY_UNIQUE = 'strategy_unique';
38
39
    /**
40
     * Load multiple entities on handleRequest (list method for example)
41
     */
42
    const LOAD_STRATEGY_MULTIPLE = 'strategy_multiple';
43
44
    /**
45
     * Entities collection.
46
     *
47
     * @var ArrayCollection|ArrayIterator
48
     */
49
    protected $entities;
50
51
    /**
52
     * @var MessageHandlerInterface
53
     */
54
    protected $messageHandler;
55
56
    /**
57
     * @var EntityManagerInterface
58
     */
59
    protected $entityManager;
60
61
    /**
62
     * @var DataProviderInterface
63
     */
64
    protected $dataProvider;
65
66
    /**
67
     * Admin configuration object
68
     *
69
     * @var AdminConfiguration
70
     */
71
    protected $configuration;
72
73
    /**
74
     * Admin configured actions
75
     *
76
     * @var ActionInterface[]
77
     */
78
    protected $actions = [];
79
80
    /**
81
     * Admin current action. It will be set after calling the handleRequest()
82
     *
83
     * @var ActionInterface
84
     */
85
    protected $currentAction;
86
87
    /**
88
     * Admin name
89
     *
90
     * @var string
91
     */
92
    protected $name;
93
94
    /**
95
     * Admin constructor.
96
     *
97
     * @param string $name
98
     * @param DataProviderInterface $dataProvider
99
     * @param AdminConfiguration $configuration
100
     * @param MessageHandlerInterface $messageHandler
101
     */
102
    public function __construct(
103
        $name,
104
        DataProviderInterface $dataProvider,
105
        AdminConfiguration $configuration,
106
        MessageHandlerInterface $messageHandler
107
    ) {
108
        $this->name = $name;
109
        $this->dataProvider = $dataProvider;
110
        $this->configuration = $configuration;
111
        $this->messageHandler = $messageHandler;
112
        $this->entities = new ArrayCollection();
113
    }
114
115
    /**
116
     * Load entities and set current action according to request
117
     *
118
     * @param Request $request
119
     * @param null $user
120
     * @return void
121
     * @throws AdminException
122
     */
123
    public function handleRequest(Request $request, $user = null)
124
    {
125
        // set current action
126
        $this->currentAction = $this->getAction($request->get('_route_params')['_action']);
127
        // check if user is logged have required permissions to get current action
128
        $this->checkPermissions($user);
129
130
        // criteria filter request
131
        $filter = new RequestFilter($this->currentAction->getConfiguration()->getCriteria());
132
        $criteriaFilter = $filter->filter($request);
133
134
        // pager filter request
135
        if ($this->currentAction->getConfiguration()->getPager() == 'pagerfanta') {
136
            $filter = new PagerfantaFilter();
137
            $pagerFilter = $filter->filter($request);
138
        } else {
139
            // empty bag
140
            $pagerFilter = new ParameterBag();
141
        }
142
143
        // if load strategy is none, no entity should be loaded
144
        if ($this->currentAction->getConfiguration()->getLoadStrategy() == Admin::LOAD_STRATEGY_NONE) {
145
            return;
146
        }
147
148
        // load entities according to action and request
149
        $this->load(
150
            $criteriaFilter->all(),
151
            $pagerFilter->get('order', []),
152
            $this->configuration->getMaxPerPage(),
153
            $pagerFilter->get('page', 1)
154
        );
155
    }
156
157
    /**
158
     * Check if user is allowed to be here
159
     *
160
     * @param UserInterface|string $user
161
     */
162
    public function checkPermissions($user)
163
    {
164
        if (!$user) {
165
            return;
166
        }
167
        $roles = $user->getRoles();
0 ignored issues
show
Bug introduced by
It seems like $user is not always an object, but can also be of type string. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
168
        $actionName = $this
169
            ->getCurrentAction()
170
            ->getName();
171
172
        if (!$this->isActionGranted($actionName, $roles)) {
173
            $message = sprintf('User with roles %s not allowed for action "%s"',
174
                implode(', ', $roles),
175
                $actionName
176
            );
177
            throw new NotFoundHttpException($message);
178
        }
179
    }
180
181
    /**
182
     * Create and return a new entity.
183
     *
184
     * @return object
185
     */
186
    public function create()
187
    {
188
        // create an entity from the data provider
189
        $entity = $this
190
            ->dataProvider
191
            ->create();
192
193
        // add it to the collection
194
        $this
0 ignored issues
show
Bug introduced by
The method add does only exist in Doctrine\Common\Collections\ArrayCollection, but not in ArrayIterator.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
195
            ->entities
196
            ->add($entity);
197
198
        return $entity;
199
    }
200
201
    /**
202
     * Save entity via admin manager. Error are catch, logged and a flash message is added to session
203
     *
204
     * @return bool true if the entity was saved without errors
205
     */
206 View Code Duplication
    public function save()
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...
207
    {
208
        try {
209
            foreach ($this->entities as $entity) {
210
                $this
211
                    ->dataProvider
212
                    ->save($entity);
213
            }
214
            // inform user everything went fine
215
            $this
216
                ->messageHandler
217
                ->handleSuccess('lag.admin.' . $this->name . '.saved');
218
            $success = true;
219
        } catch (Exception $e) {
220
            $this
221
                ->messageHandler
222
                ->handleError(
223
                    'lag.admin.saved_errors',
224
                    "An error has occurred while saving an entity : {$e->getMessage()}, stackTrace: {$e->getTraceAsString()}"
225
                );
226
            $success = false;
227
        }
228
        return $success;
229
    }
230
231
    /**
232
     * Remove an entity with data provider
233
     *
234
     * @return bool true if the entity was saved without errors
235
     */
236 View Code Duplication
    public function remove()
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...
237
    {
238
        try {
239
            foreach ($this->entities as $entity) {
240
                $this
241
                    ->dataProvider
242
                    ->remove($entity);
243
            }
244
            // inform user everything went fine
245
            $this
246
                ->messageHandler
247
                ->handleSuccess('lag.admin.' . $this->name . '.deleted');
248
            $success = true;
249
        } catch (Exception $e) {
250
            $this
251
                ->messageHandler
252
                ->handleError(
253
                    'lag.admin.deleted_errors',
254
                    "An error has occurred while deleting an entity : {$e->getMessage()}, stackTrace: {$e->getTraceAsString()} "
255
                );
256
            $success = false;
257
        }
258
        return $success;
259
    }
260
261
    /**
262
     * Generate a route for admin and action name (like lag.admin.my_admin)
263
     *
264
     * @param $actionName
265
     *
266
     * @return string
267
     *
268
     * @throws Exception
269
     */
270
    public function generateRouteName($actionName)
271
    {
272
        if (!array_key_exists($actionName, $this->getConfiguration()->getActions())) {
273
            $message = 'Invalid action name %s for admin %s (available action are: %s)';
274
            throw new Exception(sprintf($message, $actionName, $this->getName(), implode(', ', $this->getActionNames())));
275
        }
276
        // get routing name pattern
277
        $routingPattern = $this->getConfiguration()->getRoutingNamePattern();
278
        // replace admin and action name in pattern
279
        $routeName = str_replace('{admin}', Container::underscore($this->getName()), $routingPattern);
280
        $routeName = str_replace('{action}', $actionName, $routeName);
281
282
        return $routeName;
283
    }
284
285
    /**
286
     * Load entities manually according to criteria.
287
     *
288
     * @param array $criteria
289
     * @param array $orderBy
290
     * @param int $limit
291
     * @param int $offset
292
     */
293
    public function load(array $criteria, $orderBy = [], $limit = 25, $offset = 1)
294
    {
295
        $pager = $this
296
            ->getCurrentAction()
297
            ->getConfiguration()
298
            ->getPager();
299
300
        if ($pager == 'pagerfanta') {
301
            // adapter to pager fanta
302
            $adapter = new PagerFantaAdminAdapter($this->dataProvider, $criteria, $orderBy);
303
            // create pager
304
            $this->pager = new Pagerfanta($adapter);
305
            $this->pager->setMaxPerPage($limit);
306
            $this->pager->setCurrentPage($offset);
307
308
            $entities = $this
309
                ->pager
310
                ->getCurrentPageResults();
311
        } else {
312
            $entities = $this
313
                ->dataProvider
314
                ->findBy($criteria, $orderBy, $limit, $offset);
315
        }
316
        if (is_array($entities)) {
317
            $entities = new ArrayCollection($entities);
318
        }
319
        $this->entities = $entities;
320
    }
321
322
    /**
323
     * Return loaded entities
324
     *
325
     * @return mixed
326
     */
327
    public function getEntities()
328
    {
329
        return $this->entities;
330
    }
331
332
    /**
333
     * Return entity for current admin. If entity does not exist, it throws an exception.
334
     *
335
     * @return mixed
336
     *
337
     * @throws Exception
338
     */
339
    public function getUniqueEntity()
340
    {
341
        if ($this->entities->count() == 0) {
342
            throw new Exception("Entity not found in admin \"{$this->getName()}\".");
343
        }
344
        if ($this->entities->count() > 1) {
345
            throw new Exception("Too much entities found in admin \"{$this->getName()}\".");
346
        }
347
        return $this->entities->first();
0 ignored issues
show
Bug introduced by
The method first does only exist in Doctrine\Common\Collections\ArrayCollection, but not in ArrayIterator.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
348
    }
349
350
    /**
351
     * Return admin name
352
     *
353
     * @return string
354
     */
355
    public function getName()
356
    {
357
        return $this->name;
358
    }
359
360
    /**
361
     * Return true if current action is granted for user.
362
     *
363
     * @param string $actionName Le plus grand de tous les héros
364
     * @param array $roles
365
     *
366
     * @return bool
367
     */
368
    public function isActionGranted($actionName, array $roles)
369
    {
370
        $isGranted = array_key_exists($actionName, $this->actions);
371
372
        // if action exists
373
        if ($isGranted) {
374
            $isGranted = false;
375
            /** @var Action $action */
376
            $action = $this->actions[$actionName];
377
            // checking roles permissions
378
            foreach ($roles as $role) {
379
380
                if ($role instanceof Role) {
381
                    $role = $role->getRole();
382
                }
383
                if (in_array($role, $action->getPermissions())) {
384
                    $isGranted = true;
385
                }
386
            }
387
        }
388
389
        return $isGranted;
390
    }
391
392
    /**
393
     * @return ActionInterface[]
394
     */
395
    public function getActions()
396
    {
397
        return $this->actions;
398
    }
399
400
    /**
401
     * @return array
402
     */
403
    public function getActionNames()
404
    {
405
        return array_keys($this->actions);
406
    }
407
408
    /**
409
     * @param $name
410
     * @return ActionInterface
411
     * @throws Exception
412
     */
413
    public function getAction($name)
414
    {
415
        if (!array_key_exists($name, $this->getActions())) {
416
            throw new Exception("Invalid action name \"{$name}\" for admin '{$this->getName()}'");
417
        }
418
419
        return $this->actions[$name];
420
    }
421
422
    /**
423
     * Return if an action with specified name exists form this admin.
424
     *
425
     * @param $name
426
     * @return bool
427
     */
428
    public function hasAction($name)
429
    {
430
        return array_key_exists($name, $this->actions);
431
    }
432
433
    /**
434
     * @param ActionInterface $action
435
     * @return void
436
     */
437
    public function addAction(ActionInterface $action)
438
    {
439
        $this->actions[$action->getName()] = $action;
440
    }
441
442
    /**
443
     * @return ActionInterface
444
     */
445
    public function getCurrentAction()
446
    {
447
        return $this->currentAction;
448
    }
449
450
    /**
451
     * Return admin configuration object
452
     *
453
     * @return AdminConfiguration
454
     */
455
    public function getConfiguration()
456
    {
457
        return $this->configuration;
458
    }
459
}
460