Mapper::getSetterStyle()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 1 Features 1
Metric Value
c 2
b 1
f 1
dl 0
loc 6
ccs 0
cts 3
cp 0
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 2
1
<?php
2
3
namespace Respect\Structural;
4
5
use Exception;
6
use Respect\Data\AbstractMapper;
7
use Respect\Data\CollectionIterator;
8
use Respect\Data\Collections as c;
9
use Respect\Data\Collections\Collection;
10
use SplObjectStorage;
11
12
/** Maps objects to nosql operations */
13
class Mapper extends AbstractMapper implements
14
    c\Filterable,
15
    c\Mixable,
16
    c\Typable
17
{
18
    /** @var \Respect\Structural\Driver Holds our connector* */
19
    protected $driver;
20
21
    /** @var string Namespace to look for entities * */
22
    public $entityNamespace = '\\';
23
24
    /**
25
     * @param Driver $driver
26
     */
27 3
    public function __construct(Driver $driver)
28
    {
29 3
        parent::__construct();
30
31 3
        $this->driver = $driver;
32 3
    }
33
34
    /**
35
     * @return Mapper
36
     */
37 3
    public function __get($name)
38
    {
39 3
        return parent::__get($name);
40
    }
41
42
    /**
43
     * Flushes a single instance into the database. This method supports
44
     * mixing, so flushing a mixed instance will flush distinct tables on the
45
     * database
46
     *
47
     * @param object $entity Entity instance to be flushed
48
     *
49
     * @return null
50
     */
51 3
    protected function flushSingle($entity)
52
    {
53 3
        $coll = $this->tracked[$entity];
54 3
        $cols = $this->extractColumns($entity, $coll);
55
56 3
        if ($this->removed->contains($entity)) {
57 1
            $this->rawDelete($coll, $entity);
58 3
        } elseif ($this->new->contains($entity)) {
59 1
            $this->rawInsert($coll, $entity);
60 1
        } else {
61 1
            $this->rawUpdate($cols, $coll);
62
        }
63 3
    }
64
65 2
    public function persist($object, Collection $onCollection)
66
    {
67 2
        $next = $onCollection->getNext();
68
69 2
        if ($this->filterable($onCollection)) {
70
            $next->setMapper($this);
71
            $next->persist($object);
72
73
            return;
74
        }
75
76 2
        if ($next) {
77
            $remote = $this->getStyle()->remoteIdentifier($next->getName());
78
            $next->setMapper($this);
79
            $next->persist($object->$remote);
80
        }
81
82 2
        foreach ($onCollection->getChildren() as $child) {
83
            $remote = $this->getStyle()->remoteIdentifier($child->getName());
84
            $child->persist($object->$remote);
85 2
        }
86
87 2
        return parent::persist($object, $onCollection);
88
    }
89
90
    /**
91
     * Receives columns from an entity and her collection. Returns the columns
92
     * that belong only to the main entity. This method supports mixing, so
93
     * extracting mixins will also persist them on their respective
94
     * tables
95
     *
96
     * @param \Respect\Data\Collections\Collection $collection Target collection
97
     * @param array                                $cols       Entity columns
98
     *
99
     * @return array Columns left for the main collection
100
     */
101 1
    protected function extractAndOperateMixins(Collection $collection, $cols)
102
    {
103 1
        if (!$this->mixable($collection)) {
104 1
            return $cols;
105
        }
106
107
        foreach ($this->getMixins($collection) as $mix => $spec) {
108
            //Extract from $cols only the columns from the mixin
109
            $mixCols = array_intersect_key(
110
                $cols,
111
                array_combine(//create array with keys only
112
                    $spec,
113
                    array_fill(0, count($spec), '')
114
                )
115
            );
116
            if (isset($cols["{$mix}_id"])) {
117
                $mixCols['id'] = $cols["{$mix}_id"];
118
                $cols = array_diff($cols, $mixCols); //Remove mixin columns
119
                $this->rawUpdate($mixCols, $this->__get($mix));
0 ignored issues
show
Documentation introduced by
$this->__get($mix) is of type object<Respect\Structural\Mapper>, but the function expects a object<Respect\Data\Collections\Collection>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
120
            } else {
121
                $mixCols['id'] = null;
122
                $cols = array_diff($cols, $mixCols); //Remove mixin columns
123
                $this->rawinsert($mixCols, $this->__get($mix));
0 ignored issues
show
Documentation introduced by
$mixCols is of type array<string,null,{"id":"null"}>, but the function expects a object<Respect\Data\Collections\Collection>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
124
            }
125
        }
126
127
        return $cols;
128
    }
129
130 2
    protected function guessCondition(&$columns, Collection $collection)
131
    {
132 2
        $primaryName = $this->getStyle()->identifier($collection->getName());
133 2
        $condition = [$primaryName => $columns[$primaryName]];
134 2
        unset($columns[$primaryName]);
135
136 2
        return $condition;
137
    }
138
139 1
    protected function rawDelete(Collection $collection, $entity)
140
    {
141 1
        $name = $collection->getName();
142 1
        $columns = $this->extractColumns($entity, $collection);
143 1
        $condition = $this->guessCondition($columns, $collection);
144
145 1
        return $this->driver->remove($name, $condition);
146
    }
147
148 1
    protected function rawUpdate(array $columns, Collection $collection)
149
    {
150 1
        $columns = $this->extractAndOperateMixins($collection, $columns);
151 1
        $name = $collection->getName();
152 1
        $condition = $this->guessCondition($columns, $collection);
153
154 1
        $this->driver->update($name, $condition, $columns);
155 1
    }
156
157 1
    protected function rawInsert(Collection $collection, $entity = null)
158
    {
159 1
        $name = $collection->getName();
160 1
        $this->driver->insert($name, $entity);
161 1
    }
162
163 3
    public function flush()
164
    {
165
        try {
166 3
            foreach ($this->changed as $entity) {
167 3
                $this->flushSingle($entity);
168 3
            }
169 3
        } catch (Exception $e) {
170
            throw $e;
171
        }
172
173 3
        $this->reset();
174 3
    }
175
176 2
    protected function createStatement(Collection $collection, $withExtra = null)
177
    {
178 2
        $query = $this->generateQuery($collection);
179
180 2
        $withExtraList = (array)$withExtra;
181
182 2
        $withExtraList = array_merge($withExtraList, $query);
183
184 2
        return $this->driver->find($collection->getName(), $withExtraList);
185
    }
186
187 2
    protected function generateQuery(Collection $collection)
188
    {
189 2
        return $this->driver->generateQuery($collection);
190
    }
191
192 3
    protected function extractColumns($entity, Collection $collection)
0 ignored issues
show
Unused Code introduced by
The parameter $collection 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...
193
    {
194 3
        $cols = get_object_vars($entity);
195
196 3
        return $cols;
197
    }
198
199
    protected function hasComposition($entity, $next, $parent)
200
    {
201
        $style = $this->getStyle();
202
203
        return $entity === $style->composed($parent, $next) || $entity === $style->composed($next, $parent);
204
    }
205
206 2
    protected function fetchSingle(Collection $collection, $statement)
207
    {
208 2
        $name = $collection->getName();
209 2
        $entityName = $name;
210 2
        $row = $this->driver->fetch($statement);
211
212 2
        if (!$row) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $row of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
213
            return false;
214
        }
215
216 2
        if ($this->typable($collection)) {
217
            $entityName = $row->{$this->getType($collection)};
218
        }
219
220 2
        $entities = new SplObjectStorage();
221 2
        $entities[$this->transformSingleRow($row, $entityName)] = $collection;
222
223 2
        return $entities;
224
    }
225
226 2
    protected function getNewEntityByName($entityName)
227
    {
228 2
        $entityName = $this->getStyle()->styledName($entityName);
229 2
        $entityClass = $this->entityNamespace . $entityName;
230 2
        $entityClass = class_exists($entityClass) ? $entityClass : '\stdClass';
231
232 2
        return new $entityClass;
233
    }
234
235 2
    protected function transformSingleRow($row, $entityName)
236
    {
237 2
        $newRow = $this->getNewEntityByName($entityName);
238
239 2
        foreach ($row as $prop => $value) {
240 2
            $this->inferSet($newRow, $prop, $value);
241 2
        }
242
243 2
        return $newRow;
244
    }
245
246 2
    protected function inferSet(&$entity, $prop, $value)
247
    {
248
        try {
249 2
            $mirror = new \ReflectionProperty($entity, $prop);
250
            $mirror->setAccessible(true);
251
            $mirror->setValue($entity, $value);
252 2
        } catch (\ReflectionException $e) {
253 2
            $entity->$prop = $value;
254
        }
255 2
    }
256
257
    protected function fetchMulti(Collection $collection, $statement)
258
    {
259
        $row = $this->driver->fetch($statement);
260
261
        if (!$row) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $row of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
262
            return false;
263
        }
264
265
        $this->postHydrate(
266
            $entities = $this->createEntities($row, $statement, $collection)
267
        );
268
269
        return $entities;
270
    }
271
272
    protected function createEntities($row, $statement, Collection $collection)
273
    {
274
        $entities = new SplObjectStorage();
275
        $entitiesInstances = $this->buildEntitiesInstances($collection, $entities);
276
        $entityInstance = array_pop($entitiesInstances);
277
278
        //Reversely traverses the columns to avoid conflicting foreign key names
279
        foreach (array_reverse($row, true) as $col => $value) {
280
            $columnMeta = $statement->getColumnMeta($col);
281
            $columnName = $columnMeta['name'];
282
            $primaryName = $this->getStyle()->identifier(
283
                $entities[$entityInstance]->getName()
284
            );
285
286
            $this->inferSet($entityInstance, $columnName, $value);
287
288
            if ($primaryName == $columnName) {
289
                $entityInstance = array_pop($entitiesInstances);
290
            }
291
        }
292
293
        return $entities;
294
    }
295
296
    protected function buildEntitiesInstances(Collection $collection, SplObjectStorage $entities)
297
    {
298
        $entitiesInstances = [];
299
300
        foreach (CollectionIterator::recursive($collection) as $c) {
301
            if ($this->filterable($c) && !$this->getFilters($c)) {
302
                continue;
303
            }
304
305
            $entityInstance = $this->getNewEntityByName($c->getName());
306
307
            if ($this->mixable($c)) {
308
                $mixins = $this->getMixins($c);
309
                foreach ($mixins as $mix) {
310
                    $entitiesInstances[] = $entityInstance;
311
                }
312
            }
313
314
            $entities[$entityInstance] = $c;
315
            $entitiesInstances[] = $entityInstance;
316
        }
317
318
        return $entitiesInstances;
319
    }
320
321
    protected function postHydrate(SplObjectStorage $entities)
322
    {
323
        $entitiesClone = clone $entities;
324
325
        foreach ($entities as $instance) {
326
            foreach ($instance as $field => &$value) {
327
                if ($this->getStyle()->isRemoteIdentifier($field)) {
328
                    foreach ($entitiesClone as $sub) {
329
                        $this->tryHydration($entities, $sub, $field, $value);
330
                    }
331
                }
332
            }
333
        }
334
    }
335
336
    /**
337
     * @param \SplObjectStorage $entities
338
     * @param $sub
339
     * @param $field
340
     * @param $value
341
     */
342
    protected function tryHydration($entities, $sub, $field, &$value)
343
    {
344
        $tableName = $entities[$sub]->getName();
345
        $primaryName = $this->getStyle()->identifier($tableName);
346
347
        if ($tableName === $this->getStyle()->remoteFromIdentifier($field)
348
            && $sub->{$primaryName} === $value
349
        ) {
350
            $value = $sub;
351
        }
352
    }
353
354
    protected function getSetterStyle($name)
355
    {
356
        $name = str_replace('_', '', $this->getStyle()->styledProperty($name));
357
358
        return "set{$name}";
359
    }
360
361
    public function getFilters(Collection $collection)
362
    {
363
        return $collection->getExtra('filters');
364
    }
365
366
    public function getMixins(Collection $collection)
367
    {
368
        return $collection->getExtra('mixins');
369
    }
370
371
    public function getType(Collection $collection)
372
    {
373
        return $collection->getExtra('type');
374
    }
375
376 1
    public function mixable(Collection $collection)
377
    {
378 1
        return $collection->have('mixins');
379
    }
380
381 2
    public function typable(Collection $collection)
382
    {
383 2
        return $collection->have('type');
384
    }
385
386 2
    public function filterable(Collection $collection)
387
    {
388 2
        return $collection->have('filters');
389
    }
390
}
391