GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

AbstractEntityRepository   B
last analyzed

Complexity

Total Complexity 48

Size/Duplication

Total Lines 375
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 7
Bugs 0 Features 1
Metric Value
eloc 112
c 7
b 0
f 1
dl 0
loc 375
ccs 0
cts 218
cp 0
rs 8.5599
wmc 48

29 Methods

Rating   Name   Duplication   Size   Complexity  
A createNamedQuery() 0 3 1
A createNativeNamedQuery() 0 3 1
A createResultSetMappingBuilder() 0 3 1
A clear() 0 3 1
A getRandomResultFromQueryBuilder() 0 19 3
A getOneBy() 0 8 2
A getCountForQueryBuilder() 0 6 1
A find() 0 10 3
A __construct() 0 9 1
A mapCriteriaSetUuidsToStrings() 0 12 4
A initialiseEntities() 0 7 2
A getAlias() 0 12 2
A get() 0 14 3
A getRandomBy() 0 9 2
A getClassName() 0 3 1
A createQueryBuilderWithAlias() 0 3 1
A aliasPrefix() 0 3 1
A createDeletionQueryBuilderWithAlias() 0 3 1
A findAll() 0 3 1
A createQueryBuilder() 0 5 1
A initRepository() 0 8 2
A getEntityFqn() 0 10 1
A initialiseEntity() 0 5 1
A findOneBy() 0 11 3
A count() 0 5 1
A findBy() 0 5 1
A createDeletionQueryBuilder() 0 4 1
A getRandomOneBy() 0 11 3
A matching() 0 5 2

How to fix   Complexity   

Complex Class

Complex classes like AbstractEntityRepository 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 AbstractEntityRepository, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace EdmondsCommerce\DoctrineStaticMeta\Entity\Repositories;
6
7
use Doctrine\Common\Collections\Criteria;
8
use Doctrine\DBAL\Types\ConversionException;
9
use Doctrine\ORM\EntityManagerInterface;
10
use Doctrine\ORM\EntityRepository;
11
use Doctrine\ORM\LazyCriteriaCollection;
12
use Doctrine\ORM\Mapping\ClassMetadata;
13
use Doctrine\ORM\NativeQuery;
14
use Doctrine\ORM\Query;
15
use Doctrine\ORM\QueryBuilder;
16
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\NamespaceHelper;
17
use EdmondsCommerce\DoctrineStaticMeta\Entity\Factory\EntityFactoryInterface;
18
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\EntityInterface;
19
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException;
20
use Ramsey\Uuid\UuidInterface;
21
use RuntimeException;
22
23
use function str_replace;
24
25
/**
26
 * Class AbstractEntityRepository
27
 *
28
 * This provides a base class that handles instantiating the correctly configured EntityRepository and provides an
29
 * extensible baseline for further customisation
30
 *
31
 * We have extracted an interface from the standard Doctrine EntityRepository and implemented that
32
 * However, so we can add type safety, we can't "actually" implement it
33
 *
34
 * We have also deliberately left out the magic calls. Please make real methods in your concrete repository class
35
 *
36
 * Note, there are quite a few PHPMD warnings, however it needs to respect the legacy interface so they are being
37
 * suppressed
38
 *
39
 * @package EdmondsCommerce\DoctrineStaticMeta\Entity\Repositories
40
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
41
 * @SuppressWarnings(PHPMD.ExcessivePublicCount)
42
 * @SuppressWarnings(PHPMD.NumberOfChildren)
43
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
44
 */
45
abstract class AbstractEntityRepository implements EntityRepositoryInterface
46
{
47
    /**
48
     * @var array
49
     */
50
    protected static $aliasCache;
51
    /**
52
     * @var EntityManagerInterface
53
     */
54
    protected $entityManager;
55
    /**
56
     * @var EntityRepository
57
     */
58
    protected $entityRepository;
59
    /**
60
     * @var string
61
     */
62
    protected $repositoryFactoryFqn;
63
    /**
64
     * @var ClassMetadata|null
65
     */
66
    protected $metaData;
67
    /**
68
     * @var NamespaceHelper
69
     */
70
    protected $namespaceHelper;
71
    /**
72
     * @var EntityFactoryInterface
73
     */
74
    private $entityFactory;
75
76
    /**
77
     * AbstractEntityRepositoryFactory constructor.
78
     *
79
     * @param EntityManagerInterface $entityManager
80
     * @param EntityFactoryInterface $entityFactory
81
     * @param NamespaceHelper|null   $namespaceHelper
82
     */
83
    public function __construct(
84
        EntityManagerInterface $entityManager,
85
        EntityFactoryInterface $entityFactory,
86
        NamespaceHelper $namespaceHelper
87
    ) {
88
        $this->entityManager   = $entityManager;
89
        $this->namespaceHelper = $namespaceHelper;
90
        $this->entityFactory   = $entityFactory;
91
        $this->initRepository();
92
    }
93
94
    protected function initRepository(): void
95
    {
96
        if (null === $this->metaData) {
97
            $entityFqn      = $this->getEntityFqn();
98
            $this->metaData = $this->entityManager->getClassMetadata($entityFqn);
99
        }
100
101
        $this->entityRepository = new EntityRepository($this->entityManager, $this->metaData);
102
    }
103
104
    protected function getEntityFqn(): string
105
    {
106
        return '\\' . str_replace(
107
            [
108
                    'Entity\\Repositories',
109
                ],
110
            [
111
                    'Entities',
112
                ],
113
            $this->namespaceHelper->cropSuffix(static::class, 'Repository')
114
        );
115
    }
116
117
    public function getRandomResultFromQueryBuilder(QueryBuilder $queryBuilder, string $entityAlias): ?EntityInterface
118
    {
119
        $count = $this->getCountForQueryBuilder($queryBuilder, $entityAlias);
120
        if (0 === $count) {
121
            return null;
122
        }
123
124
        $queryBuilder->setMaxResults(1);
125
        $limitIndex = random_int(0, $count - 1);
126
        $results    = $queryBuilder->getQuery()
127
                                   ->setFirstResult($limitIndex)
128
                                   ->execute();
129
        $entity     = current($results);
130
        if (null === $entity) {
131
            return null;
132
        }
133
        $this->initialiseEntity($entity);
134
135
        return $entity;
136
    }
137
138
    public function getCountForQueryBuilder(QueryBuilder $queryBuilder, string $aliasToCount): int
139
    {
140
        $clone = clone $queryBuilder;
141
        $clone->select($queryBuilder->expr()->count($aliasToCount));
142
143
        return (int)$clone->getQuery()->getSingleScalarResult();
144
    }
145
146
    public function initialiseEntity(EntityInterface $entity)
147
    {
148
        $this->entityFactory->initialiseEntity($entity);
149
150
        return $entity;
151
    }
152
153
    /**
154
     * @return array|EntityInterface[]
155
     */
156
    public function findAll(): array
157
    {
158
        return $this->initialiseEntities($this->entityRepository->findAll());
159
    }
160
161
    public function initialiseEntities($entities)
162
    {
163
        foreach ($entities as $entity) {
164
            $this->initialiseEntity($entity);
165
        }
166
167
        return $entities;
168
    }
169
170
    /**
171
     * @param mixed    $id
172
     * @param int|null $lockMode
173
     * @param int|null $lockVersion
174
     *
175
     * @return EntityInterface
176
     * @throws DoctrineStaticMetaException
177
     */
178
    public function get($id, ?int $lockMode = null, ?int $lockVersion = null)
179
    {
180
        try {
181
            $entity = $this->find($id, $lockMode, $lockVersion);
182
        } catch (ConversionException $e) {
183
            $error = 'Failed getting by id ' . $id
184
                     . ', unless configured as an int ID entity, this should be a valid UUID';
185
            throw new DoctrineStaticMetaException($error, $e->getCode(), $e);
186
        }
187
        if ($entity === null) {
188
            throw new DoctrineStaticMetaException('Could not find the entity with id ' . $id);
189
        }
190
191
        return $this->initialiseEntity($entity);
192
    }
193
194
    /**
195
     * @param mixed    $id
196
     * @param int|null $lockMode
197
     * @param int|null $lockVersion
198
     *
199
     * @return EntityInterface|null
200
     */
201
    public function find($id, ?int $lockMode = null, ?int $lockVersion = null)
202
    {
203
        $entity = $this->entityRepository->find($id, $lockMode, $lockVersion);
204
        if (null === $entity) {
205
            return null;
206
        }
207
        if ($entity instanceof EntityInterface) {
208
            $this->initialiseEntity($entity);
209
210
            return $entity;
211
        }
212
    }
213
214
    /**
215
     * @param array      $criteria
216
     * @param array|null $orderBy
217
     *
218
     * @return EntityInterface
219
     */
220
    public function getOneBy(array $criteria, ?array $orderBy = null)
221
    {
222
        $result = $this->findOneBy($criteria, $orderBy);
223
        if ($result === null) {
224
            throw new RuntimeException('Could not find the entity');
225
        }
226
227
        return $this->initialiseEntity($result);
228
    }
229
230
    /**
231
     * @param array      $criteria
232
     * @param array|null $orderBy
233
     *
234
     * @return EntityInterface|null
235
     */
236
    public function findOneBy(array $criteria, ?array $orderBy = null)
237
    {
238
        $criteria = $this->mapCriteriaSetUuidsToStrings($criteria);
239
        $entity   = $this->entityRepository->findOneBy($criteria, $orderBy);
240
        if (null === $entity) {
241
            return null;
242
        }
243
        if ($entity instanceof EntityInterface) {
244
            $this->initialiseEntity($entity);
245
246
            return $entity;
247
        }
248
    }
249
250
    public function mapCriteriaSetUuidsToStrings(array $criteria): array
251
    {
252
        foreach ($criteria as $property => $value) {
253
            if ($value instanceof EntityInterface) {
254
                $criteria[$property] = $value->getId();
255
            }
256
            if ($value instanceof UuidInterface) {
257
                $criteria[$property] = $value->toString();
258
            }
259
        }
260
261
        return $criteria;
262
    }
263
264
    /**
265
     * @param array $criteria
266
     *
267
     * @return EntityInterface|null
268
     */
269
    public function getRandomOneBy(array $criteria)
270
    {
271
        $found = $this->getRandomBy($criteria);
272
        if ([] === $found) {
273
            throw new RuntimeException('Failed finding any Entities with this criteria');
274
        }
275
        $entity = current($found);
276
        if ($entity instanceof EntityInterface) {
277
            return $entity;
278
        }
279
        throw new RuntimeException('Unexpected Entity Type ' . get_class($entity));
280
    }
281
282
    /**
283
     * @param array $criteria
284
     *
285
     * @param int   $numToGet
286
     *
287
     * @return EntityInterface[]|array
288
     */
289
    public function getRandomBy(array $criteria, int $numToGet = 1): array
290
    {
291
        $count = $this->count($criteria);
292
        if (0 === $count) {
293
            return [];
294
        }
295
        $randOffset = rand(0, $count - $numToGet);
296
297
        return $this->findBy($criteria, null, $numToGet, $randOffset);
298
    }
299
300
    public function count(array $criteria = []): int
301
    {
302
        $criteria = $this->mapCriteriaSetUuidsToStrings($criteria);
303
304
        return $this->entityRepository->count($criteria);
305
    }
306
307
    /**
308
     * @param array      $criteria
309
     * @param array|null $orderBy
310
     * @param int|null   $limit
311
     * @param int|null   $offset
312
     *
313
     * @return array|EntityInterface[]
314
     */
315
    public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
316
    {
317
        $criteria = $this->mapCriteriaSetUuidsToStrings($criteria);
318
319
        return $this->initialiseEntities($this->entityRepository->findBy($criteria, $orderBy, $limit, $offset));
320
    }
321
322
    public function matching(Criteria $criteria): LazyCriteriaCollection
323
    {
324
        $collection = $this->entityRepository->matching($criteria);
325
        if ($collection instanceof LazyCriteriaCollection) {
0 ignored issues
show
introduced by
$collection is always a sub-type of Doctrine\ORM\LazyCriteriaCollection.
Loading history...
326
            return $this->initialiseEntities($collection);
327
        }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 325 is false. This is incompatible with the type-hinted return Doctrine\ORM\LazyCriteriaCollection. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
328
    }
329
330
    public function createResultSetMappingBuilder(string $alias): Query\ResultSetMappingBuilder
331
    {
332
        return $this->entityRepository->createResultSetMappingBuilder($alias);
333
    }
334
335
    public function createNamedQuery(string $queryName): Query
336
    {
337
        return $this->entityRepository->createNamedQuery($queryName);
338
    }
339
340
    public function createNativeNamedQuery(string $queryName): NativeQuery
341
    {
342
        return $this->entityRepository->createNativeNamedQuery($queryName);
343
    }
344
345
    public function clear(): void
346
    {
347
        $this->entityRepository->clear();
348
    }
349
350
    /**
351
     * Create a query builder with the alias preset
352
     *
353
     * @param string|null $indexBy
354
     *
355
     * @return QueryBuilder
356
     */
357
    public function createQueryBuilderWithAlias(string $indexBy = null): QueryBuilder
358
    {
359
        return $this->createQueryBuilder($this->getAlias(), $indexBy);
360
    }
361
362
    public function createQueryBuilder(string $alias, string $indexBy = null): QueryBuilder
363
    {
364
        return (new UuidQueryBuilder($this->entityManager))
365
            ->select($alias)
366
            ->from($this->getClassName(), $alias, $indexBy);
367
    }
368
369
    public function getClassName(): string
370
    {
371
        return $this->entityRepository->getClassName();
372
    }
373
374
    /**
375
     * Generate an alias based on the class name
376
     *
377
     * removes the words entity and repository
378
     *
379
     * gets all the upper case letters and returns them as a lower case string
380
     *
381
     * Warning - nothing is done to guarantee uniqueness for now
382
     *
383
     * @return string
384
     */
385
    public function getAlias(): string
386
    {
387
        if (isset(static::$aliasCache[static::class])) {
388
            return static::$aliasCache[static::class];
389
        }
390
        $class           = $this->namespaceHelper->getClassShortName($this->getClassName());
391
        $removeStopWords = str_ireplace(['entity', 'repository'], '', $class);
392
        $ucOnly          = preg_replace('%[^A-Z]%', '', $removeStopWords);
393
394
        static::$aliasCache[static::class] = strtolower($ucOnly);
395
396
        return static::$aliasCache[static::class];
397
    }
398
399
    public function createDeletionQueryBuilderWithAlias(): QueryBuilder
400
    {
401
        return $this->createDeletionQueryBuilder($this->getAlias());
402
    }
403
404
    public function createDeletionQueryBuilder(string $alias): QueryBuilder
405
    {
406
        return (new UuidQueryBuilder($this->entityManager))
407
            ->delete($this->getClassName(), $alias);
408
    }
409
410
    /**
411
     * For use with query builder, auto prefix alias
412
     *
413
     * @param string $property
414
     *
415
     * @return string
416
     */
417
    public function aliasPrefix(string $property): string
418
    {
419
        return $this->getAlias() . '.' . $property;
420
    }
421
}
422