Completed
Push — master ( 1de9b7...830752 )
by Kristof
38:46 queued 24:09
created

AbstractDoctrineORMAdminListConfigurator   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 236
Duplicated Lines 13.56 %

Coupling/Cohesion

Components 2
Dependencies 10

Importance

Changes 0
Metric Value
wmc 25
lcom 2
cbo 10
dl 32
loc 236
rs 10
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getEditUrlFor() 10 10 1
A getDeleteUrlFor() 10 10 1
A getPagerfanta() 12 12 2
A adaptQueryBuilder() 0 4 1
A getCount() 0 4 1
A getItems() 0 4 1
A getIterator() 0 4 1
B getQuery() 0 41 9
A finishQueryBuilder() 0 6 2
A getQueryBuilder() 0 8 1
A getPermissionDefinition() 0 4 1
A setPermissionDefinition() 0 6 1
A setEntityManager() 0 6 1
A getEntityManager() 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 Kunstmaan\AdminListBundle\AdminList\Configurator;
4
5
use Doctrine\ORM\EntityManagerInterface;
6
use Doctrine\ORM\Query;
7
use Doctrine\ORM\QueryBuilder;
8
use Kunstmaan\AdminBundle\Helper\Security\Acl\AclHelper;
9
use Kunstmaan\AdminBundle\Helper\Security\Acl\Permission\PermissionDefinition;
10
use Kunstmaan\AdminListBundle\AdminList\Filter;
11
use Kunstmaan\AdminListBundle\AdminList\FilterType\ORM\AbstractORMFilterType;
12
use Kunstmaan\AdminListBundle\AdminList\SortableInterface;
13
use Pagerfanta\Adapter\DoctrineORMAdapter;
14
use Pagerfanta\Pagerfanta;
15
use Traversable;
16
17
/**
18
 * An abstract admin list configurator that can be used with the orm query builder
19
 */
20
abstract class AbstractDoctrineORMAdminListConfigurator extends AbstractAdminListConfigurator
21
{
22
    /**
23
     * @var EntityManagerInterface
24
     */
25
    protected $em;
26
27
    /**
28
     * @var Query
29
     */
30
    private $query = null;
31
32
    /**
33
     * @var Pagerfanta
34
     */
35
    private $pagerfanta = null;
36
37
    /**
38
     * @var PermissionDefinition
39
     */
40
    private $permissionDef = null;
41
42
    /**
43
     * @var AclHelper
44
     */
45
    protected $aclHelper = null;
46
47
    /**
48
     * AbstractDoctrineORMAdminListConfigurator constructor.
49
     *
50
     * @param EntityManagerInterface $em
51
     * @param AclHelper|null         $aclHelper
52
     */
53
    public function __construct(EntityManagerInterface $em, AclHelper $aclHelper = null)
54
    {
55
        $this->em = $em;
56
        $this->aclHelper = $aclHelper;
57
    }
58
59
    /**
60
     * Return the url to edit the given $item
61
     *
62
     * @param object $item
63
     *
64
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string|array>.

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...
65
     */
66 View Code Duplication
    public function getEditUrlFor($item)
0 ignored issues
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...
67
    {
68
        $params = array('id' => $item->getId());
69
        $params = array_merge($params, $this->getExtraParameters());
70
71
        return array(
72
            'path' => $this->getPathByConvention($this::SUFFIX_EDIT),
73
            'params' => $params,
74
        );
75
    }
76
77
    /**
78
     * Get the delete url for the given $item
79
     *
80
     * @param object $item
81
     *
82
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string|array>.

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...
83
     */
84 View Code Duplication
    public function getDeleteUrlFor($item)
0 ignored issues
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...
85
    {
86
        $params = array('id' => $item->getId());
87
        $params = array_merge($params, $this->getExtraParameters());
88
89
        return array(
90
            'path' => $this->getPathByConvention($this::SUFFIX_DELETE),
91
            'params' => $params,
92
        );
93
    }
94
95
    /**
96
     * @return Pagerfanta
97
     */
98 View Code Duplication
    public function getPagerfanta()
0 ignored issues
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...
99
    {
100
        if (is_null($this->pagerfanta)) {
101
            $adapter = new DoctrineORMAdapter($this->getQuery());
0 ignored issues
show
Bug introduced by
It seems like $this->getQuery() can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
102
            $this->pagerfanta = new Pagerfanta($adapter);
103
            $this->pagerfanta->setNormalizeOutOfRangePages(true);
104
            $this->pagerfanta->setMaxPerPage($this->getLimit());
105
            $this->pagerfanta->setCurrentPage($this->getPage());
106
        }
107
108
        return $this->pagerfanta;
109
    }
110
111
    /**
112
     * @param QueryBuilder $queryBuilder
113
     */
114
    public function adaptQueryBuilder(QueryBuilder $queryBuilder)
115
    {
116
        $queryBuilder->where('1=1');
117
    }
118
119
    /**
120
     * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|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...
121
     */
122
    public function getCount()
123
    {
124
        return $this->getPagerfanta()->getNbResults();
125
    }
126
127
    /**
128
     * @return array|Traversable
129
     */
130
    public function getItems()
131
    {
132
        return $this->getPagerfanta()->getCurrentPageResults();
133
    }
134
135
    /**
136
     * Return an iterator for all items that matches the current filtering
137
     *
138
     * @return \Iterator
139
     */
140
    public function getIterator()
141
    {
142
        return $this->getQuery()->iterate();
143
    }
144
145
    /**
146
     * @return Query|null
147
     */
148
    public function getQuery()
149
    {
150
        if (is_null($this->query)) {
151
            $queryBuilder = $this->getQueryBuilder();
152
            $this->adaptQueryBuilder($queryBuilder);
153
154
            // Apply filters
155
            $filters = $this->getFilterBuilder()->getCurrentFilters();
156
            /* @var Filter $filter */
157
            foreach ($filters as $filter) {
158
                /* @var AbstractORMFilterType $type */
159
                $type = $filter->getType();
160
                $type->setQueryBuilder($queryBuilder);
161
                $filter->apply();
162
            }
163
164
            // Apply sorting
165
            if (!empty($this->orderBy)) {
166
                $orderBy = $this->orderBy;
167
                if ($this->getEntityManager()->getClassMetadata($this->getRepositoryName())->hasAssociation($this->orderBy)) {
168
                    $queryBuilder->leftJoin('b.' . $orderBy, 'A' . $orderBy);
169
                    $orderBy = 'A' . $orderBy . '.id';
170
                } elseif (!strpos($orderBy, '.')) {
171
                    $orderBy = 'b.' . $orderBy;
172
                }
173
                $queryBuilder->orderBy($orderBy, ($this->orderDirection == 'DESC' ? 'DESC' : 'ASC'));
174
            }
175
176
            // Apply other changes
177
            $this->finishQueryBuilder($queryBuilder);
178
179
            // Apply ACL restrictions (if applicable)
180
            if (!is_null($this->permissionDef) && !is_null($this->aclHelper)) {
181
                $this->query = $this->aclHelper->apply($queryBuilder, $this->permissionDef);
182
            } else {
183
                $this->query = $queryBuilder->getQuery();
184
            }
185
        }
186
187
        return $this->query;
188
    }
189
190
    /**
191
     * @param QueryBuilder $queryBuilder
192
     */
193
    protected function finishQueryBuilder(QueryBuilder $queryBuilder)
194
    {
195
        if ($this instanceof SortableInterface) {
196
            $queryBuilder->addOrderBy('b.' . $this->getSortableField());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Kunstmaan\AdminListBundl...RMAdminListConfigurator as the method getSortableField() does only exist in the following sub-classes of Kunstmaan\AdminListBundl...RMAdminListConfigurator: Kunstmaan\AdminListBundl...inList\Configurator\ORM. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
197
        }
198
    }
199
200
    /**
201
     * @return QueryBuilder
202
     */
203
    protected function getQueryBuilder()
204
    {
205
        $queryBuilder = $this->em
206
            ->getRepository($this->getRepositoryName())
207
            ->createQueryBuilder('b');
208
209
        return $queryBuilder;
210
    }
211
212
    /**
213
     * Get current permission definition.
214
     *
215
     * @return PermissionDefinition|null
216
     */
217
    public function getPermissionDefinition()
218
    {
219
        return $this->permissionDef;
220
    }
221
222
    /**
223
     * Set permission definition.
224
     *
225
     * @param PermissionDefinition $permissionDef
226
     *
227
     * @return AbstractDoctrineORMAdminListConfigurator
228
     */
229
    public function setPermissionDefinition(PermissionDefinition $permissionDef)
230
    {
231
        $this->permissionDef = $permissionDef;
232
233
        return $this;
234
    }
235
236
    /**
237
     * @param EntityManagerInterface $em
238
     *
239
     * @return AbstractDoctrineORMAdminListConfigurator
240
     */
241
    public function setEntityManager(EntityManagerInterface $em)
242
    {
243
        $this->em = $em;
244
245
        return $this;
246
    }
247
248
    /**
249
     * @return EntityManagerInterface
250
     */
251
    public function getEntityManager()
252
    {
253
        return $this->em;
254
    }
255
}
256