Completed
Push — master ( 178ca9...6dd7fd )
by Eric
24s queued 20s
created

Repository::getProperty()   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 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 2
crap 2
1
<?php
2
3
/*
4
 * This file is part of the Lug package.
5
 *
6
 * (c) Eric GELOEN <[email protected]>
7
 *
8
 * For the full copyright and license information, please read the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Lug\Component\Resource\Repository\Doctrine\ORM;
13
14
use Doctrine\ORM\EntityManagerInterface;
15
use Doctrine\ORM\EntityRepository;
16
use Doctrine\ORM\Mapping\ClassMetadata;
17
use Doctrine\ORM\QueryBuilder;
18
use Lug\Component\Grid\DataSource\Doctrine\ORM\DataSourceBuilder;
19
use Lug\Component\Resource\Model\ResourceInterface;
20
use Lug\Component\Resource\Repository\RepositoryInterface;
21
use Pagerfanta\Adapter\DoctrineORMAdapter;
22
use Pagerfanta\Pagerfanta;
23
24
/**
25
 * @author GeLo <[email protected]>
26
 */
27
class Repository extends EntityRepository implements RepositoryInterface
28
{
29
    /**
30
     * @var ResourceInterface
31
     */
32
    private $resource;
33
34 22
    /**
35
     * @param EntityManagerInterface $em
36 22
     * @param ClassMetadata          $class
37 22
     * @param ResourceInterface      $resource
38
     */
39
    public function __construct(EntityManagerInterface $em, ClassMetadata $class, ResourceInterface $resource)
40
    {
41
        parent::__construct($em, $class);
42
43
        $this->resource = $resource;
44
    }
45
46
    /**
47
     * {@inheritdoc}
48
     */
49
    public function findForIndex(array $criteria, array $orderBy = [])
50
    {
51
        return new Pagerfanta(new DoctrineORMAdapter(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \Pagerfanta\P... true), false, false)); (Pagerfanta\Pagerfanta) is incompatible with the return type declared by the interface Lug\Component\Resource\R...Interface::findForIndex of type object[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
52
            $this->buildQueryBuilder($criteria, $orderBy, true),
53
            false,
54
            false
55
        ));
56
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61
    public function findForShow(array $criteria, array $orderBy = [])
62
    {
63
        return $this->findOneBy($criteria, $orderBy);
64
    }
65
66
    /**
67
     * {@inheritdoc}
68
     */
69
    public function findForUpdate(array $criteria, array $orderBy = [])
70
    {
71
        return $this->findOneBy($criteria, $orderBy);
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    public function findForDelete(array $criteria, array $orderBy = [])
78
    {
79
        return $this->findOneBy($criteria, $orderBy);
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public function findOneBy(array $criteria, array $orderBy = [])
86
    {
87
        return $this->buildQueryBuilder($criteria, $orderBy)->getQuery()->getOneOrNullResult();
88
    }
89
90
    /**
91
     * {@inheritdoc}
92
     */
93 View Code Duplication
    public function findBy(array $criteria, array $orderBy = [], $limit = null, $offset = null)
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...
94
    {
95
        $queryBuilder = $this->buildQueryBuilder($criteria, $orderBy);
96
97
        if ($limit !== null) {
98
            $queryBuilder->setMaxResults($limit);
99
        }
100
101
        if ($offset !== null) {
102
            $queryBuilder->setFirstResult($offset);
103
        }
104
105
        return $queryBuilder->getQuery()->getResult();
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111
    public function createQueryBuilder($alias = null, $indexBy = null)
112
    {
113
        return parent::createQueryBuilder($alias ?: $this->getAlias(), $indexBy);
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function createQueryBuilderForCollection($alias = null, $indexBy = null)
120
    {
121
        return $this->createQueryBuilder($alias, $indexBy);
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function createDataSourceBuilder(array $options = [])
128
    {
129
        return new DataSourceBuilder($this, $options);
130
    }
131
132
    /**
133
     * @param string                   $property
134
     * @param QueryBuilder|string|null $root
135
     *
136
     * @return string
137
     */
138
    public function getProperty($property, $root = null)
139
    {
140
        return $this->getRootAlias($root).'.'.$property;
141
    }
142
143
    /**
144
     * @param mixed[]  $criteria
145
     * @param string[] $orderBy
146
     * @param bool     $collection
147
     *
148
     * @return QueryBuilder
149
     */
150 View Code Duplication
    protected function buildQueryBuilder(array $criteria, array $orderBy, $collection = false)
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...
151
    {
152
        $queryBuilder = $collection ? $this->createQueryBuilderForCollection() : $this->createQueryBuilder();
153
154
        $this->applyCriteria($queryBuilder, $criteria);
155
        $this->applySorting($queryBuilder, $orderBy);
156
157
        return $queryBuilder;
158
    }
159
160
    /**
161
     * @param QueryBuilder $queryBuilder
162
     * @param mixed[]      $criteria
163
     */
164
    private function applyCriteria(QueryBuilder $queryBuilder, array $criteria = null)
165
    {
166
        foreach ($criteria as $property => $value) {
0 ignored issues
show
Bug introduced by
The expression $criteria of type null|array<integer,*> 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...
167
            if ($value === null) {
168
                $queryBuilder->andWhere($queryBuilder->expr()->isNull(
169
                    $this->getProperty($property, $queryBuilder)
170
                ));
171
            } elseif (is_array($value)) {
172
                $queryBuilder
173
                    ->andWhere($queryBuilder->expr()->in(
174
                        $property = $this->getProperty($property, $queryBuilder),
175
                        $this->createPlaceholder($parameter = $this->createParameter($property))
176
                    ))
177
                    ->setParameter($parameter, $value);
178
            } elseif ($value !== null) {
179
                $queryBuilder
180
                    ->andWhere($queryBuilder->expr()->eq(
181
                        $property = $this->getProperty($property, $queryBuilder),
182
                        $this->createPlaceholder($parameter = $this->createParameter($property))
183
                    ))
184
                    ->setParameter($parameter, $value);
185
            }
186
        }
187
    }
188
189
    /**
190
     * @param QueryBuilder $queryBuilder
191
     * @param string[]     $orderBy
192
     */
193 View Code Duplication
    private function applySorting(QueryBuilder $queryBuilder, array $orderBy = [])
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...
194
    {
195
        foreach ($orderBy as $property => $order) {
196
            if (!empty($order)) {
197
                $queryBuilder->addOrderBy($this->getProperty($property, $queryBuilder), $order);
198
            }
199
        }
200
    }
201
202
    /**
203
     * @param string $property
204
     *
205
     * @return string
206
     */
207
    private function createParameter($property)
208
    {
209
        return str_replace('.', '_', $property).'_'.str_replace('.', '', uniqid(null, true));
210
    }
211
212
    /**
213
     * @param string $parameter
214
     *
215
     * @return string
216
     */
217
    private function createPlaceholder($parameter)
218
    {
219
        return ':'.$parameter;
220
    }
221
222
    /**
223
     * @param QueryBuilder|string|null $root
224
     *
225
     * @return string
226
     */
227
    private function getRootAlias($root)
228
    {
229
        if ($root instanceof QueryBuilder) {
230
            $root = $root->getRootAliases()[0];
231
        }
232
233
        return $root;
234
    }
235
236
    /**
237
     * @return string
238
     */
239
    private function getAlias()
240
    {
241
        return $this->resource->getName();
242
    }
243
}
244