Passed
Push — master ( bee307...3712bb )
by Marcel
08:35 queued 35s
created

UserRepository::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 1
c 1
b 0
f 1
nc 1
nop 1
dl 0
loc 2
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
namespace App\Repository;
4
5
use App\Entity\ActiveDirectoryUser;
6
use App\Entity\User;
7
use App\Entity\UserRole;
8
use App\Entity\UserType;
9
use DateTime;
10
use Doctrine\ORM\EntityManagerInterface;
11
use Doctrine\ORM\Query;
12
use Doctrine\ORM\QueryBuilder;
13
use Doctrine\ORM\Tools\Pagination\Paginator;
14
15
class UserRepository implements UserRepositoryInterface {
16
17
    private $em;
18
    private $isInTransaction = false;
19 14
20 14
    public function __construct(EntityManagerInterface $objectManager) {
21 14
        $this->em = $objectManager;
22
    }
23
24
    public function beginTransaction(): void {
25
        $this->em->beginTransaction();
26
        $this->isInTransaction = true;
27
    }
28
29
    public function commit(): void {
30
        if(!$this->isInTransaction) {
31
            return;
32
        }
33
34
        $this->em->flush();
35
        $this->em->commit();
36
        $this->isInTransaction = false;
37
    }
38
39
    public function findAll($offset = 0, $limit = null, bool $deleted = false) {
40
        $qb = $this->em
41
            ->createQueryBuilder()
42
            ->select('u')
43
            ->from(User::class, 'u')
44
            ->orderBy('u.username', 'asc')
45
            ->setFirstResult($offset);
46
47
        if($deleted === true) {
48
            $qb->where($qb->expr()->isNotNull('u.deletedAt'));
49
        } else {
50
            $qb->where($qb->expr()->isNull('u.deletedAt'));
51
        }
52
53
        if($limit !== null) {
54
            $qb->setMaxResults($limit);
55
        }
56
57
        return $qb->getQuery()->getResult();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $qb->getQuery()->getResult() also could return the type integer which is incompatible with the return type mandated by App\Repository\UserRepositoryInterface::findAll() of App\Entity\User[].
Loading history...
58
    }
59
60
    public function findUsersByUsernames(array $usernames) {
61
        $qb = $this->em->createQueryBuilder();
62
63
        $qb->select(['u', 'a', 'r', 't'])
64
            ->from(User::class, 'u')
65
            ->leftJoin('u.attributes', 'a')
66
            ->leftJoin('u.userRoles', 'r')
67
            ->leftJoin('u.type', 't')
68
            ->where('u.username IN (:usernames)')
69
            ->setParameter('usernames', $usernames);
70
71
        return $qb->getQuery()->getResult();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $qb->getQuery()->getResult() also could return the type integer which is incompatible with the return type mandated by App\Repository\UserRepos...:findUsersByUsernames() of App\Entity\User[].
Loading history...
72
    }
73
74
    public function findUsersUpdatedAfter(\DateTime $dateTime, array $usernames = [ ]) {
75
        $qb = $this->em
76
            ->createQueryBuilder();
77
78
        $qb->select(['DISTINCT u.username'])
79
            ->from(User::class, 'u')
80
            ->leftJoin('u.attributes', 'a')
81
            ->where(
82
                $qb->expr()->orX(
83
                    $qb->expr()->andX(
84
                        $qb->expr()->isNotNull('u.updatedAt'),
85
                        $qb->expr()->gt('u.updatedAt', ':datetime')
86
                    ),
87
                    $qb->expr()->andX(
88
                        $qb->expr()->isNotNull('a.updatedAt'),
89
                        $qb->expr()->gt('a.updatedAt', ':datetime')
90
                    )
91
                )
92
            )
93
            ->setParameter('datetime', $dateTime);
94
95
        if(count($usernames) > 0) {
96
            $qb->andWhere('u.username IN (:usernames)')
97
                ->setParameter('usernames', $usernames);
98
        }
99
100
        $usernames = $qb->getQuery()->getScalarResult();
101
102
        return $this->findUsersByUsernames($usernames);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->findUsersByUsernames($usernames) also could return the type integer which is incompatible with the return type mandated by App\Repository\UserRepos...findUsersUpdatedAfter() of App\Entity\User[].
Loading history...
103
    }
104 1
105 1
    public function findOneByUsername(string $username): ?User {
106
        $qb = $this->em->createQueryBuilder();
107 1
108 1
        $qb->select(['u', 'a', 'r', 't'])
109 1
            ->from(User::class, 'u')
110 1
            ->leftJoin('u.attributes', 'a')
111 1
            ->leftJoin('u.userRoles', 'r')
112 1
            ->leftJoin('u.type', 't')
113 1
            ->where('u.username = :username')
114
            ->setParameter('username', $username);
115 1
116
        $result = $qb->getQuery()->getResult();
117 1
118
        if(count($result) === 0) {
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type integer; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

118
        if(count(/** @scrutinizer ignore-type */ $result) === 0) {
Loading history...
119
            return null;
120
        }
121 1
122
        return $result[0];
123
    }
124
125
    private function createDefaultQueryBuilder(): QueryBuilder {
126
        return $this->em
127
            ->createQueryBuilder()
128
            ->select(['u', 'a', 'r', 't'])
129
            ->from(User::class, 'u')
130
            ->leftJoin('u.attributes', 'a')
131
            ->leftJoin('u.userRoles', 'r')
132
            ->leftJoin('u.type', 't');
133
    }
134
135
    /**
136
     * @inheritDoc
137
     */
138
    public function findOneByEmail(string $email): ?User {
139
        return $this->createDefaultQueryBuilder()
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->createDefa...)->getOneOrNullResult() could return the type integer which is incompatible with the type-hinted return App\Entity\User|null. Consider adding an additional type-check to rule them out.
Loading history...
140
            ->where('u.email = :email')
141
            ->setParameter('email', $email)
142
            ->setMaxResults(1)
143
            ->getQuery()
144
            ->getOneOrNullResult();
145
    }
146 1
147 1
    public function persist(User $user) {
148 1
        $this->em->persist($user);
149 1
        if($this->isInTransaction === false) {
150
            $this->em->flush();
151
        }
152
    }
153
154
    public function remove(User $user) {
155
        $this->em->remove($user);
156
        if($this->isInTransaction === false) {
157
            $this->em->flush();
158
        }
159
    }
160
161
    /**
162
     * @inheritDoc
163
     */
164
    public function getPaginatedUsers(int $itemsPerPage, int &$page, ?UserType $type = null, ?UserRole $role = null, ?string $query = null, ?string $grade = null, bool $deleted = false, bool $onlyNotLinked = false): Paginator {
165
        $qb = $this->em
166
            ->createQueryBuilder()
167
            ->select('u')
168
            ->from(User::class, 'u')
169
            ->orderBy('u.username', 'asc');
170
171
        $qbInner = $this->em
172
            ->createQueryBuilder()
173
            ->select('DISTINCT uInner.id')
174
            ->from(User::class, 'uInner')
175
            ->leftJoin('uInner.userRoles', 'rInner')
176
            ->leftJoin('uInner.linkedStudents', 'sInner');
177
178
        if(!empty($grade)) {
179
            $qbInner
180
                ->andWhere(
181
                    $qbInner->expr()->orX(
182
                        'uInner.grade = :grade',
183
                        'sInner.grade = :grade'
184
                    )
185
                );
186
            $qb->setParameter('grade', $grade);
187
        }
188
189
        if(!empty($query)) {
190
            $qbInner
191
                ->andWhere(
192
                    $qb->expr()->orX(
193
                        'uInner.username LIKE :query',
194
                        'uInner.firstname LIKE :query',
195
                        'uInner.lastname LIKE :query',
196
                        'uInner.email LIKE :query'
197
                    )
198
                );
199
            $qb->setParameter('query', '%' . $query . '%');
200
        }
201
202
        if($type !== null) {
203
            $qbInner
204
                ->andWhere(
205
                    'u.type = :type'
206
                );
207
            $qb->setParameter('type', $type);
208
        }
209
210
        if($role !== null) {
211
            $qbInner->andWhere(
212
                'rInner.id = :role'
213
            );
214
            $qb->setParameter('role', $role);
215
        }
216
217
        if($deleted === true) {
218
            $qbInner->andWhere($qb->expr()->isNotNull('u.deletedAt'));
219
        } else {
220
            $qbInner->andWhere($qb->expr()->isNull('u.deletedAt'));
221
        }
222
223
        if(!is_numeric($page) || $page < 1) {
0 ignored issues
show
introduced by
The condition is_numeric($page) is always true.
Loading history...
224
            $page = 1;
225
        }
226
227
        $qb->where(
228
            $qb->expr()->in('u.id', $qbInner->getDQL())
229
        );
230
231
        if($type !== null && $type->getAlias() === 'student' && $onlyNotLinked === true) {
232
            $qb->andWhere(
233
                $qb->expr()->in('u.id',
234
                    $this->em->createQueryBuilder()
235
                        ->select('uStudentInner.id')
236
                        ->from(User::class, 'uStudentInner')
237
                        ->leftJoin('uStudentInner.parents', 'pStudentInner')
238
                        ->where('pStudentInner.id IS NULL')
239
                        ->getDQL()
240
                )
241
            );
242
        }
243
244
        $offset = ($page - 1) * $itemsPerPage;
245
246
        $paginator = new Paginator($qb);
247
        $paginator->getQuery()
248
            ->setMaxResults($itemsPerPage)
249
            ->setFirstResult($offset);
250
251
        return $paginator;
252
    }
253
254
255
    /**
256
     * @inheritDoc
257
     */
258
    public function findActiveDirectoryUserByObjectGuid(string $guid): ?ActiveDirectoryUser {
259
        return $this->em->getRepository(ActiveDirectoryUser::class)
260
            ->findOneBy(['objectGuid' => $guid]);
261
    }
262
263
    /**
264
     * @inheritDoc
265
     */
266
    public function findAllActiveDirectoryUsersObjectGuid(): array {
267
        return array_map(function(array $item) {
268 5
            return $item['objectGuid'];
269 5
        },
270 5
            $this->em->createQueryBuilder()
271 5
                ->select('u.objectGuid')
272 5
                ->from(ActiveDirectoryUser::class, 'u')
273
                ->getQuery()
274
                ->getScalarResult()
275
        );
276
    }
277
278
    /**
279
     * @inheritDoc
280
     */
281
    public function findAllUuids($offset = 0, $limit = null) {
282
        $qb = $this->em
283
            ->createQueryBuilder()
284
            ->select('u.uuid')
285
            ->from(User::class, 'u')
286
            ->orderBy('u.username', 'asc')
287
            ->setFirstResult($offset);
288
289
        if($limit !== null) {
290
            $qb->setMaxResults($limit);
291
        }
292
293
        return array_map(function(array $item) {
294
            return $item['uuid'];
295
        }, $qb->getQuery()->getScalarResult());
296
    }
297
298
    public function findOneByExternalId(string $externalId): ?User {
299
        return $this->em
300
            ->getRepository(User::class)
301
            ->findOneBy([
302
                'externalId' => $externalId
303
            ]);
304
    }
305
306
    public function findOneByUuid(string $uuid): ?User {
307
        return $this->em
308
            ->getRepository(User::class)
309
            ->findOneBy([
310
                'uuid' => $uuid
311
            ]);
312
    }
313
314
    public function findOneById(int $id): ?User {
315
        return $this->em
316
            ->getRepository(User::class)
317
            ->findOneBy([
318
                'id' => $id
319
            ]);
320
    }
321
322
    /**
323
     * @inheritDoc
324
     */
325
    public function findNextNonProvisionedUsers(int $limit): array {
326
        return $this->em
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->em->create...getQuery()->getResult() could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
327
            ->createQueryBuilder()
328
            ->select('u')
329
            ->from(User::class, 'u')
330
            ->orderBy('u.createdAt', 'asc')
331
            ->where('u.isProvisioned = false')
332
            ->setMaxResults($limit)
333
            ->getQuery()
334
            ->getResult();
335
    }
336
337
    /**
338
     * @inheritDoc
339
     */
340
    public function countUsers(?UserType $userType = null): int {
341
        $qb = $this->em->createQueryBuilder();
342
343
        $qb
344
            ->select('COUNT(u.id)')
345
            ->from(User::class, 'u')
346
            ->where($qb->expr()->isNull('u.deletedAt'));
347
348
        if($userType !== null) {
349
            $qb->andWhere('u.type = :type')
350
                ->setParameter('type', $userType);
351
        }
352
353
        return $qb->getQuery()
354
            ->getSingleScalarResult();
355
    }
356
357
    /**
358
     * @inheritDoc
359
     */
360
    public function findAllExternalIdsByExternalIdList(array $externalIds): array {
361
        if(count($externalIds) === 0) {
362
            return [];
363
        }
364
365
        $qb = $this->em->createQueryBuilder();
366
        $qb
367
            ->select('u.externalId')
368
            ->from(User::class, 'u')
369
            ->where($qb->expr()->in('u.externalId', ':ids'))
370
            ->setParameter('ids', $externalIds);
371
372
        $result = $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
373
374
        return array_map(function($row) {
375
            return $row['externalId'];
376
        }, $result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type integer; however, parameter $array of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

376
        }, /** @scrutinizer ignore-type */ $result);
Loading history...
377
    }
378
379
    /**
380
     * @inheritDoc
381
     */
382
    public function removeDeletedUsers(DateTime $threshold): int {
383
        $qb = $this->em->createQueryBuilder();
384
385
        $qb->delete(User::class, 'u')
386
            ->where($qb->expr()->isNotNull('u.deletedAt'))
387
            ->andWhere('u.deletedAt < :threshold')
388
            ->setParameter('threshold', $threshold);
389
390
        return $qb->getQuery()->execute();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $qb->getQuery()->execute() could return the type array<mixed,mixed> which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
391
    }
392
393
    public function findParentUsersWithoutStudents(): array {
394
        $qbInner = $this->em->createQueryBuilder()
395
            ->select('uInner.id')
396
            ->from(User::class, 'uInner')
397
            ->leftJoin('uInner.type', 'tInner')
398
            ->leftJoin('uInner.linkedStudents', 'sInner')
399
            ->where("tInner.alias = 'parent'")
400
            ->andWhere('sInner.id IS NULL');
401
402
        $qb = $this->createDefaultQueryBuilder();
403
404
        $qb->where(
405
            $qb->expr()->in('u.id', $qbInner->getDQL())
406
        );
407
408
        return $qb->getQuery()->getResult();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $qb->getQuery()->getResult() could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
409
    }
410
411
    /**
412
     * @inheritDoc
413
     */
414
    public function findAllStudentsWithoutParents(): array {
415
        $qbInner = $this->em->createQueryBuilder()
416
            ->select('uInner.id')
417
            ->from(User::class, 'uInner')
418
            ->leftJoin('uInner.parents', 'pInner')
419
            ->leftJoin('uInner.type', 'tInner')
420
            ->where('pInner.id IS NULL')
421
            ->andWhere("tInner.alias = 'student'");
422
423
        $qb = $this->createDefaultQueryBuilder();
424
425
        $qb->where(
426
            $qb->expr()->in('u.id', $qbInner->getDQL())
427
        );
428
429
        return $qb->getQuery()->getResult();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $qb->getQuery()->getResult() could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
430
    }
431
432
    public function findGrades(): array {
433
        $result = $this->em->createQueryBuilder()
434
            ->select('DISTINCT u.grade')
435
            ->from(User::class, 'u')
436
            ->orderBy('u.grade', 'asc')
437
            ->where('u.grade IS NOT NULL')
438
            ->getQuery()
439
            ->getResult(Query::HYDRATE_ARRAY);
440
441
        return array_map(function($row) {
442
            return $row['grade'];
443
        }, $result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type integer; however, parameter $array of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

443
        }, /** @scrutinizer ignore-type */ $result);
Loading history...
444
    }
445
446
    /**
447
     * @inheritDoc
448
     */
449
    public function findStudentsByGrade(string $grade): array {
450
        return $this->createDefaultQueryBuilder()
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->createDefa...getQuery()->getResult() could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
451
            ->andWhere('u.grade = :grade')
452
            ->setParameter('grade', $grade)
453
            ->getQuery()
454
            ->getResult();
455
    }
456
457
    public function convertToActiveDirectory(User $user, ActiveDirectoryUser $activeDirectoryUser): ActiveDirectoryUser {
458
        $dbal = $this->em->getConnection();
459
        $dbal->update('user', [
460
            'user_principal_name' => $activeDirectoryUser->getUserPrincipalName(),
461
            'object_guid' => $activeDirectoryUser->getObjectGuid(),
462
            'ou' => $activeDirectoryUser->getOu(),
463
            'groups' => json_encode($activeDirectoryUser->getGroups()),
464
            'class' => 'ad'
465
        ], [
466
            'id' => $user->getId()
467
        ]);
468
469
        $this->em->detach($user);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\Persistence\ObjectManager::detach() has been deprecated: Detach operation is deprecated and will be removed in Persistence 2.0. Please use {@see ObjectManager::clear()} instead. ( Ignorable by Annotation )

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

469
        /** @scrutinizer ignore-deprecated */ $this->em->detach($user);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
470
        return $this->findOneById($user->getId());
0 ignored issues
show
Bug introduced by
It seems like $user->getId() can also be of type null; however, parameter $id of App\Repository\UserRepository::findOneById() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

470
        return $this->findOneById(/** @scrutinizer ignore-type */ $user->getId());
Loading history...
Bug Best Practice introduced by
The expression return $this->findOneById($user->getId()) returns the type null which is incompatible with the type-hinted return App\Entity\ActiveDirectoryUser.
Loading history...
471
    }
472
473
    public function convertToUser(ActiveDirectoryUser $user): User {
474
        $dbal = $this->em->getConnection();
475
        $dbal->update('user', [
476
            'user_principal_name' => null,
477
            'object_guid' => null,
478
            'ou' => null,
479
            'groups' => null,
480
            'class' => 'user'
481
        ], [
482
            'id' => $user->getId()
483
        ]);
484
485
        $this->em->detach($user);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\Persistence\ObjectManager::detach() has been deprecated: Detach operation is deprecated and will be removed in Persistence 2.0. Please use {@see ObjectManager::clear()} instead. ( Ignorable by Annotation )

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

485
        /** @scrutinizer ignore-deprecated */ $this->em->detach($user);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
486
        return $this->findOneById($user->getId());
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->findOneById($user->getId()) could return the type null which is incompatible with the type-hinted return App\Entity\User. Consider adding an additional type-check to rule them out.
Loading history...
Bug introduced by
It seems like $user->getId() can also be of type null; however, parameter $id of App\Repository\UserRepository::findOneById() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

486
        return $this->findOneById(/** @scrutinizer ignore-type */ $user->getId());
Loading history...
487
    }
488
489
490
}