From 1f7074756afe3905e8d813d722a1d45eca180f9c Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Wed, 3 Jul 2024 15:25:36 +0200 Subject: [PATCH 1/4] Preload users authorizations in users list --- src/Controller/UsersController.php | 2 +- src/Repository/UserRepository.php | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Controller/UsersController.php b/src/Controller/UsersController.php index b23219c2..16816abf 100644 --- a/src/Controller/UsersController.php +++ b/src/Controller/UsersController.php @@ -32,7 +32,7 @@ public function index(UserRepository $userRepository, UserSorter $userSorter): R { $this->denyAccessUnlessGranted('admin:manage:users'); - $users = $userRepository->findAll(); + $users = $userRepository->findAllWithAuthorizations(); $userSorter->sort($users); return $this->render('users/index.html.twig', [ diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 0acea159..ee6e2ab4 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -63,6 +63,22 @@ public function loadUserByEmail(string $email): ?User return $query->getOneOrNullResult(); } + /** + * @return User[] + */ + public function findAllWithAuthorizations(): array + { + $entityManager = $this->getEntityManager(); + + $query = $entityManager->createQuery(<<getResult(); + } + /** * @return User[] */ From 20f9de332aa3eb3cb28af9b5650382bac5e3b4fd Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Wed, 3 Jul 2024 16:21:50 +0200 Subject: [PATCH 2/4] Improve caching of user authorizations --- src/Repository/AuthorizationRepository.php | 102 ++++++++++----------- 1 file changed, 46 insertions(+), 56 deletions(-) diff --git a/src/Repository/AuthorizationRepository.php b/src/Repository/AuthorizationRepository.php index 7ebac976..71c6848f 100644 --- a/src/Repository/AuthorizationRepository.php +++ b/src/Repository/AuthorizationRepository.php @@ -14,7 +14,6 @@ use App\Entity\User; use App\Uid\UidGeneratorInterface; use App\Uid\UidGeneratorTrait; -use App\Utils\Time; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; @@ -105,22 +104,13 @@ public function grantTeamAuthorization(Team $team, TeamAuthorization $teamAuthor */ public function getAuthorizations(string $authorizationType, User $user, mixed $scope): array { - $cacheKey = self::getCacheKey($authorizationType, $user, $scope); - if (isset($this->cacheAuthorizations[$cacheKey])) { - return $this->cacheAuthorizations[$cacheKey]; - } - if ($authorizationType === 'orga' && $scope !== null) { - $authorizations = $this->getOrgaAuthorizationsFor($user, $scope); - $this->cacheAuthorizations[$cacheKey] = $authorizations; + return $this->getOrgaAuthorizationsFor($user, $scope); } elseif ($authorizationType === 'admin' && $scope === null) { - $authorizations = $this->getAdminAuthorizationsFor($user); - $this->cacheAuthorizations[$cacheKey] = $authorizations; + return $this->getAdminAuthorizationsFor($user); } else { throw new \DomainException('Given authorization type and scope are not supported together'); } - - return $authorizations; } /** @@ -128,18 +118,13 @@ public function getAuthorizations(string $authorizationType, User $user, mixed $ */ public function getAdminAuthorizationsFor(User $user): array { - $entityManager = $this->getEntityManager(); - - $query = $entityManager->createQuery(<<setParameter('user', $user); - - return $query->getResult(); + $authorizations = $this->loadUserAuthorizations($user); + + return array_filter($authorizations, function ($authorization): bool { + $role = $authorization->getRole(); + $roleType = $role->getType(); + return $roleType === 'admin' || $roleType === 'super'; + }); } /** @@ -148,44 +133,49 @@ public function getAdminAuthorizationsFor(User $user): array */ public function getOrgaAuthorizationsFor(User $user, mixed $scope): array { - $entityManager = $this->getEntityManager(); - - $queryBuilder = $entityManager->createQueryBuilder(); - $queryBuilder->select(['a', 'r']); - $queryBuilder->from('App\Entity\Authorization', 'a'); - $queryBuilder->join('a.role', 'r'); - $queryBuilder->where('a.holder = :user'); - $queryBuilder->andWhere("r.type = 'user' OR r.type = 'agent'"); - - $queryBuilder->setParameter('user', $user); - - if ($scope instanceof Organization) { - $queryBuilder->andWhere('a.organization = :organization OR a.organization IS NULL'); - $queryBuilder->setParameter('organization', $scope); - } - - $query = $queryBuilder->getQuery(); - return $query->getResult(); + $authorizations = $this->loadUserAuthorizations($user); + + return array_filter($authorizations, function ($authorization) use ($scope): bool { + $role = $authorization->getRole(); + $roleType = $role->getType(); + $correctType = $roleType === 'user' || $roleType === 'agent'; + + if ($scope instanceof Organization) { + $authOrganization = $authorization->getOrganization(); + $correctScope = ( + $authOrganization === null || + $authOrganization->getId() === $scope->getId() + ); + } else { + $correctScope = true; + } + + return $correctType && $correctScope; + }); } /** - * @param AuthorizationType $authorizationType - * @param ?Scope $scope + * @return Authorization[] */ - private static function getCacheKey(string $authorizationType, User $user, mixed $scope): string + public function loadUserAuthorizations(User $user): array { - $baseKey = "{$authorizationType}.{$user->getId()}"; - - if ($scope === 'any') { - $baseKey .= '.any'; - } elseif ($scope instanceof Organization) { - $baseKey .= ".{$scope->getId()}"; - } elseif ($scope === null) { - $baseKey .= '.null'; - } else { - throw new \DomainException('The given scope is not supported'); + $keyCache = $user->getUid(); + + if (!isset($this->cacheAuthorizations[$keyCache])) { + $entityManager = $this->getEntityManager(); + + $query = $entityManager->createQuery(<<setParameter('user', $user); + + $this->cacheAuthorizations[$keyCache] = $query->getResult(); } - return hash('sha256', $baseKey); + return $this->cacheAuthorizations[$keyCache]; } } From b01b0597ac962d8e3ee897f145e585d51a0f8d06 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Wed, 3 Jul 2024 16:22:12 +0200 Subject: [PATCH 3/4] Improve loading of authorized organizations --- src/Repository/OrganizationRepository.php | 25 +++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/Repository/OrganizationRepository.php b/src/Repository/OrganizationRepository.php index a0a528fd..2b1ccdb7 100644 --- a/src/Repository/OrganizationRepository.php +++ b/src/Repository/OrganizationRepository.php @@ -6,6 +6,7 @@ namespace App\Repository; +use App\Entity\Authorization; use App\Entity\Organization; use App\Entity\User; use App\Uid\UidGeneratorInterface; @@ -55,17 +56,19 @@ public function findLike(string $value): array public function findAuthorizedOrganizations(User $user): array { $entityManager = $this->getEntityManager(); - - $query = $entityManager->createQuery(<<setParameter('user', $user); - - $authorizedOrgaIds = $query->getSingleColumnResult(); + /** @var AuthorizationRepository */ + $authorizationRepository = $entityManager->getRepository(Authorization::class); + $authorizations = $authorizationRepository->getOrgaAuthorizationsFor($user, scope: 'any'); + + $authorizedOrgaIds = array_map(function ($authorization): ?int { + $organization = $authorization->getOrganization(); + + if ($organization) { + return $organization->getId(); + } else { + return null; + } + }, $authorizations); if (in_array(null, $authorizedOrgaIds)) { // If "null" is returned, it means that an authorization is applied globally. From 84009a11cd697d5d69f921d0de0291e3bcf0d1e7 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Wed, 3 Jul 2024 16:22:58 +0200 Subject: [PATCH 4/4] Improve loading of contracts We preload tickets and times spent as we always need them to calculate consumption or to display the number of tickets. --- src/Repository/ContractRepository.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Repository/ContractRepository.php b/src/Repository/ContractRepository.php index e9d942c3..74151644 100644 --- a/src/Repository/ContractRepository.php +++ b/src/Repository/ContractRepository.php @@ -34,8 +34,10 @@ public function findByOrganizationQuery(Organization $organization): ORM\Query $entityManager = $this->getEntityManager(); $query = $entityManager->createQuery(<<createQuery(<< (COALESCE(SUM(ts.time), 0) / 60.0) + AND c.maxHours > ( + SELECT (COALESCE(SUM(ts2.time), 0) / 60.0) + FROM App\Entity\TimeSpent ts2 + WHERE ts2.contract = c + ) ORDER BY c.endAt DESC, c.name SQL);