Completed
Push — master ( d2e043...09ed12 )
by Changwan
12:25
created

Repository::normalizeSelectQuery()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5.0342

Importance

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