Completed
Push — master ( c399a2...425bd8 )
by Julián
05:48
created

RelationalRepository::findBy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 4
1
<?php
2
3
/*
4
 * doctrine-orm-repositories (https://github.com/juliangut/doctrine-orm-repositories).
5
 * Doctrine2 ORM utility entity repositories.
6
 *
7
 * @license MIT
8
 * @link https://github.com/juliangut/doctrine-orm-repositories
9
 * @author Julián Gutiérrez <[email protected]>
10
 */
11
12
declare(strict_types=1);
13
14
namespace Jgut\Doctrine\Repository\ORM;
15
16
use Doctrine\Common\Collections\ArrayCollection;
17
use Doctrine\Common\Util\ClassUtils;
18
use Doctrine\ORM\EntityManager;
19
use Doctrine\ORM\EntityRepository;
20
use Doctrine\ORM\Query;
21
use Doctrine\ORM\QueryBuilder;
22
use Doctrine\ORM\Tools\Pagination\Paginator as RelationalPaginator;
23
use Happyr\DoctrineSpecification\EntitySpecificationRepositoryInterface;
24
use Happyr\DoctrineSpecification\EntitySpecificationRepositoryTrait;
25
use Jgut\Doctrine\Repository\EventsTrait;
26
use Jgut\Doctrine\Repository\FiltersTrait;
27
use Jgut\Doctrine\Repository\PaginatorTrait;
28
use Jgut\Doctrine\Repository\Repository;
29
use Jgut\Doctrine\Repository\RepositoryTrait;
30
use Zend\Paginator\Paginator;
31
32
/**
33
 * Relational entity repository.
34
 */
35
class RelationalRepository extends EntityRepository implements Repository, EntitySpecificationRepositoryInterface
36
{
37
    use RepositoryTrait;
38
    use EventsTrait;
39
    use FiltersTrait;
40
    use PaginatorTrait;
41
    use EntitySpecificationRepositoryTrait;
42
43
    /**
44
     * Class name.
45
     *
46
     * @var string
47
     */
48
    protected $className;
49
50
    /**
51
     * Class alias.
52
     *
53
     * @var string
54
     */
55
    protected $classAlias;
56
57
    /**
58
     * Get class alias.
59
     *
60
     * @return string
61
     */
62
    protected function getClassAlias(): string
63
    {
64
        if ($this->classAlias === null) {
65
            $this->classAlias = strtoupper($this->getEntityName()[0]);
66
        }
67
68
        return $this->classAlias;
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function getClassName(): string
75
    {
76
        if ($this->className === null) {
77
            $this->className = ClassUtils::getRealClass($this->getEntityName());
78
        }
79
80
        return $this->className;
81
    }
82
83
    /**
84
     * Finds entities by a set of criteria.
85
     *
86
     * @param array      $criteria
87
     * @param array|null $orderBy
88
     * @param int|null   $limit
89
     * @param int|null   $offset
90
     *
91
     * @return ArrayCollection
92
     *
93
     * @codeCoverageIgnore
94
     */
95
    public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
96
    {
97
        return new ArrayCollection(parent::findBy($criteria, $orderBy, $limit, $offset));
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \Doctrine\Com...rBy, $limit, $offset)); (Doctrine\Common\Collections\ArrayCollection) is incompatible with the return type declared by the interface Doctrine\Common\Persiste...bjectRepository::findBy of type array.

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...
98
    }
99
100
    /**
101
     * {@inheritdoc}
102
     */
103
    protected function getFilterCollection()
104
    {
105
        return $this->getManager()->getFilters();
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111
    protected function getManager(): EntityManager
112
    {
113
        return $this->getEntityManager();
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     *
119
     * @param array      $criteria
120
     * @param array|null $orderBy
121
     * @param int        $itemsPerPage
122
     *
123
     * @return \Zend\Paginator\Paginator
124
     */
125
    public function findPaginatedBy($criteria, array $orderBy = null, int $itemsPerPage = 10): Paginator
126
    {
127
        $queryBuilder = $this->getQueryBuilderFromCriteria($criteria, $orderBy);
128
129
        return $this->paginate($queryBuilder->getQuery(), $itemsPerPage);
130
    }
131
132
    /**
133
     * Get query builder from criteria array.
134
     *
135
     * @param array      $criteria
136
     * @param array|null $orderBy
137
     *
138
     * @return QueryBuilder
139
     */
140
    protected function getQueryBuilderFromCriteria(array $criteria, array $orderBy = null): QueryBuilder
141
    {
142
        $entityAlias = $this->getClassAlias();
143
        $queryBuilder = $this->createQueryBuilder($entityAlias);
144
        $entityAlias = !empty($queryBuilder->getRootAliases()) ? $queryBuilder->getRootAliases()[0] : $entityAlias;
145
146
        foreach ($criteria as $field => $value) {
147
            $this->addQueryCriteria($queryBuilder, $field, $value, $entityAlias);
148
        }
149
150
        if (is_array($orderBy)) {
151
            foreach ($orderBy as $field => $order) {
152
                $queryBuilder->addOrderBy($entityAlias . '.' . $field, $order);
153
            }
154
        }
155
156
        return $queryBuilder;
157
    }
158
159
    /**
160
     * Add query builder criteria.
161
     *
162
     * @param QueryBuilder $queryBuilder
163
     * @param string       $field
164
     * @param mixed        $value
165
     * @param string       $entityAlias
166
     */
167
    protected function addQueryCriteria(QueryBuilder $queryBuilder, string $field, $value, string $entityAlias)
168
    {
169
        if ($value === null) {
170
            $queryBuilder->andWhere(sprintf('%s.%s IS NULL', $entityAlias, $field));
171
        } else {
172
            $placeholder = sprintf('%s_%s', $field, substr(sha1($field), 0, 5));
173
174
            if (is_array($value)) {
175
                $queryBuilder->andWhere(sprintf('%s.%s IN (:%s)', $entityAlias, $field, $placeholder));
176
            } else {
177
                $queryBuilder->andWhere(sprintf('%s.%s = :%s', $entityAlias, $field, $placeholder));
178
            }
179
180
            $queryBuilder->setParameter($placeholder, $value);
181
        }
182
    }
183
184
    /**
185
     * Paginate query.
186
     *
187
     * @param Query $query
188
     * @param int   $itemsPerPage
189
     *
190
     * @return Paginator
191
     */
192
    protected function paginate(Query $query, int $itemsPerPage = 10): Paginator
193
    {
194
        return $this->getPaginator(new RelationalPaginatorAdapter(new RelationalPaginator($query)), $itemsPerPage);
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     *
200
     * @param array|QueryBuilder $criteria
201
     *
202
     * @return int
203
     */
204
    public function countBy($criteria): int
205
    {
206
        return $this->getEntityManager()->getUnitOfWork()->getEntityPersister($this->getEntityName())->count($criteria);
207
    }
208
209
    /**
210
     * Begin transaction.
211
     */
212
    public function beginTransaction()
213
    {
214
        $this->getManager()->getConnection()->beginTransaction();
215
    }
216
217
    /**
218
     * Commit transaction.
219
     *
220
     * @throws \Doctrine\DBAL\ConnectionException
221
     */
222
    public function commit()
223
    {
224
        $this->getManager()->getConnection()->commit();
225
    }
226
227
    /**
228
     * Rollback transaction.
229
     *
230
     * @throws \Doctrine\DBAL\ConnectionException
231
     */
232
    public function rollBack()
233
    {
234
        $this->getManager()->getConnection()->rollBack();
235
    }
236
}
237