Completed
Push — master ( 1e376d...b0060a )
by Changwan
05:50
created

Repository::update()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 13
nc 4
nop 1
dl 0
loc 18
ccs 0
cts 17
cp 0
crap 20
rs 9.2
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
11
class Repository
12
{
13
    /** @var \Wandu\Database\Contracts\ConnectionInterface */
14
    protected $connection;
15
    
16
    /** @var \Wandu\Database\Repository\RepositorySettings */
17
    protected $settings;
18
    
19
    /**
20
     * Repository constructor.
21
     * @param \Wandu\Database\Contracts\ConnectionInterface $connection
22
     * @param \Wandu\Database\Repository\RepositorySettings $settings
23
     */
24
    public function __construct(ConnectionInterface $connection, RepositorySettings $settings)
25
    {
26
        $this->connection = $connection;
27
        $this->settings = $settings;
28
    }
29
30
    /**
31
     * @param string|callable|\Wandu\Database\Contracts\QueryInterface $query
32
     * @param array $bindings
33
     * @return \Generator
34
     */
35
    public function fetch($query, array $bindings = [])
36
    {
37
        foreach ($this->connection->fetch($this->normalizeQuery($query), $bindings) as $row) {
38
            yield $this->hydrate($row);
39
        }
40
    }
41
42
    /**
43
     * @param string|callable|\Wandu\Database\Contracts\QueryInterface $query
44
     * @param array $bindings
45
     * @return object
46
     */
47
    public function first($query, array $bindings = [])
48
    {
49
        return $this->hydrate($this->connection->first($this->normalizeQuery($query), $bindings));
50
    }
51
52
    /**
53
     * @param object $entity
54
     * @return int
55
     */
56
    public function insert($entity)
57
    {
58
        $this->assertIsInstance($entity, __METHOD__);
59
        $identifierKey = $this->settings->getIdentifier();
60
        $columns = $this->settings->getColumns();
61
        $attributesToStore = [];
62
        foreach ($columns as $columnName => $propertyName) {
63
            if ($identifierKey === $propertyName) continue;
64
            $attributesToStore[$columnName] = $this->pickProperty($this->getPropertyReflection($propertyName), $entity);
65
        }
66
        $queryBuilder = $this->connection->createQueryBuilder($this->settings->getTable());
67
        $rowAffected = $this->query($queryBuilder->insert($attributesToStore));
68
        if ($this->settings->isIncrements()) {
69
            $lastInsertId = $this->connection->getLastInsertId();
70
            $this->injectProperty($this->getPropertyReflection($columns[$identifierKey]), $entity, $lastInsertId);
71
        }
72
        return $rowAffected;
73
    }
74
75
    public function update($entity)
76
    {
77
        $this->assertIsInstance($entity, __METHOD__);
78
        $identifierKey = $this->settings->getIdentifier();
79
        $columns = $this->settings->getColumns();
80
        $identifier = $this->pickProperty($this->getPropertyReflection($columns[$identifierKey]), $entity);
81
        if (!$identifier) {
82
            throw new IdentifierNotFoundException();
83
        }
84
        $attributesToStore = [];
85
        foreach ($columns as $columnName => $propertyName) {
86
            if ($identifierKey === $propertyName) continue;
87
            $attributesToStore[$columnName] = $this->pickProperty($this->getPropertyReflection($propertyName), $entity);
88
        }
89
90
        $queryBuilder = $this->connection->createQueryBuilder($this->settings->getTable());
91
        return $this->query($queryBuilder->update($attributesToStore)->where($identifierKey, $identifier));
92
    }
93
94
    /**
95
     * @param object $entity
96
     * @return int
97
     */
98
    public function delete($entity)
99
    {
100
        $this->assertIsInstance($entity, __METHOD__);
101
        $identifierKey = $this->settings->getIdentifier();
102
        $columns = $this->settings->getColumns();
103
        
104
        $identifier = $this->pickProperty($this->getPropertyReflection($columns[$identifierKey]), $entity);
105
        if (!$identifier) {
106
            throw new IdentifierNotFoundException();
107
        }
108
        $queryBuilder = $this->connection->createQueryBuilder($this->settings->getTable());
109
        $affectedRows = $this->query($queryBuilder->delete()->where($identifierKey, $identifier));
110
        $this->injectProperty($this->getPropertyReflection($columns[$identifierKey]), $entity, null);
111
        return $affectedRows;
112
    }
113
114
    /**
115
     * @param string|callable|\Wandu\Database\Contracts\QueryInterface $query
116
     * @param array $bindings
117
     * @return int
118
     */
119
    public function query($query, array $bindings = [])
120
    {
121
        return $this->connection->query($query, $bindings);
122
    }
123
124
    /**
125
     * @param array $attributes
126
     * @return object
127
     */
128
    public function hydrate(array $attributes = [])
129
    {
130
        $model = $this->settings->getModel();
131
        $casts = $this->settings->getCasts();
132
        $columns = $this->settings->getColumns(); // map
133
134
        if ($model) {
135
            $classReflection = $this->getClassReflection();
136
            $entity = $classReflection->newInstanceWithoutConstructor();
137
            foreach ($attributes as $name => $attribute) {
138
                $value = isset($casts[$name]) ? $this->cast($attribute, $casts[$name]) : $attribute;
139
                $this->injectProperty($this->getPropertyReflection($columns[$name]), $entity, $value);
140
            }
141
        } else {
142
            $entity = new stdClass();
143
            foreach ($attributes as $name => $attribute) {
144
                $value = isset($casts[$name]) ? $this->cast($attribute, $casts[$name]) : $attribute;
145
                $entity->{$columns[$name]} = $value;
146
            }
147
        }
148
        return $entity;
149
    }
150
151
    /**
152
     * @param string|callable|\Wandu\Database\Contracts\QueryInterface $query
153
     * @return string|\Wandu\Database\Contracts\QueryInterface
154
     */
155
    private function normalizeQuery($query)
156
    {
157
        if (is_callable($query)) {
158
            $connection = $this->connection;
159
            $queryBuilder = $connection->createQueryBuilder($this->settings->getTable())->select();
160
            while (is_callable($query)) {
161
                $query = call_user_func($query, $queryBuilder, $connection);
162
            }
163
        }
164
        return $query;
165
    }
166
    
167
    private function cast($value, $type)
168
    {
169
        // "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...
170
        switch ($type) {
171
            case 'string':
172
                return (string) $value;
173
            case 'integer':
174
                return (int) $value;
175
            case 'float':
176
                return (float) $value;
177
            case 'boolean':
178
                return (bool) $value;
179
            default:
180
                return $value;
181
        }
182
    }
183
    
184
    /**
185
     * @param \ReflectionProperty $property
186
     * @param object $object
187
     * @param mixed $target
188
     */
189
    private function injectProperty(ReflectionProperty $property, $object, $target)
190
    {
191
        $property->setAccessible(true);
192
        $property->setValue($object, $target);
193
    }
194
195
    /**
196
     * @param \ReflectionProperty $property
197
     * @param object $object
198
     * @return mixed
199
     */
200
    private function pickProperty(ReflectionProperty $property, $object)
201
    {
202
        $property->setAccessible(true);
203
        return $property->getValue($object);
204
    }
205
206
    /**
207
     * @return \ReflectionClass
208
     */
209
    private function getClassReflection()
210
    {
211
        static $reflection;
212
        if (!isset($reflection)) {
213
            $model = $this->settings->getModel();
214
            return $reflection = new ReflectionClass($model ? $model : 'stdClass');
215
        }
216
        return $reflection;
217
    }
218
219
    /**
220
     * @param string $name
221
     * @return \ReflectionProperty
222
     */
223
    private function getPropertyReflection($name)
224
    {
225
        static $reflections = [];
226
        if (!isset($reflections[$name])) {
227
            return $reflections[$name] = $this->getClassReflection()->getProperty($name);
228
        }
229
        return $reflections[$name];
230
    }
231
232
    /**
233
     * @param mixed $entity
234
     * @param string $method
235
     */
236
    private function assertIsInstance($entity, $method)
237
    {
238
        if (!$this->isInstance($entity)) {
239
            throw new InvalidArgumentException(
240
                "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...
241
            );
242
        }
243
    }
244
    
245
    /**
246
     * @param mixed $entity
247
     * @return boolean
248
     */
249
    private function isInstance($entity)
250
    {
251
        return $this->getClassReflection()->isInstance($entity);
252
    }
253
}
254