Passed
Push — master ( 1c4364...e333d7 )
by Kevin
15:30 queued 13:35
created

RepositoryProxy::assertCountGreaterThanOrEqual()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 10
c 1
b 0
f 0
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;
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 312
    public function __construct(ObjectRepository $repository)
21
    {
22 312
        $this->repository = $repository;
23 312
    }
24
25 8
    public function __call(string $method, array $arguments)
26
    {
27 8
        return $this->proxyResult($this->repository->{$method}(...$arguments));
28
    }
29
30 104
    public function count(): int
31
    {
32 104
        if ($this->repository instanceof EntityRepository) {
33
            // use query to avoid loading all entities
34 104
            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 8
    public function getIterator(): \Traversable
45
    {
46 8
        if (\is_iterable($this->repository)) {
47
            return yield from $this->repository;
48
        }
49
50 8
        yield from $this->findAll();
51 8
    }
52
53
    /**
54
     * @deprecated use Repository::count()
55
     */
56
    public function getCount(): int
57
    {
58
        return $this->count();
59
    }
60
61 32
    public function assertEmpty(string $message = ''): self
62
    {
63 32
        return $this->assertCount(0, $message);
64
    }
65
66 96
    public function assertCount(int $expectedCount, string $message = ''): self
67
    {
68 96
        Assert::assertSame($expectedCount, $this->count(), $message);
69
70 96
        return $this;
71
    }
72
73 8
    public function assertCountGreaterThan(int $expected, string $message = ''): self
74
    {
75 8
        Assert::assertGreaterThan($expected, $this->count(), $message);
76
77 8
        return $this;
78
    }
79
80 8
    public function assertCountGreaterThanOrEqual(int $expected, string $message = ''): self
81
    {
82 8
        Assert::assertGreaterThanOrEqual($expected, $this->count(), $message);
83
84 8
        return $this;
85
    }
86
87 8
    public function assertCountLessThan(int $expected, string $message = ''): self
88
    {
89 8
        Assert::assertLessThan($expected, $this->count(), $message);
90
91 8
        return $this;
92
    }
93
94 8
    public function assertCountLessThanOrEqual(int $expected, string $message = ''): self
95
    {
96 8
        Assert::assertLessThanOrEqual($expected, $this->count(), $message);
97
98 8
        return $this;
99
    }
100
101
    /**
102
     * @param object|array|mixed $criteria
103
     */
104 16
    public function assertExists($criteria, string $message = ''): self
105
    {
106 16
        Assert::assertNotNull($this->find($criteria), $message);
107
108 16
        return $this;
109
    }
110
111
    /**
112
     * @param object|array|mixed $criteria
113
     */
114 8
    public function assertNotExists($criteria, string $message = ''): self
115
    {
116 8
        Assert::assertNull($this->find($criteria), $message);
117
118 8
        return $this;
119
    }
120
121
    /**
122
     * @return Proxy|object|null
123
     */
124 16
    public function first(string $sortedField = 'id'): ?Proxy
125
    {
126 16
        return $this->findBy([], [$sortedField => 'ASC'], 1)[0] ?? null;
127
    }
128
129
    /**
130
     * @return Proxy|object|null
131
     */
132 16
    public function last(string $sortedField = 'id'): ?Proxy
133
    {
134 16
        return $this->findBy([], [$sortedField => 'DESC'], 1)[0] ?? null;
135
    }
136
137
    /**
138
     * Remove all rows.
139
     */
140 8
    public function truncate(): void
141
    {
142 8
        $om = Factory::configuration()->objectManagerFor($this->getClassName());
143
144 8
        if ($om instanceof EntityManagerInterface) {
145 8
            $om->createQuery("DELETE {$this->getClassName()} e")->execute();
146
147 8
            return;
148
        }
149
150
        foreach ($this as $object) {
151
            $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

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

272
        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...
273
    }
274
275 152
    public function getClassName(): string
276
    {
277 152
        return $this->repository->getClassName();
278
    }
279
280
    /**
281
     * @param mixed $result
282
     *
283
     * @return Proxy|Proxy[]|object|object[]|mixed
284
     */
285 160
    private function proxyResult($result)
286
    {
287 160
        if (\is_object($result) && \is_a($result, $this->getClassName())) {
288 136
            return Proxy::createFromPersisted($result);
289
        }
290
291 136
        if (\is_array($result)) {
292 112
            return \array_map([$this, 'proxyResult'], $result);
293
        }
294
295 24
        return $result;
296
    }
297
298 96
    private static function normalizeCriteria(array $criteria): array
299
    {
300 96
        return \array_map(
301
            function($value) {
302 32
                return $value instanceof Proxy ? $value->object() : $value;
303 96
            },
304 96
            $criteria
305
        );
306
    }
307
}
308