Completed
Push — master ( 699dc6...d8f60e )
by Changwan
05:50
created

Repository::insert()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 23
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 17
nc 9
nop 1
dl 0
loc 23
ccs 17
cts 17
cp 1
crap 5
rs 8.5906
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\Entity\Metadata;
10
use Wandu\Database\Exception\IdentifierNotFoundException;
11
use Wandu\Database\Manager;
12
use Wandu\Database\Query\SelectQuery;
13
use Wandu\Database\QueryBuilder;
14
15
class Repository
16
{
17
    /** @var \Wandu\Database\Manager */
18
    protected $manager;
19
    
20
    /** @var \Wandu\Database\Contracts\ConnectionInterface */
21
    protected $connection;
22
    
23
    /** @var \Wandu\Database\Entity\Metadata */
24
    protected $meta;
25
    
26
    /** @var \Wandu\Database\QueryBuilder */
27
    protected $queryBuilder;
28
    
29
    /** @var \ReflectionClass */
30
    protected $reflClass;
31
    
32
    /** @var \ReflectionProperty[] */
33
    protected $refProperties = [];
34
    
35
    /**
36
     * Repository constructor.
37
     * @param \Wandu\Database\Manager $manager
38
     * @param \Wandu\Database\Entity\Metadata $meta
39
     */
40 14
    public function __construct(Manager $manager, Metadata $meta)
41
    {
42 14
        $this->manager = $manager;
43 14
        $this->connection = $manager->connection($meta->getConnection());
44 14
        $this->meta = $meta;
45 14
        $this->query = new QueryBuilder($meta->getTable());
0 ignored issues
show
Bug introduced by
The property query does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
46 14
    }
47
48
    /**
49
     * @return \Wandu\Database\Entity\Metadata
50
     */
51
    public function getMeta(): Metadata
52
    {
53
        return $this->meta;
54
    }
55
    
56
    /**
57
     * @param string|callable|\Wandu\Database\Contracts\QueryInterface $query
58
     * @param array $bindings
59
     * @return \Generator
60
     */
61 5
    public function fetch($query = null, array $bindings = [])
62
    {
63 5
        foreach ($this->connection->fetch($this->normalizeSelectQuery($query), $bindings) as $row) {
64 5
            yield $this->hydrate($row);
65
        }
66 5
    }
67
68
    /**
69
     * @param string|callable|\Wandu\Database\Contracts\QueryInterface $query
70
     * @param array $bindings
71
     * @return object
72
     */
73 9
    public function first($query = null, array $bindings = [])
74
    {
75 9
        return $this->hydrate($this->connection->first($this->normalizeSelectQuery($query), $bindings));
76
    }
77
78
    /**
79
     * @param string|int $identifier
80
     * @return object
81
     */
82
    public function find($identifier)
83
    {
84 3
        return $this->first(function (SelectQuery $select) use ($identifier) {
85 3
            return $select->where($this->meta->getPrimaryKey(), $identifier);
86 3
        });
87
    }
88
    
89
    /**
90
     * @param object $entity
91
     * @return int
92
     */
93 1
    public function insert($entity)
94
    {
95 1
        $this->assertIsInstance($entity, __METHOD__);
96 1
        $primaryKey = $this->meta->getPrimaryKey();
97 1
        $primaryProperty = null;
98 1
        $columns = $this->meta->getColumns();
99 1
        $attributesToStore = [];
100 1
        foreach ($columns as $propertyName => $columnName) {
101 1
            if ($primaryKey === $columnName) {
102 1
                $primaryProperty = $propertyName;
103 1
                continue;
104
            }
105 1
            $attributesToStore[$columnName] = $this->pickProperty($this->getPropertyReflection($propertyName), $entity);
106
        }
107 1
        $rowAffected = $this->query($this->query->insert($attributesToStore));
108 1
        if ($this->meta->isIncrements()) {
109 1
            $lastInsertId = $this->connection->getLastInsertId();
110 1
            if ($primaryProperty) {
111 1
                $this->injectProperty($this->getPropertyReflection($primaryProperty), $entity, $lastInsertId);
112
            }
113
        }
114 1
        return $rowAffected;
115
    }
116
117
    /**
118
     * @param object $entity
119
     * @return int
120
     */
121 1
    public function update($entity)
122
    {
123 1
        $this->assertIsInstance($entity, __METHOD__);
124 1
        $primaryKey = $this->meta->getPrimaryKey(); 
125
126 1
        $identifier = $this->getIdentifier($entity);
127 1
        $attributesToStore = [];
128 1
        foreach ($this->meta->getColumns() as $propertyName => $columnName) {
129 1
            if ($primaryKey === $columnName) continue;
130 1
            $attributesToStore[$columnName] = $this->pickProperty($this->getPropertyReflection($propertyName), $entity);
131
        }
132
133 1
        return $this->query(
134 1
            $this->query->update($attributesToStore)->where($primaryKey, $identifier)
135
        );
136
    }
137
138
    /**
139
     * @param object $entity
140
     * @return int
141
     */
142 1
    public function delete($entity)
143
    {
144 1
        $this->assertIsInstance($entity, __METHOD__);
145
        
146 1
        $primaryKey = $this->meta->getPrimaryKey();
147 1
        $identifier = $this->getIdentifier($entity);
148
149 1
        $affectedRows = $this->query($this->query->delete()->where($primaryKey, $identifier));
150 1
        if ($identifierProperty = $this->meta->getPrimaryProperty()) {
151 1
            $this->injectProperty($this->getPropertyReflection($identifierProperty), $entity, null);
152
        }
153 1
        return $affectedRows;
154
    }
155
156
    /**
157
     * @param string|callable|\Wandu\Database\Contracts\QueryInterface $query
158
     * @param array $bindings
159
     * @return int
160
     */
161 2
    public function query($query, array $bindings = [])
162
    {
163 2
        return $this->connection->query($query, $bindings);
164
    }
165
166
    /**
167
     * @param array $attributes
168
     * @return object
169
     */
170 13
    public function hydrate(array $attributes = null)
171
    {
172 13
        if (!$attributes) {
173 1
            return null;
174
        }
175 13
        $casts = $this->meta->getCasts();
176 13
        $relations = $this->meta->getRelations();
177 13
        $entity = $this->getClassReflection()->newInstanceWithoutConstructor();
178 13
        foreach ($this->meta->getColumns() as $propertyName => $column) {
179 13
            if (!isset($attributes[$column])) continue;
180 13
            if (isset($relations[$propertyName])) {
181 1
                $value = $relations[$propertyName]->getRelation($this->manager, $attributes[$column]);
182 13
            } elseif (isset($casts[$column])) {
183 2
                $value = $this->cast($attributes[$column], $casts[$column]);
184
            } else {
185 13
                $value = $attributes[$column];
186
            }
187 13
            $this->injectProperty($this->getPropertyReflection($propertyName), $entity, $value);
188
        }
189 13
        return $entity;
190
    }
191
192
    /**
193
     * @param mixed $entity
194
     * @return string|int
195
     */
196 2
    private function getIdentifier($entity)
197
    {
198 2
        $identifier = null;
199 2
        if ($identifierProperty = $this->meta->getPrimaryProperty()) {
200 2
            $identifier = $this->pickProperty($this->getPropertyReflection($identifierProperty), $entity);
201
        }
202 2
        if (!$identifier) {
203 1
            throw new IdentifierNotFoundException();
204
        }
205 2
        return $identifier;
206
    }
207
208
    /**
209
     * @param string|callable|\Wandu\Database\Contracts\QueryInterface $query
210
     * @return string|\Wandu\Database\Contracts\QueryInterface
211
     */
212 13
    private function normalizeSelectQuery($query = null)
213
    {
214 13
        if (!isset($query) || is_callable($query)) {
215 8
            $connection = $this->connection;
216 8
            $queryBuilder = $this->query->select();
217 8
            if (!isset($query)) {
218
                return $queryBuilder;
219
            }
220 8
            while (is_callable($query)) {
221 8
                $query = call_user_func($query, $queryBuilder, $connection);
222
            }
223
        }
224 13
        return $query;
225
    }
226
    
227 2
    private function cast($value, $type)
228
    {
229
        // "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...
230
        switch ($type) {
231 2
            case 'string':
232 2
                return (string) $value;
233
            case 'integer':
234
                return (int) $value;
235
            case 'float':
236
                return (float) $value;
237
            case 'boolean':
238
                return (bool) $value;
239
            default:
240
                return $value;
241
        }
242
    }
243
    
244
    /**
245
     * @param \ReflectionProperty $property
246
     * @param object $object
247
     * @param mixed $target
248
     */
249 14
    private function injectProperty(ReflectionProperty $property, $object, $target)
250
    {
251 14
        $property->setAccessible(true);
252 14
        $property->setValue($object, $target);
253 14
    }
254
255
    /**
256
     * @param \ReflectionProperty $property
257
     * @param object $object
258
     * @return mixed
259
     */
260 2
    private function pickProperty(ReflectionProperty $property, $object)
261
    {
262 2
        $property->setAccessible(true);
263 2
        return $property->getValue($object);
264
    }
265
266
    /**
267
     * @param string $name
268
     * @return \ReflectionProperty
269
     */
270 14
    private function getPropertyReflection($name): ReflectionProperty
271
    {
272 14
        if (!isset($this->refProperties[$name])) {
273 14
            return $this->refProperties[$name] = $this->getClassReflection()->getProperty($name);
274
        }
275 7
        return $this->refProperties[$name];
276
    }
277
278
    /**
279
     * @return \ReflectionClass
280
     */
281 14
    private function getClassReflection(): ReflectionClass
282
    {
283 14
        if (!isset($this->reflClass)) {
284 14
            return $this->reflClass = new ReflectionClass($this->meta->getClass());
285
        }
286 14
        return $this->reflClass;
287
    }
288
289
    /**
290
     * @param mixed $entity
291
     * @param string $method
292
     */
293 2
    private function assertIsInstance($entity, $method)
294
    {
295 2
        $class = $this->meta->getClass();
296 2
        if (!$entity instanceof $class) {
297 2
            throw new InvalidArgumentException(
298 2
                "Argument 1 passed to {$method}() must be of the type " . $class
299
            );
300
        }
301 2
    }
302
}
303