Completed
Push — master ( 16ac11...a34a29 )
by Jacob
7s
created

Query::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
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->appendFields($builder, $fields);
96
        $this->appendSort($builder, $sort);
97
        $this->appendLimitAndOffset($builder, $limit, $offset);
98
99
        return $builder->getQuery()->execute();
100
    }
101
102
    /**
103
     * Executes an insert for the provided metadata.
104
     *
105
     * @param   EntityMetadata  $metadata
106
     * @param   array           $toInsert
107
     * @return  array|bool
108
     */
109
    public function executeInsert(EntityMetadata $metadata, array $toInsert)
110
    {
111
        return $this->createQueryBuilder($metadata)
112
            ->insert()
113
            ->setNewObj($toInsert)
114
            ->getQuery()
115
            ->execute()
116
        ;
117
    }
118
119
    /**
120
     * Updates a record from the database based on the provided metadata and criteria.
121
     *
122
     * @param   EntityMetadata  $metadata   The model metadata that the database should query against.
123
     * @param   Store           $store      The store.
124
     * @param   array           $criteria   The query criteria.
125
     * @param   array           $toUpdate   The data to update.
126
     * @return  array|bool
127
     */
128 View Code Duplication
    public function executeUpdate(EntityMetadata $metadata, Store $store, array $criteria, array $toUpdate)
129
    {
130
        $criteria = $this->getFormatter()->formatQuery($metadata, $store, $criteria);
131
        return $this->createQueryBuilder($metadata)
132
            ->update()
133
            ->setQueryArray($criteria)
134
            ->setNewObj($toUpdate)
135
            ->getQuery()
136
            ->execute();
137
        ;
138
    }
139
140
    /**
141
     * @return  Formatter
142
     */
143
    public function getFormatter()
144
    {
145
        return $this->formatter;
146
    }
147
148
    /**
149
     * Gets standard database retrieval criteria for an inverse relationship.
150
     *
151
     * @param   EntityMetadata  $owner
152
     * @param   EntityMetadata  $related
153
     * @param   string|array    $identifiers
154
     * @param   string          $inverseField
155
     * @return  array
156
     */
157
    public function getInverseCriteria(EntityMetadata $owner, EntityMetadata $related, $identifiers, $inverseField)
158
    {
159
        $criteria = [
160
            $inverseField   => (array) $identifiers,
161
        ];
162
        if (true === $owner->isChildEntity()) {
163
            // The owner is owned by a polymorphic model. Must include the type with the inverse field criteria.
164
            $criteria[$inverseField] = [
165
                Persister::IDENTIFIER_KEY   => $criteria[$inverseField],
166
                Persister::POLYMORPHIC_KEY  => $owner->type,
167
            ];
168
        }
169
        if (true === $related->isChildEntity()) {
170
            // The relationship is owned by a polymorphic model. Must include the type in the root criteria.
171
            $criteria[Persister::POLYMORPHIC_KEY] = $related->type;
172
        }
173
        return $criteria;
174
    }
175
176
    /**
177
     * Gets the MongoDB Collection object for a Model.
178
     *
179
     * @param   EntityMetadata  $metadata
180
     * @return  \Doctrine\MongoDB\Collection
181
     */
182
    public function getModelCollection(EntityMetadata $metadata)
183
    {
184
        if (!$metadata->persistence instanceof StorageMetadata) {
185
            throw PersisterException::badRequest('Wrong StorageMetadata type');
186
        }
187
        return $this->connection->selectCollection($metadata->persistence->db, $metadata->persistence->collection);
188
    }
189
190
    /**
191
     * Gets standard database retrieval criteria for an entity and the provided identifiers.
192
     *
193
     * @param   EntityMetadata      $metadata       The entity to retrieve database records for.
194
     * @param   string|array|null   $identifiers    The IDs to query.
195
     * @return  array
196
     */
197
    public function getRetrieveCritiera(EntityMetadata $metadata, $identifiers = null)
198
    {
199
        $criteria = [];
200
        if (true === $metadata->isChildEntity()) {
201
            $criteria[Persister::POLYMORPHIC_KEY] = $metadata->type;
202
        }
203
204
        $identifiers = (array) $identifiers;
205
        if (empty($identifiers)) {
206
            return $criteria;
207
        }
208
        $criteria[Persister::IDENTIFIER_KEY] = (1 === count($identifiers)) ? reset($identifiers) : $identifiers;
209
        return $criteria;
210
    }
211
212
    /**
213
     * Appends projection fields to a Query Builder.
214
     *
215
     * @param   QueryBuilder    $builder
216
     * @param   array           $fields
217
     * @return  self
218
     */
219
    private function appendFields(QueryBuilder $builder, array $fields)
220
    {
221
        list($fields, $include) = $this->prepareFields($fields);
222
        if (!empty($fields)) {
223
            $method = (true === $include) ? 'select' : 'exclude';
224
            $builder->$method(array_keys($fields));
225
        }
226
        return $this;
227
    }
228
229
    /**
230
     * Appends offset and limit criteria to a Query Builder
231
     *
232
     * @param   QueryBuilder    $builder
233
     * @param   int             $limit
234
     * @param   int             $offset
235
     * @return  self
236
     */
237
    private function appendLimitAndOffset(QueryBuilder $builder, $limit, $offset)
238
    {
239
        $limit = (int) $limit;
240
        $offset = (int) $offset;
241
242
        if ($limit > 0) {
243
            $builder->limit($limit);
244
        }
245
246
        if ($offset > 0) {
247
            $builder->skip($offset);
248
        }
249
        return $this;
250
    }
251
252
    /**
253
     * Appends sorting criteria to a Query Builder.
254
     *
255
     * @param   QueryBuilder    $builder
256
     * @param   array           $sort
257
     * @return  self
258
     */
259
    private function appendSort(QueryBuilder $builder, array $sort)
260
    {
261
        if (!empty($sort)) {
262
            $builder->sort($sort);
263
        }
264
        return $this;
265
    }
266
267
    /**
268
     * Prepares projection fields for a query and returns as a tuple.
269
     *
270
     * @param   array   $fields
271
     * @return  array
272
     * @throws  PersisterException
273
     */
274
    private function prepareFields(array $fields)
275
    {
276
        $include = null;
277
        foreach ($fields as $key => $type) {
278
            $type = (bool) $type;
279
            if (null === $include) {
280
                $include = $type;
281
            }
282
            if ($type !== $include) {
283
                PersisterException::badRequest('Field projection mismatch. You cannot both exclude and include fields.');
284
            }
285
            $fields[$key] = $type;
286
        }
287
        return [$fields, $include];
288
    }
289
}
290