Passed
Pull Request — master (#1)
by Kevin
02:38
created

RepositoryProxy::randomRange()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 9
c 0
b 0
f 0
nc 4
nop 2
dl 0
loc 19
rs 9.9666
ccs 10
cts 10
cp 1
crap 4
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
16
{
17
    /** @var ObjectRepository */
18
    private $repository;
19
20 92
    public function __construct(ObjectRepository $repository)
21
    {
22 92
        $this->repository = $repository;
23 92
    }
24
25 4
    public function __call(string $method, array $arguments)
26
    {
27 4
        return $this->proxyResult($this->repository->{$method}(...$arguments));
28
    }
29
30 20
    public function getCount(): int
31
    {
32 20
        if ($this->repository instanceof EntityRepository) {
33 20
            return $this->repository->count([]);
34
        }
35
36
        return \count($this->findAll());
37
    }
38
39 8
    public function assertEmpty(string $message = ''): self
40
    {
41 8
        return $this->assertCount(0, $message);
42
    }
43
44 20
    public function assertCount(int $expectedCount, string $message = ''): self
45
    {
46 20
        Assert::assertSame($expectedCount, $this->getCount(), $message);
47
48 20
        return $this;
49
    }
50
51 4
    public function assertCountGreaterThan(int $expected, string $message = ''): self
52
    {
53 4
        Assert::assertGreaterThan($expected, $this->getCount(), $message);
54
55 4
        return $this;
56
    }
57
58 4
    public function assertCountGreaterThanOrEqual(int $expected, string $message = ''): self
59
    {
60 4
        Assert::assertGreaterThanOrEqual($expected, $this->getCount(), $message);
61
62 4
        return $this;
63
    }
64
65 4
    public function assertCountLessThan(int $expected, string $message = ''): self
66
    {
67 4
        Assert::assertLessThan($expected, $this->getCount(), $message);
68
69 4
        return $this;
70
    }
71
72 4
    public function assertCountLessThanOrEqual(int $expected, string $message = ''): self
73
    {
74 4
        Assert::assertLessThanOrEqual($expected, $this->getCount(), $message);
75
76 4
        return $this;
77
    }
78
79
    /**
80
     * @param object|array|mixed $criteria
81
     */
82 8
    public function assertExists($criteria, string $message = ''): self
83
    {
84 8
        Assert::assertNotNull($this->find($criteria), $message);
85
86 8
        return $this;
87
    }
88
89
    /**
90
     * @param object|array|mixed $criteria
91
     */
92 4
    public function assertNotExists($criteria, string $message = ''): self
93
    {
94 4
        Assert::assertNull($this->find($criteria), $message);
95
96 4
        return $this;
97
    }
98
99
    /**
100
     * @return Proxy|object|null
101
     */
102 4
    public function first(): ?Proxy
103
    {
104 4
        return $this->findOneBy([]);
105
    }
106
107
    /**
108
     * Remove all rows.
109
     */
110 4
    public function truncate(): void
111
    {
112 4
        $om = Factory::configuration()->objectManagerFor($this->getClassName());
113
114 4
        if (!$om instanceof EntityManagerInterface) {
115
            throw new \RuntimeException('This operation is only available when using doctrine/orm');
116
        }
117
118 4
        $om->createQuery("DELETE {$this->getClassName()} e")->execute();
119 4
    }
120
121
    /**
122
     * Fetch one random object.
123
     *
124
     * @return Proxy|object
125
     *
126
     * @throws \RuntimeException if no objects are persisted
127
     */
128 12
    public function random(): Proxy
129
    {
130 12
        return $this->randomSet(1)[0];
131
    }
132
133
    /**
134
     * Fetch a random set of objects.
135
     *
136
     * @param int $number The number of objects to return
137
     *
138
     * @return Proxy[]|object[]
139
     *
140
     * @throws \RuntimeException         if not enough persisted objects to satisfy the number requested
141
     * @throws \InvalidArgumentException if number is less than zero
142
     */
143 28
    public function randomSet(int $number): array
144
    {
145 28
        if ($number < 0) {
146 4
            throw new \InvalidArgumentException(\sprintf('$number must be positive (%d given).', $number));
147
        }
148
149 24
        return $this->randomRange($number, $number);
150
    }
151
152
    /**
153
     * Fetch a random range of objects.
154
     *
155
     * @param int $min The minimum number of objects to return
156
     * @param int $max The maximum number of objects to return
157
     *
158
     * @return Proxy[]|object[]
159
     *
160
     * @throws \RuntimeException         if not enough persisted objects to satisfy the max
161
     * @throws \InvalidArgumentException if min is less than zero
162
     * @throws \InvalidArgumentException if max is less than min
163
     */
164 44
    public function randomRange(int $min, int $max): array
165
    {
166 44
        if ($min < 0) {
167 4
            throw new \InvalidArgumentException(\sprintf('$min must be positive (%d given).', $min));
168
        }
169
170 40
        if ($max < $min) {
171 4
            throw new \InvalidArgumentException(\sprintf('$max (%d) cannot be less than $min (%d).', $max, $min));
172
        }
173
174 36
        $all = \array_values($this->findAll());
175
176 36
        \shuffle($all);
177
178 36
        if (\count($all) < $max) {
179 12
            throw new \RuntimeException(\sprintf('At least %d "%s" object(s) must have been persisted (%d persisted).', $max, $this->getClassName(), \count($all)));
180
        }
181
182 24
        return \array_slice($all, 0, \random_int($min, $max));
183
    }
184
185
    /**
186
     * @param object|array|mixed $criteria
187
     *
188
     * @return Proxy|object|null
189
     */
190 16
    public function find($criteria): ?Proxy
191
    {
192 16
        if ($criteria instanceof Proxy) {
193 4
            $criteria = $criteria->object();
194
        }
195
196 16
        if (!\is_array($criteria)) {
197 4
            return $this->proxyResult($this->repository->find($criteria));
198
        }
199
200 16
        return $this->findOneBy($criteria);
201
    }
202
203
    /**
204
     * @return Proxy[]|object[]
205
     */
206 40
    public function findAll(): array
207
    {
208 40
        return $this->proxyResult($this->repository->findAll());
209
    }
210
211
    /**
212
     * @return Proxy[]|object[]
213
     */
214 4
    public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
215
    {
216 4
        return $this->proxyResult($this->repository->findBy(self::normalizeCriteria($criteria), $orderBy, $limit, $offset));
217
    }
218
219
    /**
220
     * @return Proxy|object|null
221
     */
222 20
    public function findOneBy(array $criteria): ?Proxy
223
    {
224 20
        return $this->proxyResult($this->repository->findOneBy(self::normalizeCriteria($criteria)));
225
    }
226
227 60
    public function getClassName(): string
228
    {
229 60
        return $this->repository->getClassName();
230
    }
231
232
    /**
233
     * @param mixed $result
234
     *
235
     * @return Proxy|Proxy[]|object|object[]|mixed
236
     */
237 60
    private function proxyResult($result)
238
    {
239 60
        if (\is_object($result) && $this->getClassName() === \get_class($result)) {
240 52
            return Proxy::persisted($result);
241
        }
242
243 52
        if (\is_array($result)) {
244 40
            return \array_map([$this, 'proxyResult'], $result);
245
        }
246
247 12
        return $result;
248
    }
249
250 20
    private static function normalizeCriteria(array $criteria): array
251
    {
252 20
        return \array_map(
253
            function($value) {
254 16
                return $value instanceof Proxy ? $value->object() : $value;
255 20
            },
256 20
            $criteria
257
        );
258
    }
259
}
260