Completed
Pull Request — master (#1709)
by Andreas
16:45 queued 14:38
created

DocumentRepository   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 274
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 61.73%

Importance

Changes 0
Metric Value
wmc 32
lcom 1
cbo 13
dl 0
loc 274
ccs 50
cts 81
cp 0.6173
rs 9.6
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getDocumentName() 0 4 1
A getDocumentManager() 0 4 1
A getClassMetadata() 0 4 1
A getClassName() 0 4 1
A getDocumentPersister() 0 4 1
A __construct() 0 7 1
A createQueryBuilder() 0 4 1
A createAggregationBuilder() 0 4 1
A clear() 0 4 1
D find() 0 45 10
A findAll() 0 4 1
A findBy() 0 4 1
A findOneBy() 0 4 1
B matching() 0 25 5
B __call() 0 30 5
1
<?php
2
3
namespace Doctrine\ODM\MongoDB;
4
5
use Doctrine\Common\Collections\ArrayCollection;
6
use Doctrine\Common\Collections\Collection;
7
use Doctrine\Common\Collections\Criteria;
8
use Doctrine\Common\Collections\Selectable;
9
use Doctrine\Common\Inflector\Inflector;
10
use Doctrine\Common\Persistence\ObjectRepository;
11
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
12
use Doctrine\ODM\MongoDB\Query\QueryExpressionVisitor;
13
14
/**
15
 * A DocumentRepository serves as a repository for documents with generic as well as
16
 * business specific methods for retrieving documents.
17
 *
18
 * This class is designed for inheritance and users can subclass this class to
19
 * write their own repositories with business-specific methods to locate documents.
20
 *
21
 * @since       1.0
22
 */
23
class DocumentRepository implements ObjectRepository, Selectable
24
{
25
    /**
26
     * @var string
27
     */
28
    protected $documentName;
29
30
    /**
31
     * @var DocumentManager
32
     */
33
    protected $dm;
34
35
    /**
36
     * @var UnitOfWork
37
     */
38
    protected $uow;
39
40
    /**
41
     * @var ClassMetadata
42
     */
43
    protected $class;
44
45
    /**
46
     * Initializes this instance with the specified document manager, unit of work and
47
     * class metadata.
48
     *
49
     * @param DocumentManager $dm The DocumentManager to use.
50
     * @param UnitOfWork $uow The UnitOfWork to use.
51
     * @param ClassMetadata $classMetadata The class metadata.
52
     */
53 329
    public function __construct(DocumentManager $dm, UnitOfWork $uow, ClassMetadata $classMetadata)
54
    {
55 329
        $this->documentName = $classMetadata->name;
56 329
        $this->dm = $dm;
57 329
        $this->uow = $uow;
58 329
        $this->class = $classMetadata;
59 329
    }
60
61
    /**
62
     * Creates a new Query\Builder instance that is preconfigured for this document name.
63
     *
64
     * @return Query\Builder $qb
65
     */
66 16
    public function createQueryBuilder()
67
    {
68 16
        return $this->dm->createQueryBuilder($this->documentName);
69
    }
70
71
    /**
72
     * Creates a new Aggregation\Builder instance that is prepopulated for this document name.
73
     *
74
     * @return Aggregation\Builder
75
     */
76
    public function createAggregationBuilder()
77
    {
78
        return $this->dm->createAggregationBuilder($this->documentName);
79
    }
80
81
    /**
82
     * Clears the repository, causing all managed documents to become detached.
83
     */
84
    public function clear()
85
    {
86
        $this->dm->clear($this->class->rootDocumentName);
87
    }
88
89
    /**
90
     * Finds a document matching the specified identifier. Optionally a lock mode and
91
     * expected version may be specified.
92
     *
93
     * @param mixed $id Identifier.
94
     * @param int $lockMode Optional. Lock mode; one of the LockMode constants.
95
     * @param int $lockVersion Optional. Expected version.
96
     * @throws Mapping\MappingException
97
     * @throws LockException
98
     * @return object|null The document, if found, otherwise null.
99
     */
100 234
    public function find($id, $lockMode = LockMode::NONE, $lockVersion = null)
101
    {
102 234
        if ($id === null) {
103
            return null;
104
        }
105
106
        /* TODO: What if the ID object has a field with the same name as the
107
         * class' mapped identifier field name?
108
         */
109 234
        if (is_array($id)) {
110 2
            list($identifierFieldName) = $this->class->getIdentifierFieldNames();
111
112 2
            if (isset($id[$identifierFieldName])) {
113
                $id = $id[$identifierFieldName];
114
            }
115
        }
116
117
        // Check identity map first
118 234
        if ($document = $this->uow->tryGetById($id, $this->class)) {
119 21
            if ($lockMode !== LockMode::NONE) {
120
                $this->dm->lock($document, $lockMode, $lockVersion);
121
            }
122
123 21
            return $document; // Hit!
124
        }
125
126 228
        $criteria = array('_id' => $id);
127
128 228
        if ($lockMode === LockMode::NONE) {
129 228
            return $this->getDocumentPersister()->load($criteria);
130
        }
131
132
        if ($lockMode === LockMode::OPTIMISTIC) {
133
            if (!$this->class->isVersioned) {
134
                throw LockException::notVersioned($this->documentName);
135
            }
136
            if ($document = $this->getDocumentPersister()->load($criteria)) {
137
                $this->uow->lock($document, $lockMode, $lockVersion);
138
            }
139
140
            return $document;
141
        }
142
143
        return $this->getDocumentPersister()->load($criteria, null, array(), $lockMode);
144
    }
145
146
    /**
147
     * Finds all documents in the repository.
148
     *
149
     * @return array
150
     */
151 10
    public function findAll()
152
    {
153 10
        return $this->findBy(array());
154
    }
155
156
    /**
157
     * Finds documents by a set of criteria.
158
     *
159
     * @param array        $criteria Query criteria
160
     * @param array        $sort     Sort array for Cursor::sort()
161
     * @param integer|null $limit    Limit for Cursor::limit()
162
     * @param integer|null $skip     Skip for Cursor::skip()
163
     *
164
     * @return array
165
     */
166 11
    public function findBy(array $criteria, array $sort = null, $limit = null, $skip = null)
167
    {
168 11
        return $this->getDocumentPersister()->loadAll($criteria, $sort, $limit, $skip)->toArray(false);
0 ignored issues
show
Unused Code introduced by
The call to Iterator::toArray() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
169
    }
170
171
    /**
172
     * Finds a single document by a set of criteria.
173
     *
174
     * @param array $criteria
175
     * @return object
176
     */
177 75
    public function findOneBy(array $criteria)
178
    {
179 75
        return $this->getDocumentPersister()->load($criteria);
180
    }
181
182
    /**
183
     * Adds support for magic finders.
184
     *
185
     * @param string $method
186
     * @param array $arguments
187
     * @throws MongoDBException
188
     * @throws \BadMethodCallException If the method called is an invalid find* method
189
     *                                 or no find* method at all and therefore an invalid
190
     *                                 method call.
191
     * @return array|object The found document/documents.
192
     *
193
     * @deprecated method was deprecated in 1.2 and will be removed in 2.0
194
     */
195 10
    public function __call($method, $arguments)
196
    {
197 10
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
198 10
            'Using magic findBy and findOneBy calls was deprecated in version 1.2 and will be removed altogether in 2.0.',
199 10
            E_USER_DEPRECATED
200
        );
201 10
        if (strpos($method, 'findBy') === 0) {
202
            $by = substr($method, 6, strlen($method));
203
            $method = 'findBy';
204 10
        } elseif (strpos($method, 'findOneBy') === 0) {
205 10
            $by = substr($method, 9, strlen($method));
206 10
            $method = 'findOneBy';
207
        } else {
208
            throw new \BadMethodCallException(
209
                "Undefined method: '$method'. The method name must start with 'findBy' or 'findOneBy'!"
210
            );
211
        }
212
213 10
        if (!isset($arguments[0])) {
214
            throw MongoDBException::findByRequiresParameter($method . $by);
215
        }
216
217 10
        $fieldName = Inflector::camelize($by);
218
219 10
        if ($this->class->hasField($fieldName)) {
220 10
            return $this->$method(array($fieldName => $arguments[0]));
221
        }
222
223
        throw MongoDBException::invalidFindByCall($this->documentName, $fieldName, $method . $by);
224
    }
225
226
    /**
227
     * @return string
228
     */
229
    public function getDocumentName()
230
    {
231
        return $this->documentName;
232
    }
233
234
    /**
235
     * @return DocumentManager
236
     */
237
    public function getDocumentManager()
238
    {
239
        return $this->dm;
240
    }
241
242
    /**
243
     * @return Mapping\ClassMetadata
244
     */
245
    public function getClassMetadata()
246
    {
247
        return $this->class;
248
    }
249
250
    /**
251
     * @return string
252
     */
253
    public function getClassName()
254
    {
255
        return $this->getDocumentName();
256
    }
257
258
    /**
259
     * Selects all elements from a selectable that match the expression and
260
     * returns a new collection containing these elements.
261
     *
262
     * @see Selectable::matching()
263
     * @param Criteria $criteria
264
     * @return Collection
265
     */
266 4
    public function matching(Criteria $criteria)
267
    {
268 4
        $visitor = new QueryExpressionVisitor($this->createQueryBuilder());
269 4
        $queryBuilder = $this->createQueryBuilder();
270
271 4
        if ($criteria->getWhereExpression() !== null) {
272 1
            $expr = $visitor->dispatch($criteria->getWhereExpression());
273 1
            $queryBuilder->setQueryArray($expr->getQuery());
274
        }
275
276 4
        if ($criteria->getMaxResults() !== null) {
277
            $queryBuilder->limit($criteria->getMaxResults());
278
        }
279
280 4
        if ($criteria->getFirstResult() !== null) {
281
            $queryBuilder->skip($criteria->getFirstResult());
282
        }
283
284 4
        if ($criteria->getOrderings() !== null) {
285 4
            $queryBuilder->sort($criteria->getOrderings());
286
        }
287
288
        // @TODO: wrap around a specialized Collection for efficient count on large collections
289 4
        return new ArrayCollection($queryBuilder->getQuery()->execute()->toArray());
290
    }
291
292 311
    protected function getDocumentPersister()
293
    {
294 311
        return $this->uow->getDocumentPersister($this->documentName);
295
    }
296
}
297