diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 37c40986d..a89f8fddf 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -752,21 +752,21 @@ 'path' => __DIR__ . '/sources/AppBundle/Association/Form/UserBadgeType.php', ]; $ignoreErrors[] = [ - 'message' => '#^Class AppBundle\\\\Association\\\\Form\\\\UserBadgeType extends generic class Symfony\\\\Component\\\\Form\\\\AbstractType but does not specify its types\\: TData$#', - 'identifier' => 'missingType.generics', + 'message' => '#^Cannot cast mixed to int\\.$#', + 'identifier' => 'cast.int', 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Association/Form/UserBadgeType.php', ]; $ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$userId of method AppBundle\\\\Event\\\\Entity\\\\Repository\\\\UserBadgeRepository\\:\\:findByUserId\\(\\) expects int, mixed given\\.$#', - 'identifier' => 'argument.type', + 'message' => '#^Class AppBundle\\\\Association\\\\Form\\\\UserBadgeType extends generic class Symfony\\\\Component\\\\Form\\\\AbstractType but does not specify its types\\: TData$#', + 'identifier' => 'missingType.generics', 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Association/Form/UserBadgeType.php', ]; $ignoreErrors[] = [ 'message' => '#^Possibly invalid array key type int\\|null\\.$#', 'identifier' => 'offsetAccess.invalidOffset', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/sources/AppBundle/Association/Form/UserBadgeType.php', ]; $ignoreErrors[] = [ @@ -3500,8 +3500,8 @@ 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Members/BadgeNewAction.php', ]; $ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$label of method AppBundle\\\\Event\\\\Model\\\\Badge\\:\\:setLabel\\(\\) expects string, mixed given\\.$#', - 'identifier' => 'argument.type', + 'message' => '#^Property AppBundle\\\\Event\\\\Entity\\\\Badge\\:\\:\\$label \\(string\\) does not accept mixed\\.$#', + 'identifier' => 'assign.propertyType', 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Members/BadgeNewAction.php', ]; @@ -3595,18 +3595,6 @@ 'count' => 2, 'path' => __DIR__ . '/sources/AppBundle/Controller/Admin/Members/GeneralMeeting/ReportsAction.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' => '#^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' => '#^Cannot access offset \'first\' on mixed\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', @@ -4507,6 +4495,7 @@ 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Controller/ValueResolver/AdminEventSelectionValueResolver.php', ]; + $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$code of method AppBundle\\\\Antennes\\\\AntenneRepository\\:\\:findByCode\\(\\) expects string, mixed given\\.$#', 'identifier' => 'argument.type', @@ -5821,24 +5810,6 @@ 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Event/Model/Planning.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method AppBundle\\\\Event\\\\Model\\\\Repository\\\\BadgeRepository\\:\\: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/BadgeRepository.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method AppBundle\\\\Event\\\\Model\\\\Repository\\\\BadgeRepository\\:\\: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/BadgeRepository.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/BadgeRepository.php', -]; $ignoreErrors[] = [ 'message' => '#^Argument of an invalid type CCMBenchmark\\\\Ting\\\\Repository\\\\CollectionInterface\\\\|null supplied for foreach, only iterables are supported\\.$#', 'identifier' => 'foreach.nonIterable', diff --git a/sources/AppBundle/Association/Form/UserBadgeType.php b/sources/AppBundle/Association/Form/UserBadgeType.php index 0dd0ad76b..3f27c0bd1 100644 --- a/sources/AppBundle/Association/Form/UserBadgeType.php +++ b/sources/AppBundle/Association/Form/UserBadgeType.php @@ -4,7 +4,7 @@ namespace AppBundle\Association\Form; -use AppBundle\Event\Model\Repository\BadgeRepository; +use AppBundle\Event\Entity\Repository\BadgeRepository; use AppBundle\Event\Entity\Repository\UserBadgeRepository; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; @@ -24,17 +24,17 @@ public function __construct( public function buildForm(FormBuilderInterface $builder, array $options): void { $userBadgesIds = []; - foreach ($this->userBadgeRepository->findByUserId($options['user']->getId()) as $badgeAttribue) { - $userBadgesIds[$badgeAttribue->badgeId] = $badgeAttribue->badgeId; + foreach ($this->userBadgeRepository->findByUserId((int) $options['user']->getId()) as $userBadge) { + $userBadgesIds[$userBadge->badge->id] = $userBadge->badge->id; } $badges = []; - foreach ($this->badgeRepository->getAll() as $badge) { - if (isset($userBadgesIds[$badge->getId()])) { + foreach ($this->badgeRepository->findAll() as $badge) { + if (isset($userBadgesIds[$badge->id])) { continue; } - $badges[$badge->getLabel()] = $badge->getId(); + $badges[$badge->label] = $badge->id; } $builder @@ -51,6 +51,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void DateType::class, [ 'label' => 'Date', + 'input' => 'datetime_immutable', ], ) ->add( diff --git a/sources/AppBundle/Association/Model/Repository/UserRepository.php b/sources/AppBundle/Association/Model/Repository/UserRepository.php index 02feab893..ca063f525 100644 --- a/sources/AppBundle/Association/Model/Repository/UserRepository.php +++ b/sources/AppBundle/Association/Model/Repository/UserRepository.php @@ -8,7 +8,7 @@ use AppBundle\Association\MemberType; use AppBundle\Association\Model\CompanyMember; use AppBundle\Association\Model\User; -use AppBundle\Event\Model\Badge; +use AppBundle\Event\Entity\Badge; use Aura\SqlQuery\Common\SelectInterface; use CCMBenchmark\Ting\Driver\Mysqli\Serializer\Boolean; use CCMBenchmark\Ting\Repository\CollectionInterface; @@ -154,7 +154,7 @@ public function loadByBadge(Badge $badge) return $this ->getPreparedQuery($queryBuilder->getStatement()) - ->setParams(['badge_id' => $badge->getId()]) + ->setParams(['badge_id' => $badge->id]) ->query($this->getCollection($this->getHydratorForUser())) ; } diff --git a/sources/AppBundle/Association/UserMembership/BadgesComputer.php b/sources/AppBundle/Association/UserMembership/BadgesComputer.php index 56eedfc74..a358641de 100644 --- a/sources/AppBundle/Association/UserMembership/BadgesComputer.php +++ b/sources/AppBundle/Association/UserMembership/BadgesComputer.php @@ -38,11 +38,13 @@ private function getSpecificBadges(User $user): array { $specific = []; - foreach ($this->userBadgeRepository->findByUserId($user->getId()) as $badgeAttribue) { + $userBadges = $this->userBadgeRepository->findByUserId((int) $user->getId()); + + foreach ($userBadges as $userBadge) { $specific[] = [ - 'date' => $badgeAttribue->issuedAt->format('Y-m-d'), - 'id' => $badgeAttribue->badgeId, - 'tooltip' => $badgeAttribue->badgeLabel, + 'date' => $userBadge->issuedAt->format('Y-m-d'), + 'id' => $userBadge->badge->id, + 'tooltip' => $userBadge->badge->label, ]; } diff --git a/sources/AppBundle/Controller/Admin/Members/BadgeListAction.php b/sources/AppBundle/Controller/Admin/Members/BadgeListAction.php index 0762585ac..fb93c22a5 100644 --- a/sources/AppBundle/Controller/Admin/Members/BadgeListAction.php +++ b/sources/AppBundle/Controller/Admin/Members/BadgeListAction.php @@ -5,7 +5,7 @@ namespace AppBundle\Controller\Admin\Members; use AppBundle\Association\Model\Repository\UserRepository; -use AppBundle\Event\Model\Repository\BadgeRepository; +use AppBundle\Event\Entity\Repository\BadgeRepository; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Twig\Environment; @@ -21,7 +21,7 @@ public function __construct( public function __invoke(Request $request): Response { $infos = []; - foreach ($this->badgeRepository->getAll() as $badge) { + foreach ($this->badgeRepository->findAll() as $badge) { $infos[] = [ 'badge' => $badge, 'users' => $this->userRepository->loadByBadge($badge), diff --git a/sources/AppBundle/Controller/Admin/Members/BadgeNewAction.php b/sources/AppBundle/Controller/Admin/Members/BadgeNewAction.php index 0911a770a..f85101093 100644 --- a/sources/AppBundle/Controller/Admin/Members/BadgeNewAction.php +++ b/sources/AppBundle/Controller/Admin/Members/BadgeNewAction.php @@ -5,8 +5,8 @@ namespace AppBundle\Controller\Admin\Members; use AppBundle\Association\Form\BadgeType; -use AppBundle\Event\Model\Badge; -use AppBundle\Event\Model\Repository\BadgeRepository; +use AppBundle\Event\Entity\Badge; +use AppBundle\Event\Entity\Repository\BadgeRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Filesystem\Filesystem; @@ -33,8 +33,8 @@ public function __invoke(Request $request): Response $data['image']->move($this->storageDir, $filename); $badge = new Badge(); - $badge->setLabel($data['label']); - $badge->setUrl($filename); + $badge->label = $data['label']; + $badge->url = $filename; $this->badgeRepository->save($badge); $this->addFlash('notice', 'Le badge a été ajouté'); diff --git a/sources/AppBundle/Controller/Admin/Members/UserBadgeDeleteAction.php b/sources/AppBundle/Controller/Admin/Members/UserBadgeDeleteAction.php index 13fe0d1f2..ea7ae971d 100644 --- a/sources/AppBundle/Controller/Admin/Members/UserBadgeDeleteAction.php +++ b/sources/AppBundle/Controller/Admin/Members/UserBadgeDeleteAction.php @@ -4,31 +4,38 @@ namespace AppBundle\Controller\Admin\Members; +use AppBundle\Event\Entity\Badge; +use AppBundle\Event\Entity\Repository\BadgeRepository; use AppBundle\Event\Entity\Repository\UserBadgeRepository; +use AppBundle\Event\Entity\UserBadge; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class UserBadgeDeleteAction { - public function __construct(private readonly UserBadgeRepository $userBadgeRepository) {} + public function __construct( + private readonly BadgeRepository $badgeRepository, + private readonly UserBadgeRepository $userBadgeRepository, + ) {} public function __invoke(Request $request): RedirectResponse { - $badgeId = $request->attributes->get('badgeId'); - $userId = $request->attributes->get('userId'); - - if (!is_numeric($badgeId) || !is_numeric($userId)) { - throw new \RuntimeException('Identifiants de badge invalides.'); + $badgeId = $request->attributes->getInt('badgeId'); + $badge = $this->badgeRepository->find($badgeId); + if (! $badge instanceof Badge) { + throw new NotFoundHttpException(sprintf('Badge %d inexistant', $badgeId)); } - $userBadge = $this->userBadgeRepository->find([ - 'badgeId' => (int) $badgeId, - 'userId' => (int) $userId, + $userBadge = $this->userBadgeRepository->findOneBy([ + 'userId' => $request->attributes->getInt('userId'), + 'badge' => $badge, ]); - if ($userBadge !== null) { - $this->userBadgeRepository->delete($userBadge); + if (! $userBadge instanceof UserBadge) { + throw new NotFoundHttpException('Attribution de badge introuvable'); } + $this->userBadgeRepository->delete($userBadge); - return new RedirectResponse($request->headers->get('referer')); + return new RedirectResponse($request->headers->get('referer') ?? $request->getRequestUri()); } } diff --git a/sources/AppBundle/Controller/Admin/Members/UserBadgeNewAction.php b/sources/AppBundle/Controller/Admin/Members/UserBadgeNewAction.php index 3fcd3c732..c9ebb9834 100644 --- a/sources/AppBundle/Controller/Admin/Members/UserBadgeNewAction.php +++ b/sources/AppBundle/Controller/Admin/Members/UserBadgeNewAction.php @@ -6,17 +6,20 @@ use AppBundle\Association\Form\UserBadgeType; use AppBundle\Association\Model\Repository\UserRepository; +use AppBundle\Event\Entity\Repository\BadgeRepository; 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; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class UserBadgeNewAction { public function __construct( private readonly UserRepository $userRepository, private readonly FormFactoryInterface $formFactory, + private readonly BadgeRepository $badgeRepository, private readonly UserBadgeRepository $userBadgeRepository, ) {} @@ -25,23 +28,25 @@ public function __invoke(Request $request): RedirectResponse $user = $this->userRepository->get($request->query->get('user_id')); $userBadgeForm = $this->formFactory->create(UserBadgeType::class, [], ['user' => $user]); $userBadgeForm->handleRequest($request); + if (!$userBadgeForm->isSubmitted() || !$userBadgeForm->isValid()) { + return new RedirectResponse($request->headers->get('referer') ?? $request->getRequestUri()); + } + + /** @var array{badge: int, date: \DateTimeInterface, user: string} $data */ $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.'); + $badgeId = (int) $data['badge']; + $badge = $this->badgeRepository->find($badgeId); + if (null === $badge) { + throw new NotFoundHttpException(sprintf('Badge %d inexistant', $badgeId)); } $userBadge = new UserBadge(); - $userBadge->badgeId = (int) $data['badge']; - $userBadge->issuedAt = \DateTimeImmutable::createFromInterface($data['date']); + $userBadge->badge = $badge; + $userBadge->issuedAt = $data['date']; $userBadge->userId = (int) $data['user']; $this->userBadgeRepository->save($userBadge); - return new RedirectResponse($request->headers->get('referer')); + return new RedirectResponse($request->headers->get('referer') ?? $request->getRequestUri()); } } diff --git a/sources/AppBundle/Controller/Admin/Members/UserEditAction.php b/sources/AppBundle/Controller/Admin/Members/UserEditAction.php index 73a28cc87..a4e8f15dd 100644 --- a/sources/AppBundle/Controller/Admin/Members/UserEditAction.php +++ b/sources/AppBundle/Controller/Admin/Members/UserEditAction.php @@ -49,7 +49,7 @@ public function __invoke(Request $request): Response ]); } - $userBadges = iterator_to_array($this->userBadgeRepository->findByUserId($user->getId())); + $userBadges = $this->userBadgeRepository->findByUserId((int) $user->getId()); $userBadgeForm = $this->createForm(UserBadgeType::class, [], [ 'user' => $user, 'action' => $this->generateUrl('admin_members_user_badge_new', [ diff --git a/sources/AppBundle/Controller/Website/Badge/ImageAction.php b/sources/AppBundle/Controller/Website/Badge/ImageAction.php index 03cf3ade0..bb7b7eccb 100644 --- a/sources/AppBundle/Controller/Website/Badge/ImageAction.php +++ b/sources/AppBundle/Controller/Website/Badge/ImageAction.php @@ -4,7 +4,7 @@ namespace AppBundle\Controller\Website\Badge; -use AppBundle\Event\Model\Repository\BadgeRepository; +use AppBundle\Event\Entity\Repository\BadgeRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\HttpFoundation\BinaryFileResponse; @@ -20,12 +20,12 @@ public function __construct( public function __invoke(int $id): Response { - $badge = $this->badgeRepository->get($id); + $badge = $this->badgeRepository->find($id); if (null === $badge) { throw $this->createNotFoundException(); } - $filepath = $this->storageDir . DIRECTORY_SEPARATOR . $badge->getUrl(); + $filepath = $this->storageDir . DIRECTORY_SEPARATOR . $badge->url; if (false === is_file($filepath)) { throw $this->createNotFoundException(); } diff --git a/sources/AppBundle/Event/Entity/Badge.php b/sources/AppBundle/Event/Entity/Badge.php new file mode 100644 index 000000000..e6f1d22e4 --- /dev/null +++ b/sources/AppBundle/Event/Entity/Badge.php @@ -0,0 +1,24 @@ + + */ +final class BadgeRepository extends EntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Badge::class); + } +} diff --git a/sources/AppBundle/Event/Entity/Repository/UserBadgeRepository.php b/sources/AppBundle/Event/Entity/Repository/UserBadgeRepository.php index 12f6a0c8e..dc87b7ade 100644 --- a/sources/AppBundle/Event/Entity/Repository/UserBadgeRepository.php +++ b/sources/AppBundle/Event/Entity/Repository/UserBadgeRepository.php @@ -5,7 +5,6 @@ namespace AppBundle\Event\Entity\Repository; use AppBundle\Doctrine\EntityRepository; -use AppBundle\Event\Entity\BadgeAttribue; use AppBundle\Event\Entity\UserBadge; use Doctrine\Persistence\ManagerRegistry; @@ -20,44 +19,20 @@ public function __construct(ManagerRegistry $registry) } /** - * 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 + * @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() + return $this->getEntityManager() + ->createQueryBuilder() + ->select('userBadge', 'badge') + ->from(UserBadge::class, 'userBadge') + ->innerJoin('userBadge.badge', 'badge') + ->where('userBadge.userId = :userId') + ->setParameter('userId', $userId) + ->orderBy('userBadge.issuedAt', 'ASC') + ->getQuery() + ->getResult() ; - - $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 index e1b810e22..dc855c547 100644 --- a/sources/AppBundle/Event/Entity/UserBadge.php +++ b/sources/AppBundle/Event/Entity/UserBadge.php @@ -16,9 +16,12 @@ class UserBadge public int $userId; #[ORM\Id] - #[ORM\Column(name: 'badge_id')] - public int $badgeId; + #[ORM\ManyToOne(targetEntity: Badge::class)] + #[ORM\JoinColumn(name: 'badge_id', nullable: false)] + public Badge $badge; - #[ORM\Column(name: 'issued_at', type: 'date_immutable')] - public \DateTimeImmutable $issuedAt; + #[ORM\Column(name: 'issued_at', type: 'date_immutable', nullable: false)] + // date_immutable column hydrates DateTimeImmutable; property stays + // \DateTimeInterface by choice (DBAL rejects mutable writes anyway). + public \DateTimeInterface $issuedAt; // @phpstan-ignore doctrine.columnType } diff --git a/sources/AppBundle/Event/Model/Badge.php b/sources/AppBundle/Event/Model/Badge.php deleted file mode 100644 index 7767a6607..000000000 --- a/sources/AppBundle/Event/Model/Badge.php +++ /dev/null @@ -1,58 +0,0 @@ -id; - } - - public function setId(int $id): self - { - $this->propertyChanged('id', $this->id, $id); - $this->id = $id; - - return $this; - } - - public function getLabel(): string - { - return $this->label; - } - - public function setLabel(string $label): self - { - $this->propertyChanged('label', $this->label ?? '', $label); - $this->label = $label; - - return $this; - } - - public function getUrl(): string - { - return $this->url; - } - - public function setUrl(string $url): self - { - $this->propertyChanged('url', $this->url ?? '', $url); - $this->url = $url; - - return $this; - } -} diff --git a/sources/AppBundle/Event/Model/Repository/BadgeRepository.php b/sources/AppBundle/Event/Model/Repository/BadgeRepository.php deleted file mode 100644 index 3a0aa9afb..000000000 --- a/sources/AppBundle/Event/Model/Repository/BadgeRepository.php +++ /dev/null @@ -1,55 +0,0 @@ - - */ -class BadgeRepository extends Repository implements MetadataInitializer -{ - /** - * - * @return Metadata - * - * @throws Exception - */ - public static function initMetadata(SerializerFactoryInterface $serializerFactory, array $options = []) - { - $metadata = new Metadata($serializerFactory); - $metadata->setEntity(Badge::class); - $metadata->setConnectionName('main'); - $metadata->setDatabase($options['database']); - $metadata->setTable('afup_badge'); - - $metadata - ->addField([ - 'columnName' => 'id', - 'fieldName' => 'id', - 'primary' => true, - 'autoincrement' => true, - 'type' => 'int', - ]) - ->addField([ - 'columnName' => 'label', - 'fieldName' => 'label', - 'type' => 'string', - ]) - ->addField([ - 'columnName' => 'url', - 'fieldName' => 'url', - 'type' => 'string', - ]) - ; - - return $metadata; - } -} diff --git a/templates/admin/members/user_badge/user_badges.html.twig b/templates/admin/members/user_badge/user_badges.html.twig index f82bb3106..3e152b257 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.badgeLabel }} + {{ user_badge.badge.label }} {{ 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 index 937749dcc..003ec5dda 100644 --- a/tests/integration/AppBundle/Event/Entity/Repository/UserBadgeRepositoryTest.php +++ b/tests/integration/AppBundle/Event/Entity/Repository/UserBadgeRepositoryTest.php @@ -5,88 +5,83 @@ namespace AppBundle\IntegrationTests\Event\Entity\Repository; use Afup\Tests\Support\IntegrationTestCase; +use AppBundle\Event\Entity\Badge; +use AppBundle\Event\Entity\Repository\BadgeRepository; use AppBundle\Event\Entity\Repository\UserBadgeRepository; use AppBundle\Event\Entity\UserBadge; use Doctrine\DBAL\Connection; final class UserBadgeRepositoryTest extends IntegrationTestCase { - public function testSavePersistsTheAssociation(): void + public function testFindByUserIdReturnsBadgesJoinedAndSortedByIssueDate(): void { - $repository = self::getContainer()->get(UserBadgeRepository::class); + $userBadgeRepository = self::getContainer()->get(UserBadgeRepository::class); $connection = self::getContainer()->get(Connection::class); $this->insertBadge($connection, 1, 'Speaker 2025'); - $repository->save($this->buildUserBadge(101, 1, '2025-09-01')); + $this->insertBadge($connection, 2, 'Membre depuis 10 ans'); + $this->insertUserBadge($connection, 42, 2, '2025-01-01'); + $this->insertUserBadge($connection, 42, 1, '2026-06-15'); + $this->insertUserBadge($connection, 43, 1, '2026-01-01'); - $rows = $connection->fetchAllAssociative( - 'SELECT afup_personne_physique_id, badge_id, issued_at FROM afup_personnes_physiques_badge WHERE afup_personne_physique_id = 101', - ); + $userBadges = $userBadgeRepository->findByUserId(42); - 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']); + self::assertCount(2, $userBadges); + // Tri par date d'attribution croissante + self::assertSame(2, $userBadges[0]->badge->id); + self::assertSame('Membre depuis 10 ans', $userBadges[0]->badge->label); + self::assertSame('2025-01-01', $userBadges[0]->issuedAt->format('Y-m-d')); + self::assertSame(1, $userBadges[1]->badge->id); + self::assertSame(42, $userBadges[0]->userId); } - public function testFindByUserIdReturnsBadgesWithLabelOrderedByDate(): void + public function testFindByUserIdReturnsEmptyArrayForUserWithoutBadge(): void { - $repository = self::getContainer()->get(UserBadgeRepository::class); + $userBadgeRepository = 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); + $this->insertUserBadge($connection, 42, 1, '2025-01-01'); - self::assertSame([], $repository->findByUserId(9999)); + self::assertSame([], $userBadgeRepository->findByUserId(999)); } - public function testDeleteRemovesTheAssociation(): void + public function testSaveAndDeleteThroughTheEntityManager(): void { - $repository = self::getContainer()->get(UserBadgeRepository::class); + $userBadgeRepository = self::getContainer()->get(UserBadgeRepository::class); + $badgeRepository = self::getContainer()->get(BadgeRepository::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); + $badge = new Badge(); + $badge->label = 'Speaker 2026'; + $badge->url = 'badge_speaker_2026.png'; + $badgeRepository->save($badge); - $repository->delete($userBadge); + $userBadge = new UserBadge(); + $userBadge->badge = $badge; + $userBadge->userId = 42; + $userBadge->issuedAt = new \DateTimeImmutable('2026-09-21'); + $userBadgeRepository->save($userBadge); $rows = $connection->fetchAllAssociative( - 'SELECT badge_id FROM afup_personnes_physiques_badge WHERE afup_personne_physique_id = 101', + 'SELECT * FROM afup_personnes_physiques_badge WHERE afup_personne_physique_id = :userId', + ['userId' => 42], ); + self::assertCount(1, $rows); + self::assertSame($badge->id, (int) $rows[0]['badge_id']); + self::assertSame('2026-09-21', $rows[0]['issued_at']); - 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); + $fromDatabase = $userBadgeRepository->findOneBy([ + 'userId' => 42, + 'badge' => $badge, + ]); + self::assertNotNull($fromDatabase); - return $userBadge; + $userBadgeRepository->delete($fromDatabase); + self::assertSame( + [], + $connection->fetchAllAssociative('SELECT * FROM afup_personnes_physiques_badge WHERE afup_personne_physique_id = :userId', ['userId' => 42]), + ); } private function insertBadge(Connection $connection, int $id, string $label): void @@ -94,7 +89,7 @@ private function insertBadge(Connection $connection, int $id, string $label): vo $connection->insert('afup_badge', [ 'id' => $id, 'label' => $label, - 'url' => 'https://afup.org/images/badges/test.png', + 'url' => 'badge_' . $id . '.png', ]); }