Passed
Pull Request — 2.8.x (#7953)
by Grégoire
14:03
created

EntityRepository::resolveMagicCall()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 13
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 4
nc 3
nop 3
crap 4
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM;
21
22
use Doctrine\Common\Collections\Criteria;
23
use Doctrine\Common\Collections\Selectable;
24
use Doctrine\Common\Inflector\Inflector;
25
use Doctrine\ORM\Query\ResultSetMappingBuilder;
26
use Doctrine\Common\Collections\Selectable;
0 ignored issues
show
Bug introduced by
A parse error occurred: Cannot use Doctrine\Common\Collections\Selectable as Selectable because the name is already in use
Loading history...
27
use Doctrine\Common\Collections\Criteria;
28
use Doctrine\Persistence\ObjectRepository;
29
use const E_USER_DEPRECATED;
30
use function trigger_error;
31
32
/**
33
 * An EntityRepository serves as a repository for entities with generic as well as
34
 * business specific methods for retrieving entities.
35
 *
36
 * This class is designed for inheritance and users can subclass this class to
37
 * write their own repositories with business-specific methods to locate entities.
38
 *
39
 * @since   2.0
40
 * @author  Benjamin Eberlei <[email protected]>
41
 * @author  Guilherme Blanco <[email protected]>
42
 * @author  Jonathan Wage <[email protected]>
43
 * @author  Roman Borschel <[email protected]>
44
 */
45
class EntityRepository implements ObjectRepository, Selectable
46
{
47
    /**
48
     * @var string
49
     */
50
    protected $_entityName;
51
52
    /**
53
     * @var EntityManager
54
     */
55
    protected $_em;
56
57
    /**
58
     * @var \Doctrine\ORM\Mapping\ClassMetadata
59
     */
60
    protected $_class;
61
62
    /**
63 164
     * Initializes a new <tt>EntityRepository</tt>.
64
     */
65 164
    public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class)
66 164
    {
67 164
        $this->_entityName = $class->name;
68 164
        $this->_em         = $em;
69
        $this->_class      = $class;
70
    }
71
72
    /**
73
     * Creates a new QueryBuilder instance that is prepopulated for this entity name.
74
     *
75
     * @param string $alias
76
     * @param string $indexBy The index for the from.
77
     *
78 13
     * @return QueryBuilder
79
     */
80 13
    public function createQueryBuilder($alias, $indexBy = null)
81 13
    {
82 13
        return $this->_em->createQueryBuilder()
83
            ->select($alias)
84
            ->from($this->_entityName, $alias, $indexBy);
85
    }
86
87
    /**
88
     * Creates a new result set mapping builder for this entity.
89
     *
90
     * The column naming strategy is "INCREMENT".
91
     *
92
     * @param string $alias
93
     *
94 1
     * @return ResultSetMappingBuilder
95
     */
96 1
    public function createResultSetMappingBuilder($alias)
97 1
    {
98
        $rsm = new ResultSetMappingBuilder($this->_em, ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT);
99 1
        $rsm->addRootEntityFromClassMetadata($this->_entityName, $alias);
100
101
        return $rsm;
102
    }
103
104
    /**
105
     * Creates a new Query instance based on a predefined metadata named query.
106
     *
107
     * @param string $queryName
108
     *
109 3
     * @return Query
110
     */
111 3
    public function createNamedQuery($queryName)
112
    {
113
        return $this->_em->createQuery($this->_class->getNamedQuery($queryName));
114
    }
115
116
    /**
117
     * Creates a native SQL query.
118
     *
119
     * @param string $queryName
120
     *
121 7
     * @return NativeQuery
122
     */
123 7
    public function createNativeNamedQuery($queryName)
124 7
    {
125 7
        $queryMapping   = $this->_class->getNamedNativeQuery($queryName);
126
        $rsm            = new Query\ResultSetMappingBuilder($this->_em);
127 7
        $rsm->addNamedNativeQueryMapping($this->_class, $queryMapping);
128
129
        return $this->_em->createNativeQuery($queryMapping['query'], $rsm);
130
    }
131
132
    /**
133
     * Clears the repository, causing all managed entities to become detached.
134
     *
135
     * @return void
136
     */
137 1
    public function clear()
138
    {
139 1
        $this->_em->clear($this->_class->rootEntityName);
140
    }
141 1
142 1
    /**
143
     * Finds an entity by its primary key / identifier.
144
     *
145
     * @param mixed    $id          The identifier.
146
     * @param int|null $lockMode    One of the \Doctrine\DBAL\LockMode::* constants
147
     *                              or NULL if no specific lock mode should be used
148
     *                              during the search.
149
     * @param int|null $lockVersion The lock version.
150
     *
151
     * @return object|null The entity instance or NULL if the entity can not be found.
152
     */
153
    public function find($id, $lockMode = null, $lockVersion = null)
154
    {
155 16
        return $this->_em->find($this->_entityName, $id, $lockMode, $lockVersion);
156
    }
157 16
158
    /**
159
     * Finds all entities in the repository.
160
     *
161
     * @return array The entities.
162
     */
163
    public function findAll()
164
    {
165 34
        return $this->findBy([]);
166
    }
167 34
168
    /**
169
     * Finds entities by a set of criteria.
170
     *
171
     * @param array      $criteria
172
     * @param array|null $orderBy
173
     * @param int|null   $limit
174
     * @param int|null   $offset
175
     *
176
     * @return array The objects.
177
     */
178
    public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
179
    {
180 65
        $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
181
182 65
        return $persister->loadAll($criteria, $orderBy, $limit, $offset);
183
    }
184 65
185
    /**
186
     * Finds a single entity by a set of criteria.
187
     *
188
     * @param array      $criteria
189
     * @param array|null $orderBy
190
     *
191
     * @return object|null The entity instance or NULL if the entity can not be found.
192
     */
193
    public function findOneBy(array $criteria, array $orderBy = null)
194
    {
195 22
        $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
196
197 22
        return $persister->load($criteria, null, null, [], null, 1, $orderBy);
198
    }
199 22
200
    /**
201
     * Counts entities by a set of criteria.
202
     *
203
     * @todo Add this method to `ObjectRepository` interface in the next major release
204
     *
205
     * @param array $criteria
206
     *
207
     * @return int The cardinality of the objects that match the given criteria.
208
     */
209
    public function count(array $criteria)
210
    {
211 2
        return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->count($criteria);
212
    }
213 2
214
    /**
215
     * Adds support for magic method calls.
216
     *
217
     * @param string $method
218
     * @param array  $arguments
219
     *
220
     * @return mixed The returned value from the resolved method.
221
     *
222
     * @throws ORMException
223
     * @throws \BadMethodCallException If the method called is invalid
224
     */
225
    public function __call($method, $arguments)
226
    {
227 14
        if (0 === strpos($method, 'findBy')) {
228
            return $this->resolveMagicCall('findBy', substr($method, 6), $arguments);
229 14
        }
230 8
231
        if (0 === strpos($method, 'findOneBy')) {
232
            return $this->resolveMagicCall('findOneBy', substr($method, 9), $arguments);
233 6
        }
234 4
235
        if (0 === strpos($method, 'countBy')) {
236
            return $this->resolveMagicCall('count', substr($method, 7), $arguments);
237 2
        }
238 1
239
        throw new \BadMethodCallException(
240
            "Undefined method '$method'. The method name must start with ".
241 1
            "either findBy, findOneBy or countBy!"
242 1
        );
243 1
    }
244
245
    /**
246
     * @return string
247
     */
248
    protected function getEntityName()
249
    {
250
        return $this->_entityName;
251
    }
252
253
    /**
254
     * @return string
255
     */
256
    public function getClassName()
257
    {
258
        return $this->getEntityName();
259
    }
260
261
    /**
262
     * @return EntityManager
263
     */
264
    protected function getEntityManager()
265
    {
266
        return $this->_em;
267
    }
268
269
    /**
270
     * @return Mapping\ClassMetadata
271
     */
272
    protected function getClassMetadata()
273
    {
274
        return $this->_class;
275
    }
276
277
    /**
278
     * Select all elements from a selectable that match the expression and
279
     * return a new collection containing these elements.
280
     *
281
     * @param \Doctrine\Common\Collections\Criteria $criteria
282
     *
283
     * @return \Doctrine\Common\Collections\Collection
284
     */
285
    public function matching(Criteria $criteria)
286
    {
287 26
        $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
288
289 26
        return new LazyCriteriaCollection($persister, $criteria);
290
    }
291 26
292
    /**
293
     * Resolves a magic method call to the proper existent method at `EntityRepository`.
294
     *
295
     * @param string $method    The method to call
296
     * @param string $by        The property name used as condition
297
     * @param array  $arguments The arguments to pass at method call
298
     *
299
     * @throws ORMException If the method called is invalid or the requested field/association does not exist
300
     *
301
     * @return mixed
302
     */
303
    private function resolveMagicCall($method, $by, array $arguments)
304
    {
305 13
        if (! $arguments) {
306
            throw ORMException::findByRequiresParameter($method . $by);
307 13
        }
308 1
309
        $fieldName = lcfirst(Inflector::classify($by));
310
311 12
        if (! ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName))) {
312
            throw ORMException::invalidMagicCall($this->_entityName, $fieldName, $method . $by);
313 12
        }
314 1
315
        return $this->$method([$fieldName => $arguments[0]], ...array_slice($arguments, 1));
316
    }
317
}
318