Passed
Branch master (836442)
by Kevin
02:28
created

RepositoryProxy   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 292
Duplicated Lines 0 %

Test Coverage

Coverage 92%

Importance

Changes 4
Bugs 1 Features 1
Metric Value
wmc 45
eloc 73
c 4
b 1
f 1
dl 0
loc 292
ccs 92
cts 100
cp 0.92
rs 8.8

26 Methods

Rating   Name   Duplication   Size   Complexity  
A __call() 0 3 1
A __construct() 0 3 1
A getIterator() 0 7 2
A count() 0 12 3
A truncate() 0 15 3
A normalizeCriteria() 0 7 2
A assertCountGreaterThanOrEqual() 0 5 1
A first() 0 3 1
A last() 0 3 1
A random() 0 3 1
A assertExists() 0 5 1
A proxyResult() 0 11 4
A find() 0 11 3
A findOneBy() 0 11 5
A findAll() 0 3 1
A assertNotExists() 0 5 1
A getClassName() 0 3 1
A assertCountLessThan() 0 5 1
A randomRange() 0 19 4
A assertCount() 0 5 1
A assertEmpty() 0 3 1
A findBy() 0 3 1
A getCount() 0 5 1
A assertCountGreaterThan() 0 5 1
A assertCountLessThanOrEqual() 0 5 1
A randomSet() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like RepositoryProxy 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.

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 RepositoryProxy, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Zenstruck\Foundry;
4
5
use Doctrine\ORM\EntityManagerInterface;
6
use Doctrine\ORM\EntityRepository;
7
use Doctrine\Persistence\ObjectRepository;
8
use PHPUnit\Framework\Assert;
0 ignored issues
show
Bug introduced by
The type PHPUnit\Framework\Assert was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
10
/**
11
 * @mixin EntityRepository
12
 *
13
 * @author Kevin Bond <[email protected]>
14
 */
15
final class RepositoryProxy implements ObjectRepository, \IteratorAggregate, \Countable
16
{
17
    /** @var ObjectRepository */
18
    private $repository;
19
20 400
    public function __construct(ObjectRepository $repository)
21
    {
22 400
        $this->repository = $repository;
23 400
    }
24
25 10
    public function __call(string $method, array $arguments)
26
    {
27 10
        return $this->proxyResult($this->repository->{$method}(...$arguments));
28
    }
29
30 140
    public function count(): int
31
    {
32 140
        if ($this->repository instanceof EntityRepository) {
33
            // use query to avoid loading all entities
34 140
            return $this->repository->count([]);
35
        }
36
37
        if ($this->repository instanceof \Countable) {
38
            return \count($this->repository);
39
        }
40
41
        return \count($this->findAll());
42
    }
43
44 10
    public function getIterator(): \Traversable
45
    {
46 10
        if (\is_iterable($this->repository)) {
47
            return yield from $this->repository;
48
        }
49
50 10
        yield from $this->findAll();
51 10
    }
52
53
    /**
54
     * @deprecated use RepositoryProxy::count()
55
     */
56 10
    public function getCount(): int
57
    {
58 10
        trigger_deprecation('zenstruck\foundry', '1.5.0', 'Using RepositoryProxy::getCount() is deprecated, use RepositoryProxy::count() (it is now Countable).');
59
60 10
        return $this->count();
61
    }
62
63 40
    public function assertEmpty(string $message = ''): self
64
    {
65 40
        return $this->assertCount(0, $message);
66
    }
67
68 120
    public function assertCount(int $expectedCount, string $message = ''): self
69
    {
70 120
        Assert::assertSame($expectedCount, $this->count(), $message);
71
72 120
        return $this;
73
    }
74
75 10
    public function assertCountGreaterThan(int $expected, string $message = ''): self
76
    {
77 10
        Assert::assertGreaterThan($expected, $this->count(), $message);
78
79 10
        return $this;
80
    }
81
82 10
    public function assertCountGreaterThanOrEqual(int $expected, string $message = ''): self
83
    {
84 10
        Assert::assertGreaterThanOrEqual($expected, $this->count(), $message);
85
86 10
        return $this;
87
    }
88
89 10
    public function assertCountLessThan(int $expected, string $message = ''): self
90
    {
91 10
        Assert::assertLessThan($expected, $this->count(), $message);
92
93 10
        return $this;
94
    }
95
96 10
    public function assertCountLessThanOrEqual(int $expected, string $message = ''): self
97
    {
98 10
        Assert::assertLessThanOrEqual($expected, $this->count(), $message);
99
100 10
        return $this;
101
    }
102
103
    /**
104
     * @param object|array|mixed $criteria
105
     */
106 20
    public function assertExists($criteria, string $message = ''): self
107
    {
108 20
        Assert::assertNotNull($this->find($criteria), $message);
109
110 20
        return $this;
111
    }
112
113
    /**
114
     * @param object|array|mixed $criteria
115
     */
116 10
    public function assertNotExists($criteria, string $message = ''): self
117
    {
118 10
        Assert::assertNull($this->find($criteria), $message);
119
120 10
        return $this;
121
    }
122
123
    /**
124
     * @return Proxy|object|null
125
     */
126 20
    public function first(string $sortedField = 'id'): ?Proxy
127
    {
128 20
        return $this->findBy([], [$sortedField => 'ASC'], 1)[0] ?? null;
129
    }
130
131
    /**
132
     * @return Proxy|object|null
133
     */
134 20
    public function last(string $sortedField = 'id'): ?Proxy
135
    {
136 20
        return $this->findBy([], [$sortedField => 'DESC'], 1)[0] ?? null;
137
    }
138
139
    /**
140
     * Remove all rows.
141
     */
142 10
    public function truncate(): void
143
    {
144 10
        $om = Factory::configuration()->objectManagerFor($this->getClassName());
145
146 10
        if ($om instanceof EntityManagerInterface) {
147 10
            $om->createQuery("DELETE {$this->getClassName()} e")->execute();
148
149 10
            return;
150
        }
151
152
        foreach ($this as $object) {
153
            $om->remove($object);
0 ignored issues
show
Bug introduced by
$object of type array is incompatible with the type object expected by parameter $object of Doctrine\Persistence\ObjectManager::remove(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

153
            $om->remove(/** @scrutinizer ignore-type */ $object);
Loading history...
154
        }
155
156
        $om->flush();
157
    }
158
159
    /**
160
     * Fetch one random object.
161
     *
162
     * @return Proxy|object
163
     *
164
     * @throws \RuntimeException if no objects are persisted
165
     */
166 40
    public function random(): Proxy
167
    {
168 40
        return $this->randomSet(1)[0];
169
    }
170
171
    /**
172
     * Fetch a random set of objects.
173
     *
174
     * @param int $number The number of objects to return
175
     *
176
     * @return Proxy[]|object[]
177
     *
178
     * @throws \RuntimeException         if not enough persisted objects to satisfy the number requested
179
     * @throws \InvalidArgumentException if number is less than zero
180
     */
181 80
    public function randomSet(int $number): array
182
    {
183 80
        if ($number < 0) {
184 10
            throw new \InvalidArgumentException(\sprintf('$number must be positive (%d given).', $number));
185
        }
186
187 70
        return $this->randomRange($number, $number);
188
    }
189
190
    /**
191
     * Fetch a random range of objects.
192
     *
193
     * @param int $min The minimum number of objects to return
194
     * @param int $max The maximum number of objects to return
195
     *
196
     * @return Proxy[]|object[]
197
     *
198
     * @throws \RuntimeException         if not enough persisted objects to satisfy the max
199
     * @throws \InvalidArgumentException if min is less than zero
200
     * @throws \InvalidArgumentException if max is less than min
201
     */
202 120
    public function randomRange(int $min, int $max): array
203
    {
204 120
        if ($min < 0) {
205 10
            throw new \InvalidArgumentException(\sprintf('$min must be positive (%d given).', $min));
206
        }
207
208 110
        if ($max < $min) {
209 10
            throw new \InvalidArgumentException(\sprintf('$max (%d) cannot be less than $min (%d).', $max, $min));
210
        }
211
212 100
        $all = \array_values($this->findAll());
213
214 100
        \shuffle($all);
215
216 100
        if (\count($all) < $max) {
217 30
            throw new \RuntimeException(\sprintf('At least %d "%s" object(s) must have been persisted (%d persisted).', $max, $this->getClassName(), \count($all)));
218
        }
219
220 70
        return \array_slice($all, 0, \random_int($min, $max));
221
    }
222
223
    /**
224
     * @param object|array|mixed $criteria
225
     *
226
     * @return Proxy|object|null
227
     */
228 40
    public function find($criteria): ?Proxy
229
    {
230 40
        if ($criteria instanceof Proxy) {
231 10
            $criteria = $criteria->object();
232
        }
233
234 40
        if (!\is_array($criteria)) {
235 10
            return $this->proxyResult($this->repository->find($criteria));
236
        }
237
238 40
        return $this->findOneBy($criteria);
239
    }
240
241
    /**
242
     * @return Proxy[]|object[]
243
     */
244 120
    public function findAll(): array
245
    {
246 120
        return $this->proxyResult($this->repository->findAll());
247
    }
248
249
    /**
250
     * @return Proxy[]|object[]
251
     */
252 30
    public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
253
    {
254 30
        return $this->proxyResult($this->repository->findBy(self::normalizeCriteria($criteria), $orderBy, $limit, $offset));
255
    }
256
257
    /**
258
     * @param array|null $orderBy Some ObjectRepository's (ie Doctrine\ORM\EntityRepository) add this optional parameter
259
     *
260
     * @return Proxy|object|null
261
     *
262
     * @throws \RuntimeException if the wrapped ObjectRepository does not have the $orderBy parameter
263
     */
264 90
    public function findOneBy(array $criteria, ?array $orderBy = null): ?Proxy
265
    {
266 90
        if (\is_array($orderBy)) {
267 50
            $wrappedParams = (new \ReflectionClass($this->repository))->getMethod('findOneBy')->getParameters();
268
269 50
            if (!isset($wrappedParams[1]) || 'orderBy' !== $wrappedParams[1]->getName() || !$wrappedParams[1]->isArray()) {
270 40
                throw new \RuntimeException(\sprintf('Wrapped repository\'s (%s) findOneBy method does not have an $orderBy parameter.', \get_class($this->repository)));
271
            }
272
        }
273
274 90
        return $this->proxyResult($this->repository->findOneBy(self::normalizeCriteria($criteria), $orderBy));
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\Persistence\ObjectRepository::findOneBy() has too many arguments starting with $orderBy. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

274
        return $this->proxyResult($this->repository->/** @scrutinizer ignore-call */ findOneBy(self::normalizeCriteria($criteria), $orderBy));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
275
    }
276
277 190
    public function getClassName(): string
278
    {
279 190
        return $this->repository->getClassName();
280
    }
281
282
    /**
283
     * @param mixed $result
284
     *
285
     * @return Proxy|Proxy[]|object|object[]|mixed
286
     */
287 200
    private function proxyResult($result)
288
    {
289 200
        if (\is_object($result) && \is_a($result, $this->getClassName())) {
290 170
            return Proxy::createFromPersisted($result);
291
        }
292
293 170
        if (\is_array($result)) {
294 140
            return \array_map([$this, 'proxyResult'], $result);
295
        }
296
297 30
        return $result;
298
    }
299
300 120
    private static function normalizeCriteria(array $criteria): array
301
    {
302 120
        return \array_map(
303
            function($value) {
304 40
                return $value instanceof Proxy ? $value->object() : $value;
305 120
            },
306 120
            $criteria
307
        );
308
    }
309
}
310