Passed
Pull Request — master (#66)
by Kevin
19:16
created

RepositoryProxy::assertCountGreaterThanOrEqual()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 5
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 2
crap 1
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 272
    public function __construct(ObjectRepository $repository)
21
    {
22 272
        $this->repository = $repository;
23 272
    }
24
25 8
    public function __call(string $method, array $arguments)
26
    {
27 8
        return $this->proxyResult($this->repository->{$method}(...$arguments));
28
    }
29
30 96
    public function count(): int
31
    {
32 96
        if ($this->repository instanceof EntityRepository) {
33 96
            // use query to avoid loading all entities
34
            return $this->repository->count([]);
35
        }
36
37
        if ($this->repository instanceof \Countable) {
38
            return \count($this->repository);
39 32
        }
40
41 32
        return \count($this->findAll());
42
    }
43
44 96
    public function getIterator(): \Traversable
45
    {
46 96
        if (\is_iterable($this->repository)) {
47
            return yield from $this->repository;
48 96
        }
49
50
        yield from $this->findAll();
51 8
    }
52
53 8
    /**
54
     * @deprecated use Repository::count()
55 8
     */
56
    public function getCount(): int
57
    {
58 8
        return $this->count();
59
    }
60 8
61
    public function assertEmpty(string $message = ''): self
62 8
    {
63
        return $this->assertCount(0, $message);
64
    }
65 8
66
    public function assertCount(int $expectedCount, string $message = ''): self
67 8
    {
68
        Assert::assertSame($expectedCount, $this->count(), $message);
69 8
70
        return $this;
71
    }
72 8
73
    public function assertCountGreaterThan(int $expected, string $message = ''): self
74 8
    {
75
        Assert::assertGreaterThan($expected, $this->count(), $message);
76 8
77
        return $this;
78
    }
79
80
    public function assertCountGreaterThanOrEqual(int $expected, string $message = ''): self
81
    {
82 16
        Assert::assertGreaterThanOrEqual($expected, $this->count(), $message);
83
84 16
        return $this;
85
    }
86 16
87
    public function assertCountLessThan(int $expected, string $message = ''): self
88
    {
89
        Assert::assertLessThan($expected, $this->count(), $message);
90
91
        return $this;
92 8
    }
93
94 8
    public function assertCountLessThanOrEqual(int $expected, string $message = ''): self
95
    {
96 8
        Assert::assertLessThanOrEqual($expected, $this->count(), $message);
97
98
        return $this;
99
    }
100
101
    /**
102 16
     * @param object|array|mixed $criteria
103
     */
104 16
    public function assertExists($criteria, string $message = ''): self
105
    {
106
        Assert::assertNotNull($this->find($criteria), $message);
107
108
        return $this;
109
    }
110 16
111
    /**
112 16
     * @param object|array|mixed $criteria
113
     */
114
    public function assertNotExists($criteria, string $message = ''): self
115
    {
116
        Assert::assertNull($this->find($criteria), $message);
117
118 8
        return $this;
119
    }
120 8
121
    /**
122 8
     * @return Proxy|object|null
123
     */
124
    public function first(string $sortedField = 'id'): ?Proxy
125
    {
126 8
        return $this->findBy([], [$sortedField => 'ASC'], 1)[0] ?? null;
127 8
    }
128
129
    /**
130
     * @return Proxy|object|null
131
     */
132
    public function last(string $sortedField = 'id'): ?Proxy
133
    {
134
        return $this->findBy([], [$sortedField => 'DESC'], 1)[0] ?? null;
135
    }
136 32
137
    /**
138 32
     * Remove all rows.
139
     */
140
    public function truncate(): void
141
    {
142
        $om = Factory::configuration()->objectManagerFor($this->getClassName());
143
144
        if ($om instanceof EntityManagerInterface) {
145
            $om->createQuery("DELETE {$this->getClassName()} e")->execute();
146
147
            return;
148
        }
149
150
        foreach ($this as $object) {
151 64
            $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 64
154 8
        $om->flush();
155
    }
156
157 56
    /**
158
     * Fetch one random object.
159
     *
160
     * @return Proxy|object
161
     *
162
     * @throws \RuntimeException if no objects are persisted
163
     */
164
    public function random(): Proxy
165
    {
166
        return $this->randomSet(1)[0];
167
    }
168
169
    /**
170
     * Fetch a random set of objects.
171
     *
172 96
     * @param int $number The number of objects to return
173
     *
174 96
     * @return Proxy[]|object[]
175 8
     *
176
     * @throws \RuntimeException         if not enough persisted objects to satisfy the number requested
177
     * @throws \InvalidArgumentException if number is less than zero
178 88
     */
179 8
    public function randomSet(int $number): array
180
    {
181
        if ($number < 0) {
182 80
            throw new \InvalidArgumentException(\sprintf('$number must be positive (%d given).', $number));
183
        }
184 80
185
        return $this->randomRange($number, $number);
186 80
    }
187 24
188
    /**
189
     * Fetch a random range of objects.
190 56
     *
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 32
     * @throws \InvalidArgumentException if max is less than min
199
     */
200 32
    public function randomRange(int $min, int $max): array
201 8
    {
202
        if ($min < 0) {
203
            throw new \InvalidArgumentException(\sprintf('$min must be positive (%d given).', $min));
204 32
        }
205 8
206
        if ($max < $min) {
207
            throw new \InvalidArgumentException(\sprintf('$max (%d) cannot be less than $min (%d).', $max, $min));
208 32
        }
209
210
        $all = \array_values($this->findAll());
211
212
        \shuffle($all);
213
214 88
        if (\count($all) < $max) {
215
            throw new \RuntimeException(\sprintf('At least %d "%s" object(s) must have been persisted (%d persisted).', $max, $this->getClassName(), \count($all)));
216 88
        }
217
218
        return \array_slice($all, 0, \random_int($min, $max));
219
    }
220
221
    /**
222 24
     * @param object|array|mixed $criteria
223
     *
224 24
     * @return Proxy|object|null
225
     */
226
    public function find($criteria): ?Proxy
227
    {
228
        if ($criteria instanceof Proxy) {
229
            $criteria = $criteria->object();
230
        }
231
232 40
        if (!\is_array($criteria)) {
233
            return $this->proxyResult($this->repository->find($criteria));
234 40
        }
235
236
        return $this->findOneBy($criteria);
237 144
    }
238
239 144
    /**
240
     * @return Proxy[]|object[]
241
     */
242
    public function findAll(): array
243
    {
244
        return $this->proxyResult($this->repository->findAll());
245
    }
246
247 152
    /**
248
     * @return Proxy[]|object[]
249 152
     */
250 128
    public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
251
    {
252
        return $this->proxyResult($this->repository->findBy(self::normalizeCriteria($criteria), $orderBy, $limit, $offset));
253 128
    }
254 104
255
    /**
256
     * @param array|null $orderBy Some ObjectRepository's (ie Doctrine\ORM\EntityRepository) add this optional parameter
257 24
     *
258
     * @return Proxy|object|null
259
     *
260 64
     * @throws \RuntimeException if the wrapped ObjectRepository does not have the $orderBy parameter
261
     */
262 64
    public function findOneBy(array $criteria, ?array $orderBy = null): ?Proxy
263
    {
264 32
        if (\is_array($orderBy)) {
265 64
            $wrappedParams = (new \ReflectionClass($this->repository))->getMethod('findOneBy')->getParameters();
266 64
267
            if (!isset($wrappedParams[1]) || 'orderBy' !== $wrappedParams[1]->getName() || !$wrappedParams[1]->isArray()) {
268
                throw new \RuntimeException(\sprintf('Wrapped repository\'s (%s) findOneBy method does not have an $orderBy parameter.', \get_class($this->repository)));
269
            }
270
        }
271
272
        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
    public function getClassName(): string
276
    {
277
        return $this->repository->getClassName();
278
    }
279
280
    /**
281
     * @param mixed $result
282
     *
283
     * @return Proxy|Proxy[]|object|object[]|mixed
284
     */
285
    private function proxyResult($result)
286
    {
287
        if (\is_object($result) && \is_a($result, $this->getClassName())) {
288
            return Proxy::createFromPersisted($result);
289
        }
290
291
        if (\is_array($result)) {
292
            return \array_map([$this, 'proxyResult'], $result);
293
        }
294
295
        return $result;
296
    }
297
298
    private static function normalizeCriteria(array $criteria): array
299
    {
300
        return \array_map(
301
            function($value) {
302
                return $value instanceof Proxy ? $value->object() : $value;
303
            },
304
            $criteria
305
        );
306
    }
307
}
308