Completed
Push — master ( 747e00...006ae1 )
by Changwan
03:04
created

Repository::hydrate()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 26
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 8.7021

Importance

Changes 0
Metric Value
cc 8
eloc 19
nc 8
nop 1
dl 0
loc 26
ccs 14
cts 18
cp 0.7778
crap 8.7021
rs 5.3846
c 0
b 0
f 0
1
<?php
2
namespace Wandu\Database\Repository;
3
4
use InvalidArgumentException;
5
use ReflectionClass;
6
use ReflectionProperty;
7
use stdClass;
8
use Wandu\Database\Contracts\ConnectionInterface;
9
use Wandu\Database\Exception\IdentifierNotFoundException;
10
use Wandu\Database\Query\SelectQuery;
11
12
class Repository
13
{
14
    /** @var \Wandu\Database\Contracts\ConnectionInterface */
15
    protected $connection;
16
    
17
    /** @var \Wandu\Database\Repository\RepositorySettings */
18
    protected $settings;
19
    
20
    /**
21
     * Repository constructor.
22
     * @param \Wandu\Database\Contracts\ConnectionInterface $connection
23
     * @param \Wandu\Database\Repository\RepositorySettings $settings
24
     */
25 13
    public function __construct(ConnectionInterface $connection, RepositorySettings $settings)
26
    {
27 13
        $this->connection = $connection;
28 13
        $this->settings = $settings;
29 13
    }
30
31
    /**
32
     * @param string|callable|\Wandu\Database\Contracts\QueryInterface $query
33
     * @param array $bindings
34
     * @return \Generator
35
     */
36 5
    public function fetch($query = null, array $bindings = [])
37
    {
38 5
        foreach ($this->connection->fetch($this->normalizeQuery($query), $bindings) as $row) {
39 5
            yield $this->hydrate($row);
40
        }
41 5
    }
42
43
    /**
44
     * @param string|callable|\Wandu\Database\Contracts\QueryInterface $query
45
     * @param array $bindings
46
     * @return object
47
     */
48 7
    public function first($query = null, array $bindings = [])
49
    {
50 7
        return $this->hydrate($this->connection->first($this->normalizeQuery($query), $bindings));
51
    }
52
53
    /**
54
     * @param string|int $identifier
55
     * @return object
56
     */
57
    public function find($identifier)
58
    {
59 1
        return $this->first(function (SelectQuery $select) use ($identifier) {
60 1
            $columns = $this->settings->getColumns();
61 1
            return $select->where($columns[$this->settings->getIdentifier()], $identifier);
62 1
        });
63
    }
64
    
65
    /**
66
     * @param object $entity
67
     * @return int
68
     */
69 1
    public function insert($entity)
70
    {
71 1
        $this->assertIsInstance($entity, __METHOD__);
72 1
        $identifierKey = $this->settings->getIdentifier();
73 1
        $columns = $this->settings->getColumns();
74 1
        $attributesToStore = [];
75 1
        foreach ($columns as $propertyName => $columnName) {
76 1
            if ($identifierKey === $propertyName) continue;
77 1
            $attributesToStore[$columnName] = $this->pickProperty($this->getPropertyReflection($propertyName), $entity);
78
        }
79 1
        $queryBuilder = $this->connection->createQueryBuilder($this->settings->getTable());
80 1
        $rowAffected = $this->query($queryBuilder->insert($attributesToStore));
81 1
        if ($this->settings->isIncrements()) {
82 1
            $lastInsertId = $this->connection->getLastInsertId();
83 1
            $this->injectProperty($this->getPropertyReflection($identifierKey), $entity, $lastInsertId);
84
        }
85 1
        return $rowAffected;
86
    }
87
88
    /**
89
     * @param object $entity
90
     * @return int
91
     */
92 1
    public function update($entity)
93
    {
94 1
        $this->assertIsInstance($entity, __METHOD__);
95 1
        $identifierKey = $this->settings->getIdentifier();
96 1
        $columns = $this->settings->getColumns();
97 1
        $identifier = $this->pickProperty($this->getPropertyReflection($identifierKey), $entity);
98 1
        if (!$identifier) {
99
            throw new IdentifierNotFoundException();
100
        }
101 1
        $attributesToStore = [];
102 1
        foreach ($columns as $propertyName => $columnName) {
103 1
            if ($identifierKey === $propertyName) continue;
104 1
            $attributesToStore[$columnName] = $this->pickProperty($this->getPropertyReflection($propertyName), $entity);
105
        }
106
107 1
        $queryBuilder = $this->connection->createQueryBuilder($this->settings->getTable());
108 1
        return $this->query($queryBuilder->update($attributesToStore)->where($columns[$identifierKey], $identifier));
109
    }
110
111
    /**
112
     * @param object $entity
113
     * @return int
114
     */
115 1
    public function delete($entity)
116
    {
117 1
        $this->assertIsInstance($entity, __METHOD__);
118 1
        $identifierKey = $this->settings->getIdentifier();
119 1
        $columns = $this->settings->getColumns();
120
        
121 1
        $identifier = $this->pickProperty($this->getPropertyReflection($identifierKey), $entity);
122 1
        if (!$identifier) {
123 1
            throw new IdentifierNotFoundException();
124
        }
125 1
        $queryBuilder = $this->connection->createQueryBuilder($this->settings->getTable());
126 1
        $affectedRows = $this->query($queryBuilder->delete()->where($columns[$identifierKey], $identifier));
127 1
        $this->injectProperty($this->getPropertyReflection($identifierKey), $entity, null);
128 1
        return $affectedRows;
129
    }
130
131
    /**
132
     * @param string|callable|\Wandu\Database\Contracts\QueryInterface $query
133
     * @param array $bindings
134
     * @return int
135
     */
136 2
    public function query($query, array $bindings = [])
137
    {
138 2
        return $this->connection->query($query, $bindings);
139
    }
140
141
    /**
142
     * @param array $attributes
143
     * @return object
144
     */
145 11
    public function hydrate(array $attributes = null)
146
    {
147 11
        if (!$attributes) {
148 1
            return null;
149
        }
150 11
        $model = $this->settings->getModel();
151 11
        $casts = $this->settings->getCasts();
152 11
        $columns = $this->settings->getColumns(); // map
153
154 11
        if ($model) {
155 11
            $classReflection = $this->getClassReflection();
156 11
            $entity = $classReflection->newInstanceWithoutConstructor();
157 11
            foreach ($columns as $propertyName => $column) {
158 11
                if (!isset($attributes[$column])) continue;
159 11
                $value = isset($casts[$column]) ? $this->cast($attributes[$column], $casts[$column]) : $attributes[$column];
160 11
                $this->injectProperty($this->getPropertyReflection($propertyName), $entity, $value);
161
            }
162
        } else {
163
            $entity = new stdClass();
164
            foreach ($attributes as $name => $attribute) {
165
                $value = isset($casts[$name]) ? $this->cast($attribute, $casts[$name]) : $attribute;
166
                $entity->{$columns[$name]} = $value;
167
            }
168
        }
169 11
        return $entity;
170
    }
171
172
    /**
173
     * @param string|callable|\Wandu\Database\Contracts\QueryInterface $query
174
     * @return string|\Wandu\Database\Contracts\QueryInterface
175
     */
176 11
    private function normalizeQuery($query = null)
177
    {
178 11
        if (!isset($query) || is_callable($query)) {
179 6
            $connection = $this->connection;
180 6
            $queryBuilder = $connection->createQueryBuilder($this->settings->getTable())->select();
181 6
            if (!isset($query)) {
182
                return $queryBuilder;
183
            }
184 6
            while (is_callable($query)) {
185 6
                $query = call_user_func($query, $queryBuilder, $connection);
186
            }
187
        }
188 11
        return $query;
189
    }
190
    
191
    private function cast($value, $type)
192
    {
193
        // "string", "integer", "float", "boolean", "array", "datetime", "date", "time"
0 ignored issues
show
Unused Code Comprehensibility introduced by
66% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
194
        switch ($type) {
195
            case 'string':
196
                return (string) $value;
197
            case 'integer':
198
                return (int) $value;
199
            case 'float':
200
                return (float) $value;
201
            case 'boolean':
202
                return (bool) $value;
203
            default:
204
                return $value;
205
        }
206
    }
207
    
208
    /**
209
     * @param \ReflectionProperty $property
210
     * @param object $object
211
     * @param mixed $target
212
     */
213 12
    private function injectProperty(ReflectionProperty $property, $object, $target)
214
    {
215 12
        $property->setAccessible(true);
216 12
        $property->setValue($object, $target);
217 12
    }
218
219
    /**
220
     * @param \ReflectionProperty $property
221
     * @param object $object
222
     * @return mixed
223
     */
224 2
    private function pickProperty(ReflectionProperty $property, $object)
225
    {
226 2
        $property->setAccessible(true);
227 2
        return $property->getValue($object);
228
    }
229
230
    /**
231
     * @return \ReflectionClass
232
     */
233 12
    private function getClassReflection()
234
    {
235 12
        static $reflection;
236 12
        if (!isset($reflection)) {
237 1
            $model = $this->settings->getModel();
238 1
            return $reflection = new ReflectionClass($model ? $model : 'stdClass');
239
        }
240 12
        return $reflection;
241
    }
242
243
    /**
244
     * @param string $name
245
     * @return \ReflectionProperty
246
     */
247 12
    private function getPropertyReflection($name)
248
    {
249 12
        static $reflections = [];
250 12
        if (!isset($reflections[$name])) {
251 1
            return $reflections[$name] = $this->getClassReflection()->getProperty($name);
252
        }
253 12
        return $reflections[$name];
254
    }
255
256
    /**
257
     * @param mixed $entity
258
     * @param string $method
259
     */
260 2
    private function assertIsInstance($entity, $method)
261
    {
262 2
        if (!$this->isInstance($entity)) {
263 2
            throw new InvalidArgumentException(
264 2
                "Argument 1 passed to {$method}() must be of the type " . $this->getClassReflection()->getName()
0 ignored issues
show
Bug introduced by
Consider using $this->getClassReflection()->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
265
            );
266
        }
267 2
    }
268
    
269
    /**
270
     * @param mixed $entity
271
     * @return boolean
272
     */
273 2
    private function isInstance($entity)
274
    {
275 2
        return $this->getClassReflection()->isInstance($entity);
276
    }
277
}
278