From 4fbccb237deef7283bd4a4bf9a34d357e983234e Mon Sep 17 00:00:00 2001 From: Baptiste Leduc Date: Mon, 21 Sep 2026 19:14:41 +0000 Subject: [PATCH] Migre l'association membre<->badge de Ting vers Doctrine --- phpstan-baseline.php | 142 +----------------- .../Association/Form/UserBadgeType.php | 6 +- .../UserMembership/BadgesComputer.php | 12 +- .../Admin/Members/UserBadgeDeleteAction.php | 19 ++- .../Admin/Members/UserBadgeNewAction.php | 19 ++- .../Admin/Members/UserEditAction.php | 2 +- .../AppBundle/Event/Entity/BadgeAttribue.php | 19 +++ .../Entity/Repository/UserBadgeRepository.php | 63 ++++++++ sources/AppBundle/Event/Entity/UserBadge.php | 24 +++ .../Model/Repository/UserBadgeRepository.php | 83 ---------- sources/AppBundle/Event/Model/UserBadge.php | 93 ------------ .../members/user_badge/user_badges.html.twig | 6 +- .../Repository/UserBadgeRepositoryTest.php | 109 ++++++++++++++ 13 files changed, 260 insertions(+), 337 deletions(-) create mode 100644 sources/AppBundle/Event/Entity/BadgeAttribue.php create mode 100644 sources/AppBundle/Event/Entity/Repository/UserBadgeRepository.php create mode 100644 sources/AppBundle/Event/Entity/UserBadge.php delete mode 100644 sources/AppBundle/Event/Model/Repository/UserBadgeRepository.php delete mode 100644 sources/AppBundle/Event/Model/UserBadge.php create mode 100644 tests/integration/AppBundle/Event/Entity/Repository/UserBadgeRepositoryTest.php diff --git a/phpstan-baseline.php b/phpstan-baseline.php index f85ead6d8..34d5ed068 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -757,22 +757,10 @@ 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Association/Form/TicketEventType.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#', - 'identifier' => 'foreach.nonIterable', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Association/Form/UserBadgeType.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot call method getBadge\\(\\) on mixed\\.$#', - 'identifier' => 'method.nonObject', - 'count' => 2, - 'path' => __DIR__ . '/sources/AppBundle/Association/Form/UserBadgeType.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getId\\(\\) on mixed\\.$#', 'identifier' => 'method.nonObject', - 'count' => 4, + 'count' => 2, 'path' => __DIR__ . '/sources/AppBundle/Association/Form/UserBadgeType.php', ]; $ignoreErrors[] = [ @@ -782,13 +770,13 @@ 'path' => __DIR__ . '/sources/AppBundle/Association/Form/UserBadgeType.php', ]; $ignoreErrors[] = [ - 'message' => '#^Possibly invalid array key type int\\|null\\.$#', - 'identifier' => 'offsetAccess.invalidOffset', + 'message' => '#^Parameter \\#1 \\$userId of method AppBundle\\\\Event\\\\Entity\\\\Repository\\\\UserBadgeRepository\\:\\:findByUserId\\(\\) expects int, mixed given\\.$#', + 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Association/Form/UserBadgeType.php', ]; $ignoreErrors[] = [ - 'message' => '#^Possibly invalid array key type mixed\\.$#', + 'message' => '#^Possibly invalid array key type int\\|null\\.$#', 'identifier' => 'offsetAccess.invalidOffset', 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Association/Form/UserBadgeType.php', @@ -1696,7 +1684,7 @@ $ignoreErrors[] = [ 'message' => '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#', 'identifier' => 'foreach.nonIterable', - 'count' => 3, + 'count' => 2, 'path' => __DIR__ . '/sources/AppBundle/Association/UserMembership/BadgesComputer.php', ]; $ignoreErrors[] = [ @@ -1750,12 +1738,6 @@ $ignoreErrors[] = [ 'message' => '#^Cannot call method format\\(\\) on mixed\\.$#', 'identifier' => 'method.nonObject', - 'count' => 3, - 'path' => __DIR__ . '/sources/AppBundle/Association/UserMembership/BadgesComputer.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot call method getBadge\\(\\) on mixed\\.$#', - 'identifier' => 'method.nonObject', 'count' => 2, 'path' => __DIR__ . '/sources/AppBundle/Association/UserMembership/BadgesComputer.php', ]; @@ -1765,24 +1747,6 @@ 'count' => 2, 'path' => __DIR__ . '/sources/AppBundle/Association/UserMembership/BadgesComputer.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot call method getId\\(\\) on mixed\\.$#', - 'identifier' => 'method.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Association/UserMembership/BadgesComputer.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot call method getIssuedAt\\(\\) on mixed\\.$#', - 'identifier' => 'method.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Association/UserMembership/BadgesComputer.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot call method getLabel\\(\\) on mixed\\.$#', - 'identifier' => 'method.nonObject', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Association/UserMembership/BadgesComputer.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot call method getPath\\(\\) on mixed\\.$#', 'identifier' => 'method.nonObject', @@ -3757,72 +3721,24 @@ 'count' => 2, 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Members/GeneralMeeting/ReportsAction.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$entity of method CCMBenchmark\\\\Ting\\\\Repository\\\\Repository\\\\:\\:delete\\(\\) expects AppBundle\\\\Event\\\\Model\\\\UserBadge, AppBundle\\\\Event\\\\Model\\\\UserBadge\\|null given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Members/UserBadgeDeleteAction.php', -]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$url of class Symfony\\\\Component\\\\HttpFoundation\\\\RedirectResponse constructor expects string, string\\|null given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Members/UserBadgeDeleteAction.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access offset \'badge\' on mixed\\.$#', - 'identifier' => 'offsetAccess.nonOffsetAccessible', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Members/UserBadgeNewAction.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access offset \'date\' on mixed\\.$#', - 'identifier' => 'offsetAccess.nonOffsetAccessible', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Members/UserBadgeNewAction.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Cannot access offset \'user\' on mixed\\.$#', - 'identifier' => 'offsetAccess.nonOffsetAccessible', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Members/UserBadgeNewAction.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$badgeId of method AppBundle\\\\Event\\\\Model\\\\UserBadge\\:\\:setBadgeId\\(\\) expects int, mixed given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Members/UserBadgeNewAction.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$issuedAt of method AppBundle\\\\Event\\\\Model\\\\UserBadge\\:\\:setIssuedAt\\(\\) expects DateTime\\|null, mixed given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Members/UserBadgeNewAction.php', -]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$url of class Symfony\\\\Component\\\\HttpFoundation\\\\RedirectResponse constructor expects string, string\\|null given\\.$#', 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Members/UserBadgeNewAction.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$userId of method AppBundle\\\\Event\\\\Model\\\\UserBadge\\:\\:setUserId\\(\\) expects int, mixed given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Members/UserBadgeNewAction.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset \'first\' on mixed\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Members/UserEditAction.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$iterator of function iterator_to_array expects iterable, mixed given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Members/UserEditAction.php', -]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#2 \\$plainPassword of method Symfony\\\\Component\\\\PasswordHasher\\\\Hasher\\\\UserPasswordHasherInterface\\:\\:hashPassword\\(\\) expects string, mixed given\\.$#', 'identifier' => 'argument.type', @@ -7213,54 +7129,6 @@ 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Event/Model/Repository/TicketTypeRepository.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method AppBundle\\\\Event\\\\Model\\\\Repository\\\\UserBadgeRepository\\:\\:findByUserId\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Event/Model/Repository/UserBadgeRepository.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method AppBundle\\\\Event\\\\Model\\\\Repository\\\\UserBadgeRepository\\:\\:findByUserId\\(\\) has parameter \\$userId with no type specified\\.$#', - 'identifier' => 'missingType.parameter', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Event/Model/Repository/UserBadgeRepository.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method AppBundle\\\\Event\\\\Model\\\\Repository\\\\UserBadgeRepository\\:\\:getHydratorForUserBadge\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Event/Model/Repository/UserBadgeRepository.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method AppBundle\\\\Event\\\\Model\\\\Repository\\\\UserBadgeRepository\\:\\:initMetadata\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#', - 'identifier' => 'missingType.iterableValue', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Event/Model/Repository/UserBadgeRepository.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method AppBundle\\\\Event\\\\Model\\\\Repository\\\\UserBadgeRepository\\:\\:initMetadata\\(\\) return type with generic class CCMBenchmark\\\\Ting\\\\Repository\\\\Metadata does not specify its types\\: T$#', - 'identifier' => 'missingType.generics', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Event/Model/Repository/UserBadgeRepository.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$databaseName of method CCMBenchmark\\\\Ting\\\\Repository\\\\Metadata\\\\:\\:setDatabase\\(\\) expects string, mixed given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Event/Model/Repository/UserBadgeRepository.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$hydrator of method CCMBenchmark\\\\Ting\\\\Repository\\\\Repository\\\\:\\:getCollection\\(\\) expects CCMBenchmark\\\\Ting\\\\Repository\\\\HydratorInterface\\\\|null, mixed given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Event/Model/Repository/UserBadgeRepository.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Unable to resolve the template type U in call to method CCMBenchmark\\\\Ting\\\\Repository\\\\Repository\\\\:\\:getCollection\\(\\)$#', - 'identifier' => 'argument.templateType', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Event/Model/Repository/UserBadgeRepository.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot cast mixed to int\\.$#', 'identifier' => 'cast.int', diff --git a/sources/AppBundle/Association/Form/UserBadgeType.php b/sources/AppBundle/Association/Form/UserBadgeType.php index ddaa366d8..0dd0ad76b 100644 --- a/sources/AppBundle/Association/Form/UserBadgeType.php +++ b/sources/AppBundle/Association/Form/UserBadgeType.php @@ -5,7 +5,7 @@ namespace AppBundle\Association\Form; use AppBundle\Event\Model\Repository\BadgeRepository; -use AppBundle\Event\Model\Repository\UserBadgeRepository; +use AppBundle\Event\Entity\Repository\UserBadgeRepository; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\DateType; @@ -24,8 +24,8 @@ public function __construct( public function buildForm(FormBuilderInterface $builder, array $options): void { $userBadgesIds = []; - foreach ($this->userBadgeRepository->findByUserId($options['user']->getId()) as $userBadge) { - $userBadgesIds[$userBadge->getBadge()->getId()] = $userBadge->getBadge()->getId(); + foreach ($this->userBadgeRepository->findByUserId($options['user']->getId()) as $badgeAttribue) { + $userBadgesIds[$badgeAttribue->badgeId] = $badgeAttribue->badgeId; } diff --git a/sources/AppBundle/Association/UserMembership/BadgesComputer.php b/sources/AppBundle/Association/UserMembership/BadgesComputer.php index aa32c731f..56eedfc74 100644 --- a/sources/AppBundle/Association/UserMembership/BadgesComputer.php +++ b/sources/AppBundle/Association/UserMembership/BadgesComputer.php @@ -9,7 +9,7 @@ use AppBundle\Association\Model\CompanyMember; use AppBundle\Association\Model\User; use AppBundle\Event\Model\Repository\EventRepository; -use AppBundle\Event\Model\Repository\UserBadgeRepository; +use AppBundle\Event\Entity\Repository\UserBadgeRepository; class BadgesComputer { @@ -38,13 +38,11 @@ private function getSpecificBadges(User $user): array { $specific = []; - $userBadges = $this->userBadgeRepository->findByUserId($user->getId()); - - foreach ($userBadges as $userBadge) { + foreach ($this->userBadgeRepository->findByUserId($user->getId()) as $badgeAttribue) { $specific[] = [ - 'date' => $userBadge->getIssuedAt()->format('Y-m-d'), - 'id' => $userBadge->getBadge()->getId(), - 'tooltip' => $userBadge->getBadge()->getLabel(), + 'date' => $badgeAttribue->issuedAt->format('Y-m-d'), + 'id' => $badgeAttribue->badgeId, + 'tooltip' => $badgeAttribue->badgeLabel, ]; } diff --git a/sources/AppBundle/Controller/Admin/Members/UserBadgeDeleteAction.php b/sources/AppBundle/Controller/Admin/Members/UserBadgeDeleteAction.php index e83e174e6..13fe0d1f2 100644 --- a/sources/AppBundle/Controller/Admin/Members/UserBadgeDeleteAction.php +++ b/sources/AppBundle/Controller/Admin/Members/UserBadgeDeleteAction.php @@ -4,7 +4,7 @@ namespace AppBundle\Controller\Admin\Members; -use AppBundle\Event\Model\Repository\UserBadgeRepository; +use AppBundle\Event\Entity\Repository\UserBadgeRepository; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; @@ -14,11 +14,20 @@ public function __construct(private readonly UserBadgeRepository $userBadgeRepos public function __invoke(Request $request): RedirectResponse { - $userBadge = $this->userBadgeRepository->getOneBy([ - 'badgeId' => $request->attributes->get('badgeId'), - 'userId' => $request->attributes->get('userId'), + $badgeId = $request->attributes->get('badgeId'); + $userId = $request->attributes->get('userId'); + + if (!is_numeric($badgeId) || !is_numeric($userId)) { + throw new \RuntimeException('Identifiants de badge invalides.'); + } + + $userBadge = $this->userBadgeRepository->find([ + 'badgeId' => (int) $badgeId, + 'userId' => (int) $userId, ]); - $this->userBadgeRepository->delete($userBadge); + if ($userBadge !== null) { + $this->userBadgeRepository->delete($userBadge); + } return new RedirectResponse($request->headers->get('referer')); } diff --git a/sources/AppBundle/Controller/Admin/Members/UserBadgeNewAction.php b/sources/AppBundle/Controller/Admin/Members/UserBadgeNewAction.php index 91c2cdabc..3fcd3c732 100644 --- a/sources/AppBundle/Controller/Admin/Members/UserBadgeNewAction.php +++ b/sources/AppBundle/Controller/Admin/Members/UserBadgeNewAction.php @@ -6,8 +6,8 @@ use AppBundle\Association\Form\UserBadgeType; use AppBundle\Association\Model\Repository\UserRepository; -use AppBundle\Event\Model\Repository\UserBadgeRepository; -use AppBundle\Event\Model\UserBadge; +use AppBundle\Event\Entity\Repository\UserBadgeRepository; +use AppBundle\Event\Entity\UserBadge; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; @@ -27,10 +27,19 @@ public function __invoke(Request $request): RedirectResponse $userBadgeForm->handleRequest($request); $data = $userBadgeForm->getData(); + if ( + !is_array($data) + || !is_numeric($data['badge'] ?? null) + || !is_numeric($data['user'] ?? null) + || !($data['date'] ?? null) instanceof \DateTimeInterface + ) { + throw new \RuntimeException('Données du formulaire d\'attribution de badge invalides.'); + } + $userBadge = new UserBadge(); - $userBadge->setBadgeId($data['badge']); - $userBadge->setIssuedAt($data['date']); - $userBadge->setUserId($data['user']); + $userBadge->badgeId = (int) $data['badge']; + $userBadge->issuedAt = \DateTimeImmutable::createFromInterface($data['date']); + $userBadge->userId = (int) $data['user']; $this->userBadgeRepository->save($userBadge); return new RedirectResponse($request->headers->get('referer')); diff --git a/sources/AppBundle/Controller/Admin/Members/UserEditAction.php b/sources/AppBundle/Controller/Admin/Members/UserEditAction.php index d6e5a5981..73a28cc87 100644 --- a/sources/AppBundle/Controller/Admin/Members/UserEditAction.php +++ b/sources/AppBundle/Controller/Admin/Members/UserEditAction.php @@ -8,7 +8,7 @@ use AppBundle\Association\Form\UserEditType; use AppBundle\Association\Model\Repository\UserRepository; use AppBundle\AuditLog\Audit; -use AppBundle\Event\Model\Repository\UserBadgeRepository; +use AppBundle\Event\Entity\Repository\UserBadgeRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/sources/AppBundle/Event/Entity/BadgeAttribue.php b/sources/AppBundle/Event/Entity/BadgeAttribue.php new file mode 100644 index 000000000..e617d3318 --- /dev/null +++ b/sources/AppBundle/Event/Entity/BadgeAttribue.php @@ -0,0 +1,19 @@ + + */ +final class UserBadgeRepository extends EntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, UserBadge::class); + } + + /** + * Retourne les badges attribués à un membre, triés par date d'attribution. + * + * La jointure avec afup_badge se fait en DBAL : cette table est encore + * portée par l'entité Ting AppBundle\Event\Model\Badge. + * + * @return list + */ + public function findByUserId(int $userId): array + { + $rows = $this->getEntityManager()->getConnection()->createQueryBuilder() + ->select('badge.id AS badge_id', 'badge.label AS badge_label', 'user_badge.issued_at AS issued_at') + ->from('afup_personnes_physiques_badge', 'user_badge') + ->innerJoin('user_badge', 'afup_badge', 'badge', 'user_badge.badge_id = badge.id') + ->where('user_badge.afup_personne_physique_id = :user_id') + ->setParameter('user_id', $userId) + ->orderBy('user_badge.issued_at') + ->executeQuery() + ->fetchAllAssociative() + ; + + $badgesAttribues = []; + foreach ($rows as $row) { + $badgeId = $row['badge_id']; + $badgeLabel = $row['badge_label']; + $issuedAt = $row['issued_at']; + + if (!is_int($badgeId) || !is_string($badgeLabel) || !is_string($issuedAt)) { + throw new \RuntimeException('Données inattendues dans la table afup_personnes_physiques_badge.'); + } + + $badgesAttribues[] = new BadgeAttribue( + $userId, + $badgeId, + $badgeLabel, + new \DateTimeImmutable($issuedAt), + ); + } + + return $badgesAttribues; + } +} diff --git a/sources/AppBundle/Event/Entity/UserBadge.php b/sources/AppBundle/Event/Entity/UserBadge.php new file mode 100644 index 000000000..e1b810e22 --- /dev/null +++ b/sources/AppBundle/Event/Entity/UserBadge.php @@ -0,0 +1,24 @@ + - */ -class UserBadgeRepository extends Repository implements MetadataInitializer -{ - public function findByUserId($userId) - { - $sql = 'SELECT afup_personnes_physiques_badge.*, - afup_badge.* - FROM afup_personnes_physiques_badge - JOIN afup_badge ON (afup_personnes_physiques_badge.badge_id = afup_badge.id) - WHERE afup_personnes_physiques_badge.afup_personne_physique_id = :user_id - ORDER BY afup_personnes_physiques_badge.issued_at'; - - return $this - ->getPreparedQuery($sql) - ->setParams(['user_id' => $userId]) - ->query($this->getCollection($this->getHydratorForUserBadge())) - ; - } - - private function getHydratorForUserBadge() - { - return new HydratorSingleObject() - ->mapObjectTo('afup_badge', 'afup_personnes_physiques_badge', 'setBadge') - ; - } - - - /** - * - * @return Metadata - * - * @throws Exception - */ - public static function initMetadata(SerializerFactoryInterface $serializerFactory, array $options = []) - { - $metadata = new Metadata($serializerFactory); - $metadata->setEntity(UserBadge::class); - $metadata->setConnectionName('main'); - $metadata->setDatabase($options['database']); - $metadata->setTable('afup_personnes_physiques_badge'); - - $metadata - ->addField([ - 'columnName' => 'afup_personne_physique_id', - 'fieldName' => 'userId', - 'primary' => true, - 'type' => 'int', - ]) - ->addField([ - 'columnName' => 'badge_id', - 'fieldName' => 'badgeId', - 'primary' => true, - 'type' => 'int', - ]) - ->addField([ - 'columnName' => 'issued_at', - 'fieldName' => 'issuedAt', - 'type' => 'datetime', - 'serializer_options' => [ - 'unserialize' => ['unSerializeUseFormat' => false], - ], - ]) - ; - - return $metadata; - } -} diff --git a/sources/AppBundle/Event/Model/UserBadge.php b/sources/AppBundle/Event/Model/UserBadge.php deleted file mode 100644 index 38b38dd56..000000000 --- a/sources/AppBundle/Event/Model/UserBadge.php +++ /dev/null @@ -1,93 +0,0 @@ -userId; - } - - /** - * @param int $userId - * - * @return $this - */ - public function setUserId($userId): self - { - $userId = (int) $userId; - $this->propertyChanged('userId', $this->userId, $userId); - $this->userId = $userId; - - return $this; - } - - /** - * @return int - */ - public function getBadgeId(): ?int - { - return $this->badgeId; - } - - /** - * @param int $badgeId - * - * @return $this - */ - public function setBadgeId($badgeId): self - { - $badgeId = (int) $badgeId; - $this->propertyChanged('badgeId', $this->badgeId, $badgeId); - $this->badgeId = $badgeId; - - return $this; - } - - public function getBadge(): ?Badge - { - return $this->badge; - } - - public function setBadge(?Badge $badge = null): self - { - $this->badge = $badge; - - return $this; - } - - public function getIssuedAt(): ?\DateTime - { - return $this->issuedAt; - } - - /** - * @return $this - */ - public function setIssuedAt(?\DateTime $issuedAt = null): self - { - $this->propertyChanged('issuedAt', $this->issuedAt, $issuedAt); - $this->issuedAt = $issuedAt; - - return $this; - } -} diff --git a/templates/admin/members/user_badge/user_badges.html.twig b/templates/admin/members/user_badge/user_badges.html.twig index 77497dfdb..f82bb3106 100644 --- a/templates/admin/members/user_badge/user_badges.html.twig +++ b/templates/admin/members/user_badge/user_badges.html.twig @@ -11,14 +11,14 @@ {% for user_badge in user_badges %} - {{ user_badge.badge.label }} + {{ user_badge.badgeLabel }} {{ user_badge.issuedAt|date('d/m/Y') }} diff --git a/tests/integration/AppBundle/Event/Entity/Repository/UserBadgeRepositoryTest.php b/tests/integration/AppBundle/Event/Entity/Repository/UserBadgeRepositoryTest.php new file mode 100644 index 000000000..937749dcc --- /dev/null +++ b/tests/integration/AppBundle/Event/Entity/Repository/UserBadgeRepositoryTest.php @@ -0,0 +1,109 @@ +get(UserBadgeRepository::class); + $connection = self::getContainer()->get(Connection::class); + + $this->insertBadge($connection, 1, 'Speaker 2025'); + $repository->save($this->buildUserBadge(101, 1, '2025-09-01')); + + $rows = $connection->fetchAllAssociative( + 'SELECT afup_personne_physique_id, badge_id, issued_at FROM afup_personnes_physiques_badge WHERE afup_personne_physique_id = 101', + ); + + self::assertCount(1, $rows); + self::assertSame('101', (string) $rows[0]['afup_personne_physique_id']); + self::assertSame('1', (string) $rows[0]['badge_id']); + self::assertSame('2025-09-01', (string) $rows[0]['issued_at']); + } + + public function testFindByUserIdReturnsBadgesWithLabelOrderedByDate(): void + { + $repository = self::getContainer()->get(UserBadgeRepository::class); + $connection = self::getContainer()->get(Connection::class); + + $this->insertBadge($connection, 1, 'Speaker 2025'); + $this->insertBadge($connection, 2, 'Benevole 2024'); + $this->insertUserBadge($connection, 101, 1, '2025-01-01'); + $this->insertUserBadge($connection, 101, 2, '2024-01-01'); + + $badges = $repository->findByUserId(101); + + // Tri par date d'attribution croissante + self::assertCount(2, $badges); + self::assertSame(101, $badges[0]->userId); + self::assertSame(2, $badges[0]->badgeId); + self::assertSame('Benevole 2024', $badges[0]->badgeLabel); + self::assertSame('2024-01-01', $badges[0]->issuedAt->format('Y-m-d')); + self::assertSame(1, $badges[1]->badgeId); + self::assertSame('Speaker 2025', $badges[1]->badgeLabel); + self::assertSame('2025-01-01', $badges[1]->issuedAt->format('Y-m-d')); + } + + public function testFindByUserIdReturnsEmptyListForUnknownUser(): void + { + $repository = self::getContainer()->get(UserBadgeRepository::class); + + self::assertSame([], $repository->findByUserId(9999)); + } + + public function testDeleteRemovesTheAssociation(): void + { + $repository = self::getContainer()->get(UserBadgeRepository::class); + $connection = self::getContainer()->get(Connection::class); + + $this->insertBadge($connection, 1, 'Speaker 2025'); + $this->insertUserBadge($connection, 101, 1, '2025-01-01'); + + $userBadge = $repository->find(['badgeId' => 1, 'userId' => 101]); + self::assertInstanceOf(UserBadge::class, $userBadge); + + $repository->delete($userBadge); + + $rows = $connection->fetchAllAssociative( + 'SELECT badge_id FROM afup_personnes_physiques_badge WHERE afup_personne_physique_id = 101', + ); + + self::assertSame([], $rows); + } + + private function buildUserBadge(int $userId, int $badgeId, string $issuedAt): UserBadge + { + $userBadge = new UserBadge(); + $userBadge->userId = $userId; + $userBadge->badgeId = $badgeId; + $userBadge->issuedAt = new \DateTimeImmutable($issuedAt); + + return $userBadge; + } + + private function insertBadge(Connection $connection, int $id, string $label): void + { + $connection->insert('afup_badge', [ + 'id' => $id, + 'label' => $label, + 'url' => 'https://afup.org/images/badges/test.png', + ]); + } + + private function insertUserBadge(Connection $connection, int $userId, int $badgeId, string $issuedAt): void + { + $connection->insert('afup_personnes_physiques_badge', [ + 'afup_personne_physique_id' => $userId, + 'badge_id' => $badgeId, + 'issued_at' => $issuedAt, + ]); + } +}