From 50df2b1c17edc7174bfa7145f00b78e161fc4f36 Mon Sep 17 00:00:00 2001 From: Baptiste Leduc Date: Mon, 21 Sep 2026 18:45:44 +0000 Subject: [PATCH] =?UTF-8?q?Migre=20l'entit=C3=A9=20TalkInvitation=20de=20T?= =?UTF-8?q?ing=20vers=20Doctrine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpstan-baseline.php | 48 ----- .../Controller/Event/CFP/EditAction.php | 18 +- .../Controller/Event/CFP/InviteAction.php | 10 +- .../Repository/TalkInvitationRepository.php | 37 ++++ .../AppBundle/Event/Entity/TalkInvitation.php | 37 ++++ .../Event/Enum/TalkInvitationState.php | 11 ++ .../Event/Form/TalkInvitationType.php | 12 ++ .../Repository/TalkInvitationRepository.php | 88 --------- .../AppBundle/Event/Model/TalkInvitation.php | 183 ------------------ .../Event/Talk/InvitationFormHandler.php | 12 +- templates/event/cfp/edit.html.twig | 4 +- .../TalkInvitationRepositoryTest.php | 71 +++++++ 12 files changed, 190 insertions(+), 341 deletions(-) create mode 100644 sources/AppBundle/Event/Entity/Repository/TalkInvitationRepository.php create mode 100644 sources/AppBundle/Event/Entity/TalkInvitation.php create mode 100644 sources/AppBundle/Event/Enum/TalkInvitationState.php delete mode 100644 sources/AppBundle/Event/Model/Repository/TalkInvitationRepository.php delete mode 100644 sources/AppBundle/Event/Model/TalkInvitation.php create mode 100644 tests/integration/AppBundle/Event/Entity/Repository/TalkInvitationRepositoryTest.php diff --git a/phpstan-baseline.php b/phpstan-baseline.php index f85ead6d8..21f1d8660 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -4291,18 +4291,6 @@ 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Controller/Event/CFP/EditAction.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$submittedBy of method AppBundle\\\\Event\\\\Model\\\\TalkInvitation\\:\\:setSubmittedBy\\(\\) expects int, int\\|null given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Controller/Event/CFP/EditAction.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Parameter \\#1 \\$talkId of method AppBundle\\\\Event\\\\Model\\\\TalkInvitation\\:\\:setTalkId\\(\\) expects int, int\\|null given\\.$#', - 'identifier' => 'argument.type', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Controller/Event/CFP/EditAction.php', -]; $ignoreErrors[] = [ 'message' => '#^Parameter \\#1 \\$eventSlug of method AppBundle\\\\Controller\\\\Event\\\\EventActionHelper\\:\\:getEvent\\(\\) expects string, mixed given\\.$#', 'identifier' => 'argument.type', @@ -5473,12 +5461,6 @@ 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Event/Form/TalkAdminType.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Class AppBundle\\\\Event\\\\Form\\\\TalkInvitationType extends generic class Symfony\\\\Component\\\\Form\\\\AbstractType but does not specify its types\\: TData$#', - 'identifier' => 'missingType.generics', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Event/Form/TalkInvitationType.php', -]; $ignoreErrors[] = [ 'message' => '#^Class AppBundle\\\\Event\\\\Form\\\\TalkType extends generic class Symfony\\\\Component\\\\Form\\\\AbstractType but does not specify its types\\: TData$#', 'identifier' => 'missingType.generics', @@ -6697,36 +6679,6 @@ 'count' => 1, 'path' => __DIR__ . '/sources/AppBundle/Event/Model/Repository/SponsorTicketRepository.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method AppBundle\\\\Event\\\\Model\\\\Repository\\\\TalkInvitationRepository\\:\\:getPendingInvitationsByTalkId\\(\\) has parameter \\$talkId with no type specified\\.$#', - 'identifier' => 'missingType.parameter', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Event/Model/Repository/TalkInvitationRepository.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method AppBundle\\\\Event\\\\Model\\\\Repository\\\\TalkInvitationRepository\\:\\:getPendingInvitationsByTalkId\\(\\) return type with generic interface CCMBenchmark\\\\Ting\\\\Repository\\\\CollectionInterface does not specify its types\\: T$#', - 'identifier' => 'missingType.generics', - 'count' => 1, - 'path' => __DIR__ . '/sources/AppBundle/Event/Model/Repository/TalkInvitationRepository.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method AppBundle\\\\Event\\\\Model\\\\Repository\\\\TalkInvitationRepository\\:\\: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/TalkInvitationRepository.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method AppBundle\\\\Event\\\\Model\\\\Repository\\\\TalkInvitationRepository\\:\\: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/TalkInvitationRepository.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/TalkInvitationRepository.php', -]; $ignoreErrors[] = [ 'message' => '#^Cannot access offset \'\\.aggregation\' on mixed\\.$#', 'identifier' => 'offsetAccess.nonOffsetAccessible', diff --git a/sources/AppBundle/Controller/Event/CFP/EditAction.php b/sources/AppBundle/Controller/Event/CFP/EditAction.php index c7cd5df2c..3e5182d11 100644 --- a/sources/AppBundle/Controller/Event/CFP/EditAction.php +++ b/sources/AppBundle/Controller/Event/CFP/EditAction.php @@ -8,13 +8,14 @@ use AppBundle\Controller\Event\EventActionHelper; use AppBundle\Event\Form\TalkInvitationType; use AppBundle\Event\Form\TalkType; +use AppBundle\Event\Entity\TalkInvitation; +use AppBundle\Event\Enum\TalkInvitationState; use AppBundle\Event\Model\GithubUser; use AppBundle\Event\Model\Repository\SpeakerRepository; -use AppBundle\Event\Model\Repository\TalkInvitationRepository; use AppBundle\Event\Model\Repository\TalkRepository; use AppBundle\Event\Model\Repository\VoteRepository; use AppBundle\Event\Model\Talk; -use AppBundle\Event\Model\TalkInvitation; +use AppBundle\Event\Entity\Repository\TalkInvitationRepository; use AppBundle\Event\Talk\InvitationFormHandler; use AppBundle\Event\Talk\TalkFormHandler; use AppBundle\Security\Authentication; @@ -95,7 +96,7 @@ public function __invoke(Request $request): RedirectResponse|Response 'event' => $event, 'form' => $talkForm->createView(), 'talk' => $talk, - 'invitations' => $this->talkInvitationRepository->getPendingInvitationsByTalkId($talk->getId()), + 'invitations' => $this->talkInvitationRepository->getPendingInvitationsByTalkId($talkId), 'speakers' => $this->speakerRepository->getSpeakersByTalk($talk), 'invitationForm' => $invitationForm->createView(), 'votes' => $this->voteRepository->getVotesByTalkWithUser($talk->getId()), @@ -106,12 +107,11 @@ public function __invoke(Request $request): RedirectResponse|Response private function createInvitationForm(GithubUser $user, Talk $talk): FormInterface { $invitation = new TalkInvitation(); - $invitation - ->setSubmittedBy($user->getId()) - ->setSubmittedOn(new DateTime()) - ->setToken(base64_encode(random_bytes(30))) - ->setState(TalkInvitation::STATE_PENDING) - ->setTalkId($talk->getId()); + $invitation->submittedBy = (int) $user->getId(); + $invitation->submittedOn = new DateTime(); + $invitation->token = base64_encode(random_bytes(30)); + $invitation->state = TalkInvitationState::Pending; + $invitation->talkId = (int) $talk->getId(); return $this->createForm(TalkInvitationType::class, $invitation); } diff --git a/sources/AppBundle/Controller/Event/CFP/InviteAction.php b/sources/AppBundle/Controller/Event/CFP/InviteAction.php index 844c56f91..3b1ca1ba4 100644 --- a/sources/AppBundle/Controller/Event/CFP/InviteAction.php +++ b/sources/AppBundle/Controller/Event/CFP/InviteAction.php @@ -6,10 +6,10 @@ use AppBundle\CFP\SpeakerFactory; use AppBundle\Controller\Event\EventActionHelper; -use AppBundle\Event\Model\Repository\TalkInvitationRepository; +use AppBundle\Event\Entity\Repository\TalkInvitationRepository; use AppBundle\Event\Model\Repository\TalkRepository; use AppBundle\Event\Model\Repository\TalkToSpeakersRepository; -use AppBundle\Event\Model\TalkInvitation; +use AppBundle\Event\Enum\TalkInvitationState; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; @@ -31,7 +31,7 @@ public function __invoke(Request $request): RedirectResponse $event = $this->eventActionHelper->getEvent($request->attributes->get('eventSlug')); $token = $request->attributes->get('token'); $talkId = (int) $request->attributes->get('talkId'); - $invitation = $this->talkInvitationRepository->get(['talk_id' => $talkId, 'token' => $token]); + $invitation = $this->talkInvitationRepository->findOneBy(['talkId' => $talkId, 'token' => $token]); $talk = $this->talkRepository->get($talkId); if ($invitation === null || $talk === null || $talk->getForumId() !== $event->getId()) { @@ -49,8 +49,8 @@ public function __invoke(Request $request): RedirectResponse ]); } - if ($invitation->getState() === TalkInvitation::STATE_PENDING) { - $invitation->setState(TalkInvitation::STATE_ACCEPTED); + if ($invitation->state === TalkInvitationState::Pending) { + $invitation->state = TalkInvitationState::Accepted; $this->addFlash('success', $this->translator->trans('Vous etes désormais co-conférencier !')); // Save co-speaker $this->talkInvitationRepository->save($invitation); diff --git a/sources/AppBundle/Event/Entity/Repository/TalkInvitationRepository.php b/sources/AppBundle/Event/Entity/Repository/TalkInvitationRepository.php new file mode 100644 index 000000000..18a9b99c9 --- /dev/null +++ b/sources/AppBundle/Event/Entity/Repository/TalkInvitationRepository.php @@ -0,0 +1,37 @@ + + */ +final class TalkInvitationRepository extends EntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, TalkInvitation::class); + } + + /** + * @return list + */ + public function getPendingInvitationsByTalkId(int $talkId): array + { + ($queryBuilder = $this->createQueryBuilder('i')) + ->where($queryBuilder->expr()->andX( + $queryBuilder->expr()->eq('i.talkId', ':talkId'), + $queryBuilder->expr()->eq('i.state', ':state'), + )) + ->setParameter('talkId', $talkId) + ->setParameter('state', TalkInvitationState::Pending); + + return $queryBuilder->getQuery()->getResult(); + } +} diff --git a/sources/AppBundle/Event/Entity/TalkInvitation.php b/sources/AppBundle/Event/Entity/TalkInvitation.php new file mode 100644 index 000000000..312347512 --- /dev/null +++ b/sources/AppBundle/Event/Entity/TalkInvitation.php @@ -0,0 +1,37 @@ + + */ class TalkInvitationType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void @@ -18,4 +23,11 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ->add('save', SubmitType::class, ['label' => 'Inviter']) ; } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => TalkInvitation::class, + ]); + } } diff --git a/sources/AppBundle/Event/Model/Repository/TalkInvitationRepository.php b/sources/AppBundle/Event/Model/Repository/TalkInvitationRepository.php deleted file mode 100644 index 0cde236f3..000000000 --- a/sources/AppBundle/Event/Model/Repository/TalkInvitationRepository.php +++ /dev/null @@ -1,88 +0,0 @@ - - */ -class TalkInvitationRepository extends Repository implements MetadataInitializer -{ - /** - * @param $talkId - * @return CollectionInterface - */ - public function getPendingInvitationsByTalkId($talkId) - { - $query = $this->getPreparedQuery( - 'SELECT asi.id, asi.talk_id, asi.state, asi.submitted_on, asi.submitted_by, asi.token, asi.email - FROM afup_sessions_invitation asi - WHERE asi.talk_id = :talkId AND asi.state = :state', - ); - $query->setParams(['talkId' => $talkId, 'state' => TalkInvitation::STATE_PENDING]); - - return $query->query(); - } - - /** - * @return Metadata - */ - public static function initMetadata(SerializerFactoryInterface $serializerFactory, array $options = []) - { - $metadata = new Metadata($serializerFactory); - $metadata->setEntity(TalkInvitation::class); - $metadata->setConnectionName('main'); - $metadata->setDatabase($options['database']); - $metadata->setTable('afup_sessions_invitation'); - - $metadata - ->addField([ - 'columnName' => 'id', - 'fieldName' => 'id', - 'primary' => true, - 'autoincrement' => true, - 'type' => 'int', - ]) - ->addField([ - 'columnName' => 'talk_id', - 'fieldName' => 'talkId', - 'type' => 'int', - ]) - ->addField([ - 'columnName' => 'state', - 'fieldName' => 'state', - 'type' => 'int', - ]) - ->addField([ - 'columnName' => 'submitted_on', - 'fieldName' => 'submittedOn', - 'type' => 'datetime', - ]) - ->addField([ - 'columnName' => 'submitted_by', - 'fieldName' => 'submittedBy', - 'type' => 'int', - ]) - ->addField([ - 'columnName' => 'token', - 'fieldName' => 'token', - 'type' => 'string', - ]) - ->addField([ - 'columnName' => 'email', - 'fieldName' => 'email', - 'type' => 'string', - ]) - ; - - return $metadata; - } -} diff --git a/sources/AppBundle/Event/Model/TalkInvitation.php b/sources/AppBundle/Event/Model/TalkInvitation.php deleted file mode 100644 index 1df4741c5..000000000 --- a/sources/AppBundle/Event/Model/TalkInvitation.php +++ /dev/null @@ -1,183 +0,0 @@ -id; - } - - /** - * @param int $id - */ - public function setId($id): self - { - $this->propertyChanged('id', $this->id, $id); - $this->id = $id; - return $this; - } - - /** - * @return int - */ - public function getTalkId() - { - return $this->talkId; - } - - /** - * @param int $talkId - */ - public function setTalkId($talkId): self - { - $this->propertyChanged('talkId', $this->talkId, $talkId); - $this->talkId = $talkId; - return $this; - } - - /** - * @return string - */ - public function getEmail() - { - return $this->email; - } - - /** - * @param string $email - */ - public function setEmail($email): self - { - $this->propertyChanged('email', $this->email, $email); - $this->email = $email; - return $this; - } - - /** - * @return string - */ - public function getToken() - { - return $this->token; - } - - /** - * @param string $token - */ - public function setToken($token): self - { - $this->propertyChanged('token', $this->token, $token); - $this->token = $token; - return $this; - } - - /** - * @return \DateTime - */ - public function getSubmittedOn(): ?\DateTime - { - return $this->submittedOn; - } - - public function setSubmittedOn(\DateTime $submittedOn): self - { - $this->propertyChanged('submittedOn', $this->submittedOn, $submittedOn); - - $this->submittedOn = $submittedOn; - return $this; - } - - /** - * @return int - */ - public function getState() - { - return $this->state; - } - - /** - * @param int $state - */ - public function setState($state): self - { - $this->propertyChanged('state', $this->state, $state); - - $this->state = $state; - return $this; - } - - /** - * @return int - */ - public function getSubmittedBy() - { - return $this->submittedBy; - } - - /** - * @param int $submittedBy - */ - public function setSubmittedBy($submittedBy): self - { - $this->propertyChanged('submittedBy', $this->submittedBy, $submittedBy); - $this->submittedBy = $submittedBy; - return $this; - } -} diff --git a/sources/AppBundle/Event/Talk/InvitationFormHandler.php b/sources/AppBundle/Event/Talk/InvitationFormHandler.php index e22c5ccf1..f25c05487 100644 --- a/sources/AppBundle/Event/Talk/InvitationFormHandler.php +++ b/sources/AppBundle/Event/Talk/InvitationFormHandler.php @@ -6,12 +6,12 @@ use AppBundle\Email\Mailer\Mailer; use AppBundle\Email\Mailer\MailUser; +use AppBundle\Event\Entity\Repository\TalkInvitationRepository; +use AppBundle\Event\Entity\TalkInvitation; use AppBundle\Event\Model\Event; use AppBundle\Event\Model\GithubUser; -use AppBundle\Event\Model\Repository\TalkInvitationRepository; use AppBundle\Event\Model\Talk; -use AppBundle\Event\Model\TalkInvitation; -use CCMBenchmark\Ting\Driver\QueryException; +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormInterface; @@ -41,7 +41,7 @@ public function handle(Request $request, Event $event, FormInterface $form, Gith $invitation = $form->getData(); try { $this->talkInvitationRepository->save($invitation); - } catch (QueryException $exception) { + } catch (UniqueConstraintViolationException $exception) { $form->addError(new FormError($exception->getMessage())); } // Send mail to the other guy, begging for him to join the talk @@ -52,10 +52,10 @@ public function handle(Request $request, Event $event, FormInterface $form, Gith '%link%' => $this->urlGenerator->generate('cfp_invite', [ 'eventSlug' => $event->getPath(), 'talkId' => $talk->getId(), - 'token' => $invitation->getToken(), + 'token' => $invitation->token, ], UrlGeneratorInterface::ABSOLUTE_URL), ]); - $this->mailer->sendSimpleMessage('CFP Afup', $text, new MailUser($invitation->getEmail())); + $this->mailer->sendSimpleMessage('CFP Afup', $text, new MailUser($invitation->email)); }); return true; diff --git a/templates/event/cfp/edit.html.twig b/templates/event/cfp/edit.html.twig index 631758622..76eec9e9a 100644 --- a/templates/event/cfp/edit.html.twig +++ b/templates/event/cfp/edit.html.twig @@ -18,11 +18,11 @@
  • {{ speaker.firstname~' '~speaker.lastname }}
  • {% endfor %} - {% if invitations.count > 0 %} + {% if invitations|length > 0 %}

    {{ 'Invitations en attente'|trans }}

      {% for invitation in invitations %} -
    • {{ invitation['asi'].email }}
    • +
    • {{ invitation.email }}
    • {% endfor %}
    {% endif %} diff --git a/tests/integration/AppBundle/Event/Entity/Repository/TalkInvitationRepositoryTest.php b/tests/integration/AppBundle/Event/Entity/Repository/TalkInvitationRepositoryTest.php new file mode 100644 index 000000000..cb4fb317f --- /dev/null +++ b/tests/integration/AppBundle/Event/Entity/Repository/TalkInvitationRepositoryTest.php @@ -0,0 +1,71 @@ +get(TalkInvitationRepository::class); + $connection = self::getContainer()->get(Connection::class); + + $this->insertInvitation($connection, 1, 'pending@example.com', TalkInvitationState::Pending->value); + $this->insertInvitation($connection, 1, 'accepted@example.com', TalkInvitationState::Accepted->value); + $this->insertInvitation($connection, 2, 'other-talk@example.com', TalkInvitationState::Pending->value); + + $invitations = $repository->getPendingInvitationsByTalkId(1); + + self::assertCount(1, $invitations); + self::assertSame('pending@example.com', $invitations[0]->email); + self::assertSame(TalkInvitationState::Pending, $invitations[0]->state); + self::assertSame(1, $invitations[0]->talkId); + } + + public function testGetPendingInvitationsByTalkIdReturnsEmptyArrayForUnknownTalk(): void + { + $repository = self::getContainer()->get(TalkInvitationRepository::class); + + self::assertSame([], $repository->getPendingInvitationsByTalkId(9999)); + } + + public function testSavePersistsInvitation(): void + { + $repository = self::getContainer()->get(TalkInvitationRepository::class); + + $invitation = new TalkInvitation(); + $invitation->talkId = 10; + $invitation->submittedBy = 20; + $invitation->submittedOn = new \DateTime('2026-09-21 10:00:00'); + $invitation->token = 'token-abc'; + $invitation->email = 'invite@example.com'; + $invitation->state = TalkInvitationState::Pending; + + $repository->save($invitation); + + $fromDatabase = $repository->findOneBy(['talkId' => 10, 'token' => 'token-abc']); + self::assertInstanceOf(TalkInvitation::class, $fromDatabase); + self::assertSame('invite@example.com', $fromDatabase->email); + self::assertSame(20, $fromDatabase->submittedBy); + self::assertSame(TalkInvitationState::Pending, $fromDatabase->state); + } + + private function insertInvitation(Connection $connection, int $talkId, string $email, int $state): void + { + $connection->insert('afup_sessions_invitation', [ + 'talk_id' => $talkId, + 'state' => $state, + 'submitted_on' => '2026-09-21 10:00:00', + 'submitted_by' => 20, + 'token' => 'token-' . md5($email), + 'email' => $email, + ]); + } +}