Completed
Pull Request — master (#5)
by Jacob
03:12
created

Persister::getIdentifierCriteria()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 18
rs 8.8571
cc 5
eloc 13
nc 4
nop 1
1
<?php
2
3
namespace As3\Modlr\Persister\MongoDb;
4
5
use As3\Modlr\Store\Store;
6
use As3\Modlr\Models\Model;
7
use As3\Modlr\Models\Collection;
8
use As3\Modlr\Metadata\EntityMetadata;
9
use As3\Modlr\Metadata\AttributeMetadata;
10
use As3\Modlr\Metadata\RelationshipMetadata;
11
use As3\Modlr\Persister\PersisterInterface;
12
use As3\Modlr\Persister\PersisterException;
13
use As3\Modlr\Persister\Record;
14
use Doctrine\MongoDB\Connection;
15
use \MongoId;
16
17
/**
18
 * Persists and retrieves models to/from a MongoDB database.
19
 *
20
 * @author Jacob Bare <[email protected]>
21
 */
22
final class Persister implements PersisterInterface
23
{
24
    const IDENTIFIER_KEY    = '_id';
25
    const POLYMORPHIC_KEY   = '_type';
26
    const PERSISTER_KEY     = 'mongodb';
27
28
    /**
29
     * The Doctine MongoDB connection.
30
     *
31
     * @var Connection
32
     */
33
    private $connection;
34
35
    /**
36
     * The query/database operations formatter.
37
     *
38
     * @var Formatter
39
     */
40
    private $formatter;
41
42
    /**
43
     * @var StorageMetadataFactory
44
     */
45
    private $smf;
46
47
    /**
48
     * Constructor.
49
     *
50
     * @param   Connection              $connection
51
     * @param   StorageMetadataFactory  $smf
52
     */
53
    public function __construct(Connection $connection, StorageMetadataFactory $smf)
54
    {
55
        $this->connection = $connection;
56
        $this->formatter = new Formatter();
57
        $this->smf = $smf;
58
59
    }
60
61
    /**
62
     * {@inheritDoc}
63
     */
64
    public function getPersisterKey()
65
    {
66
        return self::PERSISTER_KEY;
67
    }
68
69
    /**
70
     * {@inheritDoc}
71
     */
72
    public function getPersistenceMetadataFactory()
73
    {
74
        return $this->smf;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->smf; (As3\Modlr\Persister\MongoDb\StorageMetadataFactory) is incompatible with the return type declared by the interface As3\Modlr\Persister\Pers...sistenceMetadataFactory of type As3\Modlr\Metadata\Inter...etadataFactoryInterface.

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...
75
    }
76
77
    /**
78
     * {@inheritDoc}
79
     * @todo    Implement sorting and pagination (limit/skip).
80
     */
81
    public function all(EntityMetadata $metadata, Store $store, array $identifiers = [])
82
    {
83
        $criteria = $this->getRetrieveCritiera($metadata, $identifiers);
84
        $cursor = $this->doQuery($metadata, $store, $criteria);
85
        return $this->hydrateRecords($metadata, $cursor->toArray(), $store);
86
    }
87
88
    /**
89
     * {@inheritDoc}
90
     */
91
    public function query(EntityMetadata $metadata, Store $store, array $criteria, array $fields = [], array $sort = [], $offset = 0, $limit = 0)
92
    {
93
        $cursor = $this->doQuery($metadata, $store, $criteria);
94
        return $this->hydrateRecords($metadata, $cursor->toArray(), $store);
95
    }
96
97
    /**
98
     * {@inheritDoc}
99
     */
100
    public function inverse(EntityMetadata $owner, EntityMetadata $rel, Store $store, array $identifiers, $inverseField)
101
    {
102
        $criteria = $this->getInverseCriteria($owner, $rel, $identifiers, $inverseField);
103
        $cursor = $this->doQuery($rel, $store, $criteria);
104
        return $this->hydrateRecords($rel, $cursor->toArray(), $store);
105
    }
106
107
    /**
108
     * {@inheritDoc}
109
     */
110
    public function retrieve(EntityMetadata $metadata, $identifier, Store $store)
111
    {
112
        $criteria = $this->getRetrieveCritiera($metadata, $identifier);
113
        $result = $this->doQuery($metadata, $store, $criteria)->getSingleResult();
114
        if (null === $result) {
115
            return;
116
        }
117
        return $this->hydrateRecord($metadata, $result, $store);
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->doQuery($metadata...ria)->getSingleResult() on line 113 can also be of type object; however, As3\Modlr\Persister\Mong...sister::hydrateRecord() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
118
    }
119
120
    /**
121
     * {@inheritDoc}
122
     * @todo    Optimize the changeset to query generation.
123
     */
124
    public function create(Model $model)
125
    {
126
        $metadata = $model->getMetadata();
127
        $insert[$this->getIdentifierKey()] = $this->convertId($model->getId());
0 ignored issues
show
Coding Style Comprehensibility introduced by
$insert was never initialized. Although not strictly required by PHP, it is generally a good practice to add $insert = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
128
        if (true === $metadata->isChildEntity()) {
129
            $insert[$this->getPolymorphicKey()] = $metadata->type;
130
        }
131
132
        $changeset = $model->getChangeSet();
133 View Code Duplication
        foreach ($changeset['attributes'] as $key => $values) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
134
            $value = $this->getFormatter()->getAttributeDbValue($metadata->getAttribute($key), $values['new']);
0 ignored issues
show
Bug introduced by
It seems like $metadata->getAttribute($key) can be null; however, getAttributeDbValue() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
135
            if (null === $value) {
136
                continue;
137
            }
138
            $insert[$key] = $value;
139
        }
140 View Code Duplication
        foreach ($changeset['hasOne'] as $key => $values) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
141
            $value = $this->getFormatter()->getHasOneDbValue($metadata->getRelationship($key), $values['new']);
0 ignored issues
show
Bug introduced by
It seems like $metadata->getRelationship($key) can be null; however, getHasOneDbValue() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
142
            if (null === $value) {
143
                continue;
144
            }
145
            $insert[$key] = $value;
146
        }
147 View Code Duplication
        foreach ($changeset['hasMany'] as $key => $values) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
148
            $value = $this->getFormatter()->getHasManyDbValue($metadata->getRelationship($key), $values['new']);
0 ignored issues
show
Bug introduced by
It seems like $metadata->getRelationship($key) can be null; however, getHasManyDbValue() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
149
            if (null === $value) {
150
                continue;
151
            }
152
            $insert[$key] = $value;
153
        }
154
        $this->createQueryBuilder($metadata)
155
            ->insert()
156
            ->setNewObj($insert)
157
            ->getQuery()
158
            ->execute()
159
        ;
160
        return $model;
161
    }
162
163
    /**
164
     * {@inheritDoc}
165
     * @todo    Optimize the changeset to query generation.
166
     */
167
    public function update(Model $model)
168
    {
169
        $metadata = $model->getMetadata();
170
        $criteria = $this->getRetrieveCritiera($metadata, $model->getId());
171
        $changeset = $model->getChangeSet();
172
173
        $update = [];
174 View Code Duplication
        foreach ($changeset['attributes'] as $key => $values) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
175
            if (null === $values['new']) {
176
                $op = '$unset';
177
                $value = 1;
178
            } else {
179
                $op = '$set';
180
                $value = $this->getFormatter()->getAttributeDbValue($metadata->getAttribute($key), $values['new']);
0 ignored issues
show
Bug introduced by
It seems like $metadata->getAttribute($key) can be null; however, getAttributeDbValue() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
181
            }
182
            $update[$op][$key] = $value;
183
        }
184
185
        // @todo Must prevent inverse relationships from persisting
186 View Code Duplication
        foreach ($changeset['hasOne'] as $key => $values) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
187
            if (null === $values['new']) {
188
                $op = '$unset';
189
                $value = 1;
190
            } else {
191
                $op = '$set';
192
                $value = $this->getFormatter()->getHasOneDbValue($metadata->getRelationship($key), $values['new']);
0 ignored issues
show
Bug introduced by
It seems like $metadata->getRelationship($key) can be null; however, getHasOneDbValue() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
193
            }
194
            $update[$op][$key] = $value;
195
        }
196
197 View Code Duplication
        foreach ($changeset['hasMany'] as $key => $values) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
198
            if (null === $values['new']) {
199
                $op = '$unset';
200
                $value = 1;
201
            } else {
202
                $op = '$set';
203
                $value = $this->getFormatter()->getHasManyDbValue($metadata->getRelationship($key), $values['new']);
0 ignored issues
show
Bug introduced by
It seems like $metadata->getRelationship($key) can be null; however, getHasManyDbValue() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
204
            }
205
            $update[$op][$key] = $value;
206
        }
207
208
        if (empty($update)) {
209
            return $model;
210
        }
211
212
        $this->createQueryBuilder($metadata)
213
            ->update()
214
            ->setQueryArray($criteria)
215
            ->setNewObj($update)
216
            ->getQuery()
217
            ->execute();
218
        ;
219
        return $model;
220
    }
221
222
    /**
223
     * {@inheritDoc}
224
     */
225
    public function delete(Model $model)
226
    {
227
        $metadata = $model->getMetadata();
228
        $criteria = $this->getRetrieveCritiera($metadata, $model->getId());
229
230
        $this->createQueryBuilder($metadata)
231
            ->remove()
232
            ->setQueryArray($criteria)
233
            ->getQuery()
234
            ->execute();
235
        ;
236
        return $model;
237
    }
238
239
    /**
240
     * {@inheritDoc}
241
     */
242
    public function generateId($strategy = null)
243
    {
244
        if (false === $this->getFormatter()->isIdStrategySupported($strategy)) {
245
            throw PersisterException::nyi('ID generation currently only supports an object strategy, or none at all.');
246
        }
247
        return new MongoId();
248
    }
249
250
    /**
251
     * @return  Formatter
252
     */
253
    public function getFormatter()
254
    {
255
        return $this->formatter;
256
    }
257
258
    /**
259
     * {@inheritDoc}
260
     */
261
    public function convertId($identifier, $strategy = null)
262
    {
263
        return $this->getFormatter()->getIdentifierDbValue($identifier, $strategy);
264
    }
265
266
    /**
267
     * {@inheritDoc}
268
     */
269
    public function getIdentifierKey()
270
    {
271
        return self::IDENTIFIER_KEY;
272
    }
273
274
    /**
275
     * {@inheritDoc}
276
     */
277
    public function getPolymorphicKey()
278
    {
279
        return self::POLYMORPHIC_KEY;
280
    }
281
282
    /**
283
     * {@inheritDoc}
284
     */
285
    public function extractType(EntityMetadata $metadata, array $data)
286
    {
287
        if (false === $metadata->isPolymorphic()) {
288
            return $metadata->type;
289
        }
290
        if (!isset($data[$this->getPolymorphicKey()])) {
291
            throw PersisterException::badRequest(sprintf('Unable to extract polymorphic type. The "%s" key was not found.', $this->getPolymorphicKey()));
292
        }
293
        return $data[$this->getPolymorphicKey()];
294
    }
295
296
    /**
297
     * Finds records from the database based on the provided metadata and criteria.
298
     *
299
     * @param   EntityMetadata  $metadata   The model metadata that the database should query against.
300
     * @param   Store           $store      The store.
301
     * @param   array           $criteria   The query criteria.
302
     * @param   array           $fields     Fields to include/exclude.
303
     * @param   array           $sort       The sort criteria.
304
     * @param   int             $offset     The starting offset, aka the number of Models to skip.
305
     * @param   int             $limit      The number of Models to limit.
306
     * @return  \Doctrine\MongoDB\Cursor
307
     */
308
    protected function doQuery(EntityMetadata $metadata, Store $store, array $criteria, array $fields = [], array $sort = [], $offset = 0, $limit = 0)
0 ignored issues
show
Unused Code introduced by
The parameter $fields 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...
Unused Code introduced by
The parameter $sort 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...
Unused Code introduced by
The parameter $offset 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...
Unused Code introduced by
The parameter $limit 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...
309
    {
310
        $criteria = $this->getFormatter()->formatQuery($metadata, $store, $criteria);
311
        return $this->createQueryBuilder($metadata)
312
            ->find()
313
            ->setQueryArray($criteria)
314
            ->getQuery()
315
            ->execute()
316
        ;
317
    }
318
319
    /**
320
     * Processes multiple, raw MongoDB results an converts them into an array of standardized Record objects.
321
     *
322
     * @param   EntityMetadata  $metadata
323
     * @param   array           $results
324
     * @param   Store           $store
325
     * @return  Record[]
326
     */
327
    protected function hydrateRecords(EntityMetadata $metadata, array $results, Store $store)
328
    {
329
        $records = [];
330
        foreach ($results as $data) {
331
            $records[] = $this->hydrateRecord($metadata, $data, $store);
332
        }
333
        return $records;
334
    }
335
336
    /**
337
     * Processes raw MongoDB data an converts it into a standardized Record object.
338
     *
339
     * @param   EntityMetadata  $metadata
340
     * @param   array           $data
341
     * @param   Store           $store
342
     * @return  Record
343
     */
344
    protected function hydrateRecord(EntityMetadata $metadata, array $data, Store $store)
345
    {
346
        $identifier = $data[$this->getIdentifierKey()];
347
        unset($data[$this->getIdentifierKey()]);
348
349
        $type = $this->extractType($metadata, $data);
350
        unset($data[$this->getPolymorphicKey()]);
351
352
        $metadata = $store->getMetadataForType($type);
353
        foreach ($metadata->getRelationships() as $key => $relMeta) {
354
            if (!isset($data[$key])) {
355
                continue;
356
            }
357
            if (true === $relMeta->isMany() && !is_array($data[$key])) {
358
                throw PersisterException::badRequest(sprintf('Relationship key "%s" is a reference many. Expected record data type of array, "%s" found on model "%s" for identifier "%s"', $key, gettype($data[$key]), $type, $identifier));
359
            }
360
            $references = $relMeta->isOne() ? [$data[$key]] : $data[$key];
361
362
            $extracted = [];
363
            foreach ($references as $reference) {
364
                $extracted[] =  $this->extractRelationship($relMeta, $reference);
365
            }
366
            $data[$key] = $relMeta->isOne() ? reset($extracted) : $extracted;
367
        }
368
        return new Record($type, $identifier, $data);
369
    }
370
371
    /**
372
     * Extracts a standard relationship array that the store expects from a raw MongoDB reference value.
373
     *
374
     * @param   RelationshipMetadata    $relMeta
375
     * @param   mixed                   $reference
376
     * @return  array
377
     * @throws  \RuntimeException   If the relationship could not be extracted.
378
     */
379
    protected function extractRelationship(RelationshipMetadata $relMeta, $reference)
380
    {
381
        $simple = false === $relMeta->isPolymorphic();
382
        $idKey = $this->getIdentifierKey();
383
        $typeKey = $this->getPolymorphicKey();
384
        if (true === $simple && is_array($reference) && isset($reference[$idKey])) {
385
            return [
386
                'id'    => $reference[$idKey],
387
                'type'  => $relMeta->getEntityType(),
388
            ];
389
        } elseif (true === $simple && !is_array($reference)) {
390
            return [
391
                'id'    => $reference,
392
                'type'  => $relMeta->getEntityType(),
393
            ];
394
        } elseif (false === $simple && is_array($reference) && isset($reference[$idKey]) && isset($reference[$typeKey])) {
395
            return [
396
                'id'    => $reference[$idKey],
397
                'type'  => $reference[$typeKey],
398
            ];
399
        } else {
400
            throw new RuntimeException('Unable to extract a reference id.');
401
        }
402
    }
403
404
    /**
405
     * Gets standard database retrieval criteria for an inverse relationship.
406
     *
407
     * @param   EntityMetadata  $metadata       The entity to retrieve database records for.
0 ignored issues
show
Bug introduced by
There is no parameter named $metadata. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
408
     * @param   string|array    $identifiers    The IDs to query.
409
     * @return  array
410
     */
411
    protected function getInverseCriteria(EntityMetadata $owner, EntityMetadata $related, $identifiers, $inverseField)
412
    {
413
        $criteria[$inverseField] = (array) $identifiers;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$criteria was never initialized. Although not strictly required by PHP, it is generally a good practice to add $criteria = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
414
        if (true === $owner->isChildEntity()) {
415
            // The owner is owned by a polymorphic model. Must include the type with the inverse field criteria.
416
            $criteria[$inverseField] = [
417
                $this->getIdentifierKey()   => $criteria[$inverseField],
418
                $this->getPolymorphicKey()  => $owner->type,
419
            ];
420
        }
421
        if (true === $related->isChildEntity()) {
422
            // The relationship is owned by a polymorphic model. Must include the type in the root criteria.
423
            $criteria[$this->getPolymorphicKey()] = $related->type;
424
        }
425
        return $criteria;
426
    }
427
428
    /**
429
     * Gets standard database retrieval criteria for an entity and the provided identifiers.
430
     *
431
     * @param   EntityMetadata      $metadata       The entity to retrieve database records for.
432
     * @param   string|array|null   $identifiers    The IDs to query.
433
     * @return  array
434
     */
435
    protected function getRetrieveCritiera(EntityMetadata $metadata, $identifiers = null)
436
    {
437
        $criteria = [];
438
        if (true === $metadata->isChildEntity()) {
439
            $criteria[$this->getPolymorphicKey()] = $metadata->type;
440
        }
441
442
        if (null === $identifiers) {
443
            return $criteria;
444
        }
445
        $identifiers = (array) $identifiers;
446
        if (empty($identifiers)) {
447
            return $criteria;
448
        }
449
        $criteria[$this->getIdentifierKey()] = (1 === count($identifiers)) ? $identifiers[0] : $identifiers;
450
        return $criteria;
451
    }
452
453
    /**
454
     * Creates a builder object for querying MongoDB based on the provided metadata.
455
     *
456
     * @param   EntityMetadata  $metadata
457
     * @return  \Doctrine\MongoDB\Query\Builder
458
     */
459
    protected function createQueryBuilder(EntityMetadata $metadata)
460
    {
461
        return $this->getModelCollection($metadata)->createQueryBuilder();
462
    }
463
464
    /**
465
     * Gets the MongoDB Collection object for a Model.
466
     *
467
     * @param   EntityMetadata  $metadata
468
     * @return  \Doctrine\MongoDB\Collection
469
     */
470
    protected function getModelCollection(EntityMetadata $metadata)
471
    {
472
        return $this->connection->selectCollection($metadata->persistence->db, $metadata->persistence->collection);
0 ignored issues
show
Bug introduced by
Accessing db on the interface As3\Modlr\Metadata\Inter...s\StorageLayerInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing collection on the interface As3\Modlr\Metadata\Inter...s\StorageLayerInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
473
    }
474
}
475