Completed
Push — master ( 3deaa5...4c5cb4 )
by Changwan
06:31
created

Repository   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 250
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 87.6%

Importance

Changes 0
Metric Value
dl 0
loc 250
ccs 106
cts 121
cp 0.876
rs 8.2608
c 0
b 0
f 0
wmc 40
lcom 1
cbo 6

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A fetch() 0 6 2
A first() 0 4 1
A find() 0 4 1
A insert() 0 18 4
A update() 0 18 4
A delete() 0 15 2
A query() 0 4 1
B hydrate() 0 22 6
A normalizeQuery() 0 11 3
B cast() 0 16 5
A injectProperty() 0 5 1
A pickProperty() 0 5 1
A getClassReflection() 0 9 3
A getPropertyReflection() 0 8 2
A assertIsInstance() 0 8 2
A isInstance() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Repository often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Repository, and based on these observations, apply Extract Interface, too.

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, 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, 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)
163
    {
164 10
        if (is_callable($query)) {
165 5
            $connection = $this->connection;
166 5
            $queryBuilder = $connection->createQueryBuilder($this->settings->getTable())->select();
167 5
            while (is_callable($query)) {
168 5
                $query = call_user_func($query, $queryBuilder, $connection);
169 5
            }
170 5
        }
171 10
        return $query;
172
    }
173
    
174 10
    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 10
            case 'string':
179 10
                return (string) $value;
180 10
            case 'integer':
181 10
                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 11
    private function injectProperty(ReflectionProperty $property, $object, $target)
197
    {
198 11
        $property->setAccessible(true);
199 11
        $property->setValue($object, $target);
200 11
    }
201
202
    /**
203
     * @param \ReflectionProperty $property
204
     * @param object $object
205
     * @return mixed
206
     */
207 2
    private function pickProperty(ReflectionProperty $property, $object)
208
    {
209 2
        $property->setAccessible(true);
210 2
        return $property->getValue($object);
211
    }
212
213
    /**
214
     * @return \ReflectionClass
215
     */
216 11
    private function getClassReflection()
217
    {
218 11
        static $reflection;
219 11
        if (!isset($reflection)) {
220 1
            $model = $this->settings->getModel();
221 1
            return $reflection = new ReflectionClass($model ? $model : 'stdClass');
222
        }
223 11
        return $reflection;
224
    }
225
226
    /**
227
     * @param string $name
228
     * @return \ReflectionProperty
229
     */
230 11
    private function getPropertyReflection($name)
231
    {
232 11
        static $reflections = [];
233 11
        if (!isset($reflections[$name])) {
234 1
            return $reflections[$name] = $this->getClassReflection()->getProperty($name);
235
        }
236 11
        return $reflections[$name];
237
    }
238
239
    /**
240
     * @param mixed $entity
241
     * @param string $method
242
     */
243 2
    private function assertIsInstance($entity, $method)
244
    {
245 2
        if (!$this->isInstance($entity)) {
246 2
            throw new InvalidArgumentException(
247 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...
248 2
            );
249
        }
250 2
    }
251
    
252
    /**
253
     * @param mixed $entity
254
     * @return boolean
255
     */
256 2
    private function isInstance($entity)
257
    {
258 2
        return $this->getClassReflection()->isInstance($entity);
259
    }
260
}
261