Completed
Push — master ( 274fc3...7f7474 )
by Jacob
10s
created

Query::getRetrieveCritiera()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 11
nc 9
nop 2
1
<?php
2
3
namespace As3\Modlr\Persister\MongoDb;
4
5
use As3\Modlr\Metadata\EntityMetadata;
6
use As3\Modlr\Persister\PersisterException;
7
use As3\Modlr\Store\Store;
8
use Doctrine\MongoDB\Connection;
9
use Doctrine\MongoDB\Query\Builder as QueryBuilder;
10
11
/**
12
 * Handles query operations for a MongoDB database connection.
13
 *
14
 * @author Jacob Bare <[email protected]>
15
 */
16
final class Query
17
{
18
    /**
19
     * The Doctine MongoDB connection.
20
     *
21
     * @var Connection
22
     */
23
    private $connection;
24
25
    /**
26
     * The query/database operations formatter.
27
     *
28
     * @var Formatter
29
     */
30
    private $formatter;
31
32
    /**
33
     * Constructor.
34
     *
35
     * @param   Connection  $connection
36
     * @param   Formatter   $formatter
37
     */
38
    public function __construct(Connection $connection, Formatter $formatter)
39
    {
40
        $this->connection = $connection;
41
        $this->formatter = $formatter;
42
    }
43
44
    /**
45
     * Creates a builder object for querying MongoDB based on the provided metadata.
46
     *
47
     * @param   EntityMetadata  $metadata
48
     * @return  QueryBuilder
49
     */
50
    public function createQueryBuilder(EntityMetadata $metadata)
51
    {
52
        return $this->getModelCollection($metadata)->createQueryBuilder();
53
    }
54
55
    /**
56
     * Executes a delete for the provided metadata and criteria.
57
     *
58
     * @param   EntityMetadata  $metadata
59
     * @param   Store           $store
60
     * @param   array           $criteria
61
     * @return  array|bool
62
     */
63 View Code Duplication
    public function executeDelete(EntityMetadata $metadata, Store $store, array $criteria)
64
    {
65
        $criteria = $this->getFormatter()->formatQuery($metadata, $store, $criteria);
66
        return $this->createQueryBuilder($metadata)
67
            ->remove()
68
            ->setQueryArray($criteria)
69
            ->getQuery()
70
            ->execute();
71
        ;
72
    }
73
74
    /**
75
     * Finds records from the database based on the provided metadata and criteria.
76
     *
77
     * @param   EntityMetadata  $metadata   The model metadata that the database should query against.
78
     * @param   Store           $store      The store.
79
     * @param   array           $criteria   The query criteria.
80
     * @param   array           $fields     Fields to include/exclude.
81
     * @param   array           $sort       The sort criteria.
82
     * @param   int             $offset     The starting offset, aka the number of Models to skip.
83
     * @param   int             $limit      The number of Models to limit.
84
     * @return  \Doctrine\MongoDB\Cursor
85
     */
86
    public function executeFind(EntityMetadata $metadata, Store $store, array $criteria, array $fields = [], array $sort = [], $offset = 0, $limit = 0)
87
    {
88
        $criteria = $this->getFormatter()->formatQuery($metadata, $store, $criteria);
89
90
        $builder = $this->createQueryBuilder($metadata)
91
            ->find()
92
            ->setQueryArray($criteria)
93
        ;
94
95
        $this->appendSearch($builder, $criteria);
96
        $this->appendFields($builder, $fields);
97
        $this->appendSort($builder, $sort);
98
        $this->appendLimitAndOffset($builder, $limit, $offset);
99
100
        return $builder->getQuery()->execute();
101
    }
102
103
    /**
104
     * Executes an insert for the provided metadata.
105
     *
106
     * @param   EntityMetadata  $metadata
107
     * @param   array           $toInsert
108
     * @return  array|bool
109
     */
110
    public function executeInsert(EntityMetadata $metadata, array $toInsert)
111
    {
112
        return $this->createQueryBuilder($metadata)
113
            ->insert()
114
            ->setNewObj($toInsert)
115
            ->getQuery()
116
            ->execute()
117
        ;
118
    }
119
120
    /**
121
     * Updates a record from the database based on the provided metadata and criteria.
122
     *
123
     * @param   EntityMetadata  $metadata   The model metadata that the database should query against.
124
     * @param   Store           $store      The store.
125
     * @param   array           $criteria   The query criteria.
126
     * @param   array           $toUpdate   The data to update.
127
     * @return  array|bool
128
     */
129 View Code Duplication
    public function executeUpdate(EntityMetadata $metadata, Store $store, array $criteria, array $toUpdate)
130
    {
131
        $criteria = $this->getFormatter()->formatQuery($metadata, $store, $criteria);
132
        return $this->createQueryBuilder($metadata)
133
            ->update()
134
            ->setQueryArray($criteria)
135
            ->setNewObj($toUpdate)
136
            ->getQuery()
137
            ->execute();
138
        ;
139
    }
140
141
    /**
142
     * @return  Formatter
143
     */
144
    public function getFormatter()
145
    {
146
        return $this->formatter;
147
    }
148
149
    /**
150
     * Gets standard database retrieval criteria for an inverse relationship.
151
     *
152
     * @param   EntityMetadata  $owner
153
     * @param   EntityMetadata  $related
154
     * @param   string|array    $identifiers
155
     * @param   string          $inverseField
156
     * @return  array
157
     */
158
    public function getInverseCriteria(EntityMetadata $owner, EntityMetadata $related, $identifiers, $inverseField)
0 ignored issues
show
Unused Code introduced by
The parameter $owner is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
159
    {
160
        $criteria = [
161
            $inverseField   => (array) $identifiers,
162
        ];
163
        if (true === $related->isChildEntity()) {
164
            // The relationship is owned by a polymorphic model. Must include the type in the root criteria.
165
            $criteria[Persister::POLYMORPHIC_KEY] = $related->type;
166
        }
167
        return $criteria;
168
    }
169
170
    /**
171
     * Gets the MongoDB Collection object for a Model.
172
     *
173
     * @param   EntityMetadata  $metadata
174
     * @return  \Doctrine\MongoDB\Collection
175
     */
176
    public function getModelCollection(EntityMetadata $metadata)
177
    {
178
        if (!$metadata->persistence instanceof StorageMetadata) {
179
            throw PersisterException::badRequest('Wrong StorageMetadata type');
180
        }
181
        return $this->connection->selectCollection($metadata->persistence->db, $metadata->persistence->collection);
182
    }
183
184
    /**
185
     * Gets standard database retrieval criteria for an entity and the provided identifiers.
186
     *
187
     * @param   EntityMetadata      $metadata       The entity to retrieve database records for.
188
     * @param   string|array|null   $identifiers    The IDs to query.
189
     * @return  array
190
     */
191
    public function getRetrieveCritiera(EntityMetadata $metadata, $identifiers = null)
192
    {
193
        $criteria = [];
194
        if (true === $metadata->isChildEntity()) {
195
            $criteria[Persister::POLYMORPHIC_KEY] = $metadata->type;
196
        } elseif (true === $metadata->isPolymorphic() && false === $metadata->isAbstract()) {
197
            $criteria[Persister::POLYMORPHIC_KEY] = $metadata->type;
198
        }
199
200
        $identifiers = (array) $identifiers;
201
        if (empty($identifiers)) {
202
            return $criteria;
203
        }
204
        $criteria[Persister::IDENTIFIER_KEY] = (1 === count($identifiers)) ? reset($identifiers) : $identifiers;
205
        return $criteria;
206
    }
207
208
    /**
209
     * Appends projection fields to a Query Builder.
210
     *
211
     * @param   QueryBuilder    $builder
212
     * @param   array           $fields
213
     * @return  self
214
     */
215
    private function appendFields(QueryBuilder $builder, array $fields)
216
    {
217
        list($fields, $include) = $this->prepareFields($fields);
218
        if (!empty($fields)) {
219
            $method = (true === $include) ? 'select' : 'exclude';
220
            $builder->$method(array_keys($fields));
221
        }
222
        return $this;
223
    }
224
225
    /**
226
     * Appends offset and limit criteria to a Query Builder
227
     *
228
     * @param   QueryBuilder    $builder
229
     * @param   int             $limit
230
     * @param   int             $offset
231
     * @return  self
232
     */
233
    private function appendLimitAndOffset(QueryBuilder $builder, $limit, $offset)
234
    {
235
        $limit = (int) $limit;
236
        $offset = (int) $offset;
237
238
        if ($limit > 0) {
239
            $builder->limit($limit);
240
        }
241
242
        if ($offset > 0) {
243
            $builder->skip($offset);
244
        }
245
        return $this;
246
    }
247
248
    /**
249
     * Appends text search score and sorting to a Query Builder.
250
     *
251
     * @param   QueryBuilder    $builder
252
     * @param   array           $criteria
253
     * @return  self
254
     */
255
    private function appendSearch(QueryBuilder $builder, array $criteria)
256
    {
257
        if (false === $this->isSearchQuery($criteria)) {
258
            return $this;
259
        }
260
        $builder->selectMeta('searchScore', 'textScore');
261
        $builder->sortMeta('searchScore', 'textScore');
262
        return $this;
263
    }
264
265
    /**
266
     * Appends sorting criteria to a Query Builder.
267
     *
268
     * @param   QueryBuilder    $builder
269
     * @param   array           $sort
270
     * @return  self
271
     */
272
    private function appendSort(QueryBuilder $builder, array $sort)
273
    {
274
        if (!empty($sort)) {
275
            $builder->sort($sort);
276
        }
277
        return $this;
278
    }
279
280
    /**
281
     * Determines if the provided query criteria contains text search.
282
     *
283
     * @param   array   $criteria
284
     * @return  bool
285
     */
286
    private function isSearchQuery(array $criteria)
287
    {
288
        if (isset($criteria['$text'])) {
289
            return true;
290
        }
291
        foreach ($criteria as $key => $value) {
292
            if (is_array($value) && true === $this->isSearchQuery($value)) {
293
                return true;
294
            }
295
        }
296
        return false;
297
    }
298
299
    /**
300
     * Prepares projection fields for a query and returns as a tuple.
301
     *
302
     * @param   array   $fields
303
     * @return  array
304
     * @throws  PersisterException
305
     */
306
    private function prepareFields(array $fields)
307
    {
308
        $include = null;
309
        foreach ($fields as $key => $type) {
310
            $type = (bool) $type;
311
            if (null === $include) {
312
                $include = $type;
313
            }
314
            if ($type !== $include) {
315
                PersisterException::badRequest('Field projection mismatch. You cannot both exclude and include fields.');
316
            }
317
            $fields[$key] = $type;
318
        }
319
        return [$fields, $include];
320
    }
321
}
322