Completed
Push — master ( b0060a...c89521 )
by Changwan
07:23
created

Repository::find()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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