Completed
Push — master ( 40ab0c...a57043 )
by Changwan
04:15
created

Repository::normalizeQuery()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.0187

Importance

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