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

Persister::getIdentifierKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace As3\Modlr\Persister\MongoDb;
4
5
use \Closure;
6
use \MongoId;
7
use As3\Modlr\Metadata\EntityMetadata;
8
use As3\Modlr\Models\Model;
9
use As3\Modlr\Persister\PersisterException;
10
use As3\Modlr\Persister\PersisterInterface;
11
use As3\Modlr\Store\Store;
12
use Doctrine\MongoDB\Connection;
13
14
/**
15
 * Persists and retrieves models to/from a MongoDB database.
16
 *
17
 * @author Jacob Bare <[email protected]>
18
 */
19
final class Persister implements PersisterInterface
20
{
21
    const IDENTIFIER_KEY    = '_id';
22
    const POLYMORPHIC_KEY   = '_type';
23
    const PERSISTER_KEY     = 'mongodb';
24
25
    /**
26
     * Provides a map of changeset methods.
27
     *
28
     * @var array
29
     */
30
    private $changeSetMethods = [
31
        'attributes'    => ['getAttribute', 'getAttributeDbValue'],
32
        'hasOne'        => ['getRelationship', 'getHasOneDbValue'],
33
        'hasMany'       => ['getRelationship', 'getHasManyDbValue'],
34
    ];
35
36
    /**
37
     * The raw result hydrator.
38
     *
39
     * @var Hydrator
40
     */
41
    private $hydrator;
42
43
    /**
44
     * @var StorageMetadataFactory
45
     */
46
    private $smf;
47
48
    /**
49
     * The query service.
50
     *
51
     * @var Query
52
     */
53
    private $query;
54
55
    /**
56
     * Constructor.
57
     *
58
     * @param   Query                   $query
59
     * @param   StorageMetadataFactory  $smf
60
     */
61
    public function __construct(Query $query, StorageMetadataFactory $smf)
62
    {
63
        $this->hydrator = new Hydrator();
64
        $this->smf = $smf;
65
        $this->query = $query;
66
    }
67
68
    /**
69
     * {@inheritDoc}
70
     */
71
    public function all(EntityMetadata $metadata, Store $store, array $identifiers = [])
72
    {
73
        $criteria = $this->getQuery()->getRetrieveCritiera($metadata, $identifiers);
74
        $cursor = $this->getQuery()->executeFind($metadata, $store, $criteria);
75
        return $this->getHydrator()->hydrateMany($metadata, $cursor->toArray(), $store);
76
    }
77
78
    /**
79
     * {@inheritDoc}
80
     */
81
    public function convertId($identifier, $strategy = null)
82
    {
83
        return $this->getFormatter()->getIdentifierDbValue($identifier, $strategy);
84
    }
85
86
    /**
87
     * {@inheritDoc}
88
     * @todo    Optimize the changeset to query generation.
89
     */
90
    public function create(Model $model)
91
    {
92
        $metadata = $model->getMetadata();
93
        $insert = $this->createInsertObj($model);
94
        $this->getQuery()->executeInsert($metadata, $insert);
95
        return $model;
96
    }
97
98
    /**
99
     * {@inheritDoc}
100
     */
101
    public function delete(Model $model)
102
    {
103
        $metadata = $model->getMetadata();
104
        $criteria = $this->getQuery()->getRetrieveCritiera($metadata, $model->getId());
105
        $this->getQuery()->executeDelete($metadata, $model->getStore(), $criteria);
106
        return $model;
107
    }
108
109
    /**
110
     * {@inheritDoc}
111
     */
112
    public function extractType(EntityMetadata $metadata, array $data)
113
    {
114
        return $this->getHydrator()->extractType($metadata, $data);
115
    }
116
117
    /**
118
     * {@inheritDoc}
119
     */
120
    public function generateId($strategy = null)
121
    {
122
        if (false === $this->getFormatter()->isIdStrategySupported($strategy)) {
123
            throw PersisterException::nyi('ID generation currently only supports an object strategy, or none at all.');
124
        }
125
        return new MongoId();
126
    }
127
128
    /**
129
     * @return  Formatter
130
     */
131
    public function getFormatter()
132
    {
133
        return $this->getQuery()->getFormatter();
134
    }
135
136
    /**
137
     * @return  Hydrator
138
     */
139
    public function getHydrator()
140
    {
141
        return $this->hydrator;
142
    }
143
144
    /**
145
     * {@inheritDoc}
146
     */
147
    public function getIdentifierKey()
148
    {
149
        return self::IDENTIFIER_KEY;
150
    }
151
152
    /**
153
     * {@inheritDoc}
154
     */
155
    public function getPersistenceMetadataFactory()
156
    {
157
        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...
158
    }
159
160
    /**
161
     * {@inheritDoc}
162
     */
163
    public function getPersisterKey()
164
    {
165
        return self::PERSISTER_KEY;
166
    }
167
168
    /**
169
     * {@inheritDoc}
170
     */
171
    public function getPolymorphicKey()
172
    {
173
        return self::POLYMORPHIC_KEY;
174
    }
175
176
    /**
177
     * @return Query
178
     */
179
    public function getQuery()
180
    {
181
        return $this->query;
182
    }
183
184
    /**
185
     * {@inheritDoc}
186
     */
187
    public function inverse(EntityMetadata $owner, EntityMetadata $rel, Store $store, array $identifiers, $inverseField)
188
    {
189
        $criteria = $this->getQuery()->getInverseCriteria($owner, $rel, $identifiers, $inverseField);
190
        $cursor = $this->getQuery()->executeFind($rel, $store, $criteria);
191
        return $this->getHydrator()->hydrateMany($rel, $cursor->toArray(), $store);
192
    }
193
194
    /**
195
     * {@inheritDoc}
196
     */
197
    public function query(EntityMetadata $metadata, Store $store, array $criteria, array $fields = [], array $sort = [], $offset = 0, $limit = 0)
198
    {
199
        $cursor = $this->getQuery()->executeFind($metadata, $store, $criteria);
200
        return $this->getHydrator()->hydrateMany($metadata, $cursor->toArray(), $store);
201
    }
202
203
    /**
204
     * {@inheritDoc}
205
     */
206
    public function retrieve(EntityMetadata $metadata, $identifier, Store $store)
207
    {
208
        $criteria = $this->getQuery()->getRetrieveCritiera($metadata, $identifier);
209
        $result = $this->getQuery()->executeFind($metadata, $store, $criteria)->getSingleResult();
210
        if (null === $result) {
211
            return;
212
        }
213
        return $this->getHydrator()->hydrateOne($metadata, $result, $store);
1 ignored issue
show
Bug introduced by
It seems like $result defined by $this->getQuery()->execu...ria)->getSingleResult() on line 209 can also be of type object; however, As3\Modlr\Persister\MongoDb\Hydrator::hydrateOne() 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...
214
    }
215
216
    /**
217
     * {@inheritDoc}
218
     * @todo    Optimize the changeset to query generation.
219
     */
220
    public function update(Model $model)
221
    {
222
        $metadata = $model->getMetadata();
223
        $criteria = $this->getQuery()->getRetrieveCritiera($metadata, $model->getId());
224
        $update = $this->createUpdateObj($model);
225
226
        if (empty($update)) {
227
            return $model;
228
        }
229
230
        $this->getQuery()->executeUpdate($metadata, $model->getStore(), $criteria, $update);
231
        return $model;
232
    }
233
234
    /**
235
     * Appends the change set values to a database object based on the provided handler.
236
     *
237
     * @param   Model   $model
238
     * @param   array   $obj
239
     * @param   Closure $handler
240
     * @return  array
241
     */
242
    private function appendChangeSet(Model $model, array $obj, Closure $handler)
243
    {
244
        $metadata = $model->getMetadata();
245
        $changeset = $model->getChangeSet();
246
        $formatter = $this->getFormatter();
247
248
        foreach ($this->changeSetMethods as $setKey => $methods) {
249
            list($metaMethod, $formatMethod) = $methods;
250
            foreach ($changeset[$setKey] as $key => $values) {
251
                $value = $formatter->$formatMethod($metadata->$metaMethod($key), $values['new']);
252
                $obj = $handler($key, $value, $obj);
253
            }
254
        }
255
        return $obj;
256
    }
257
258
    /**
259
     * Creates the database insert object for a Model.
260
     *
261
     * @param   Model   $model
262
     * @return  array
263
     */
264
    private function createInsertObj(Model $model)
265
    {
266
        $metadata = $model->getMetadata();
267
        $insert = [
268
            $this->getIdentifierKey()   => $this->convertId($model->getId()),
269
        ];
270
        if (true === $metadata->isChildEntity()) {
271
            $insert[$this->getPolymorphicKey()] = $metadata->type;
272
        }
273
        return $this->appendChangeSet($model, $insert, $this->getCreateChangeSetHandler());
274
    }
275
276
    /**
277
     * Creates the database update object for a Model.
278
     *
279
     * @param   Model   $model
280
     * @return  array
281
     */
282
    private function createUpdateObj(Model $model)
283
    {
284
        return $this->appendChangeSet($model, [], $this->getUpdateChangeSetHandler());
285
    }
286
287
    /**
288
     * Gets the change set handler Closure for create.
289
     *
290
     * @return  Closure
291
     */
292
    private function getCreateChangeSetHandler()
293
    {
294
        return function ($key, $value, $obj) {
295
            if (null !== $value) {
296
                $obj[$key] = $value;
297
            }
298
            return $obj;
299
        };
300
    }
301
302
    /**
303
     * Gets the change set handler Closure for update.
304
     *
305
     * @return  Closure
306
     */
307
    private function getUpdateChangeSetHandler()
308
    {
309
        return function ($key, $value, $obj) {
310
            $op = '$set';
311
            if (null === $value) {
312
                $op = '$unset';
313
                $value = 1;
314
            }
315
            $obj[$op][$key] = $value;
316
            return $obj;
317
        };
318
    }
319
}
320