From 7f3de62b2c042deeae6df2e3da9a77f1fb78328a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 9 Jul 2024 23:36:12 +0200 Subject: [PATCH 1/5] Move the metadata on each workflow transition from the event subscriber to the entity EntityWorkflow::setStep method The main update is in the setStep method of EntityWorkflow, where parameters are added to capture the transition details. These include the exact transition, the user who made the transition and the time of transition. The WorkflowController extracts this information and put it into the transition's context. The MarkingStore transfer it to the EntityWorkflow::setStep method, and all metadata are recorded within the entities themselve. --- .../Controller/WorkflowController.php | 23 +++++++++- .../Entity/Workflow/EntityWorkflow.php | 9 +++- .../Entity/Workflow/EntityWorkflowTest.php | 44 +++++++++++++++---- .../EntityWorkflowMarkingStoreTest.php | 12 ++++- .../Workflow/EntityWorkflowMarkingStore.php | 6 ++- ...ntityWorkflowTransitionEventSubscriber.php | 6 --- 6 files changed, 81 insertions(+), 19 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Controller/WorkflowController.php b/src/Bundle/ChillMainBundle/Controller/WorkflowController.php index 3a8626f26..6898d87e4 100644 --- a/src/Bundle/ChillMainBundle/Controller/WorkflowController.php +++ b/src/Bundle/ChillMainBundle/Controller/WorkflowController.php @@ -25,6 +25,7 @@ use Chill\MainBundle\Workflow\EntityWorkflowManager; use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Clock\ClockInterface; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\HttpFoundation\Request; @@ -39,7 +40,18 @@ use Symfony\Contracts\Translation\TranslatorInterface; class WorkflowController extends AbstractController { - public function __construct(private readonly EntityWorkflowManager $entityWorkflowManager, private readonly EntityWorkflowRepository $entityWorkflowRepository, private readonly ValidatorInterface $validator, private readonly PaginatorFactory $paginatorFactory, private readonly Registry $registry, private readonly EntityManagerInterface $entityManager, private readonly TranslatorInterface $translator, private readonly ChillSecurity $security, private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry) {} + public function __construct( + private readonly EntityWorkflowManager $entityWorkflowManager, + private readonly EntityWorkflowRepository $entityWorkflowRepository, + private readonly ValidatorInterface $validator, + private readonly PaginatorFactory $paginatorFactory, + private readonly Registry $registry, + private readonly EntityManagerInterface $entityManager, + private readonly TranslatorInterface $translator, + private readonly ChillSecurity $security, + private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry, + private readonly ClockInterface $clock, + ) {} #[Route(path: '/{_locale}/main/workflow/create', name: 'chill_main_workflow_create')] public function create(Request $request): Response @@ -310,7 +322,14 @@ class WorkflowController extends AbstractController throw $this->createAccessDeniedException(sprintf("not allowed to apply transition {$transition}: %s", implode(', ', $msgs))); } - $workflow->apply($entityWorkflow, $transition, ['context' => $stepDTO]); + $byUser = $this->security->getUser(); + + $workflow->apply($entityWorkflow, $transition, [ + 'context' => $stepDTO, + 'byUser' => $byUser, + 'transition' => $transition, + 'transitionAt' => $this->clock->now(), + ]); $this->entityManager->flush(); diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php index 9c314115e..b1a93534d 100644 --- a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php @@ -413,8 +413,15 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface * * @return $this */ - public function setStep(string $step, WorkflowTransitionContextDTO $transitionContextDTO): self + public function setStep(string $step, WorkflowTransitionContextDTO $transitionContextDTO, string $transition, \DateTimeImmutable $transitionAt, ?User $byUser = null): self { + $previousStep = $this->getCurrentStep(); + + $previousStep + ->setTransitionAfter($transition) + ->setTransitionAt($transitionAt) + ->setTransitionBy($byUser); + $newStep = new EntityWorkflowStep(); $newStep->setCurrentStep($step); diff --git a/src/Bundle/ChillMainBundle/Tests/Entity/Workflow/EntityWorkflowTest.php b/src/Bundle/ChillMainBundle/Tests/Entity/Workflow/EntityWorkflowTest.php index ec5fa6ae2..30003144f 100644 --- a/src/Bundle/ChillMainBundle/Tests/Entity/Workflow/EntityWorkflowTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Entity/Workflow/EntityWorkflowTest.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Tests\Entity\Workflow; +use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO; use PHPUnit\Framework\TestCase; @@ -26,7 +27,7 @@ final class EntityWorkflowTest extends TestCase { $entityWorkflow = new EntityWorkflow(); - $entityWorkflow->setStep('final', new WorkflowTransitionContextDTO($entityWorkflow)); + $entityWorkflow->setStep('final', new WorkflowTransitionContextDTO($entityWorkflow), 'finalize', new \DateTimeImmutable()); $entityWorkflow->getCurrentStep()->setIsFinal(true); $this->assertTrue($entityWorkflow->isFinal()); @@ -38,16 +39,16 @@ final class EntityWorkflowTest extends TestCase $this->assertFalse($entityWorkflow->isFinal()); - $entityWorkflow->setStep('two', new WorkflowTransitionContextDTO($entityWorkflow)); + $entityWorkflow->setStep('two', new WorkflowTransitionContextDTO($entityWorkflow), 'two', new \DateTimeImmutable()); $this->assertFalse($entityWorkflow->isFinal()); - $entityWorkflow->setStep('previous_final', new WorkflowTransitionContextDTO($entityWorkflow)); + $entityWorkflow->setStep('previous_final', new WorkflowTransitionContextDTO($entityWorkflow), 'three', new \DateTimeImmutable()); $this->assertFalse($entityWorkflow->isFinal()); $entityWorkflow->getCurrentStep()->setIsFinal(true); - $entityWorkflow->setStep('final', new WorkflowTransitionContextDTO($entityWorkflow)); + $entityWorkflow->setStep('final', new WorkflowTransitionContextDTO($entityWorkflow), 'four', new \DateTimeImmutable()); $this->assertTrue($entityWorkflow->isFinal()); } @@ -58,23 +59,50 @@ final class EntityWorkflowTest extends TestCase $this->assertFalse($entityWorkflow->isFreeze()); - $entityWorkflow->setStep('step_one', new WorkflowTransitionContextDTO($entityWorkflow)); + $entityWorkflow->setStep('step_one', new WorkflowTransitionContextDTO($entityWorkflow), 'to_step_one', new \DateTimeImmutable()); $this->assertFalse($entityWorkflow->isFreeze()); - $entityWorkflow->setStep('step_three', new WorkflowTransitionContextDTO($entityWorkflow)); + $entityWorkflow->setStep('step_three', new WorkflowTransitionContextDTO($entityWorkflow), 'to_step_three', new \DateTimeImmutable()); $this->assertFalse($entityWorkflow->isFreeze()); - $entityWorkflow->setStep('freezed', new WorkflowTransitionContextDTO($entityWorkflow)); + $entityWorkflow->setStep('freezed', new WorkflowTransitionContextDTO($entityWorkflow), 'to_freezed', new \DateTimeImmutable()); $entityWorkflow->getCurrentStep()->setFreezeAfter(true); $this->assertTrue($entityWorkflow->isFreeze()); - $entityWorkflow->setStep('after_freeze', new WorkflowTransitionContextDTO($entityWorkflow)); + $entityWorkflow->setStep('after_freeze', new WorkflowTransitionContextDTO($entityWorkflow), 'to_after_freeze', new \DateTimeImmutable()); $this->assertTrue($entityWorkflow->isFreeze()); $this->assertTrue($entityWorkflow->getCurrentStep()->isFreezeAfter()); } + + public function testPreviousStepMetadataAreFilled() + { + $entityWorkflow = new EntityWorkflow(); + $initialStep = $entityWorkflow->getCurrentStep(); + + $entityWorkflow->setStep('step_one', new WorkflowTransitionContextDTO($entityWorkflow), 'to_step_one', new \DateTimeImmutable('2024-01-01'), $user1 = new User()); + + $previous = $entityWorkflow->getCurrentStep(); + + $entityWorkflow->setStep('step_one', new WorkflowTransitionContextDTO($entityWorkflow), 'to_step_two', new \DateTimeImmutable('2024-01-02'), $user2 = new User()); + + $final = $entityWorkflow->getCurrentStep(); + + $stepsChained = $entityWorkflow->getStepsChained(); + + self::assertCount(3, $stepsChained); + self::assertSame($initialStep, $stepsChained[0]); + self::assertSame($previous, $stepsChained[1]); + self::assertSame($final, $stepsChained[2]); + self::assertEquals($user1, $initialStep->getTransitionBy()); + self::assertEquals('2024-01-01', $initialStep->getTransitionAt()?->format('Y-m-d')); + self::assertEquals('to_step_one', $initialStep->getTransitionAfter()); + self::assertEquals($user2, $previous->getTransitionBy()); + self::assertEquals('2024-01-02', $previous->getTransitionAt()?->format('Y-m-d')); + self::assertEquals('to_step_two', $previous->getTransitionAfter()); + } } diff --git a/src/Bundle/ChillMainBundle/Tests/Workflow/EntityWorkflowMarkingStoreTest.php b/src/Bundle/ChillMainBundle/Tests/Workflow/EntityWorkflowMarkingStoreTest.php index a32cefb09..4922cab17 100644 --- a/src/Bundle/ChillMainBundle/Tests/Workflow/EntityWorkflowMarkingStoreTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Workflow/EntityWorkflowMarkingStoreTest.php @@ -39,19 +39,29 @@ class EntityWorkflowMarkingStoreTest extends TestCase { $markingStore = $this->buildMarkingStore(); $workflow = new EntityWorkflow(); + $previousStep = $workflow->getCurrentStep(); $dto = new WorkflowTransitionContextDTO($workflow); $dto->futureCcUsers[] = $user1 = new User(); $dto->futureDestUsers[] = $user2 = new User(); $dto->futureDestEmails[] = $email = 'test@example.com'; - $markingStore->setMarking($workflow, new Marking(['foo' => 1]), ['context' => $dto]); + $markingStore->setMarking($workflow, new Marking(['foo' => 1]), [ + 'context' => $dto, + 'transition' => 'bar_transition', + 'byUser' => $user3 = new User(), + 'transitionAt' => $at = new \DateTimeImmutable(), + ]); $currentStep = $workflow->getCurrentStep(); self::assertEquals('foo', $currentStep->getCurrentStep()); self::assertContains($email, $currentStep->getDestEmail()); self::assertContains($user1, $currentStep->getCcUser()); self::assertContains($user2, $currentStep->getDestUser()); + + self::assertSame($user3, $previousStep->getTransitionBy()); + self::assertSame($at, $previousStep->getTransitionAt()); + self::assertEquals('bar_transition', $previousStep->getTransitionAfter()); } private function buildMarkingStore(): EntityWorkflowMarkingStore diff --git a/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowMarkingStore.php b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowMarkingStore.php index dca929e86..ee07d7a62 100644 --- a/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowMarkingStore.php +++ b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowMarkingStore.php @@ -40,10 +40,14 @@ final readonly class EntityWorkflowMarkingStore implements MarkingStoreInterface $next = array_keys($places)[0]; $transitionDTO = $context['context'] ?? null; + $transition = $context['transition']; + $byUser = $context['byUser'] ?? null; + $at = $context['transitionAt']; + if (!$transitionDTO instanceof WorkflowTransitionContextDTO) { throw new \UnexpectedValueException(sprintf('Expected instance of %s', WorkflowTransitionContextDTO::class)); } - $subject->setStep($next, $transitionDTO); + $subject->setStep($next, $transitionDTO, $transition, $at, $byUser); } } diff --git a/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/EntityWorkflowTransitionEventSubscriber.php b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/EntityWorkflowTransitionEventSubscriber.php index ce33ea6d0..9c74c861c 100644 --- a/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/EntityWorkflowTransitionEventSubscriber.php +++ b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/EntityWorkflowTransitionEventSubscriber.php @@ -108,12 +108,6 @@ final readonly class EntityWorkflowTransitionEventSubscriber implements EventSub /** @var EntityWorkflow $entityWorkflow */ $entityWorkflow = $event->getSubject(); - $step = $entityWorkflow->getCurrentStep(); - - $step - ->setTransitionAfter($event->getTransition()->getName()) - ->setTransitionAt(new \DateTimeImmutable('now')) - ->setTransitionBy($this->security->getUser()); $this->chillLogger->info('[workflow] apply transition on entityWorkflow', [ 'relatedEntityClass' => $entityWorkflow->getRelatedEntityClass(), From b5af9f7b63ef42f31f2cc1a656034788a3dd4ebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 9 Jul 2024 23:36:39 +0200 Subject: [PATCH 2/5] Add futurePersonSignatures property to WorkflowTransitionContextDTO A new property named futurePersonSignatures has been added to the WorkflowTransitionContextDTO class. This will hold a list of Person objects expected to sign the next step, improving the scope of information available within the workflow context. --- .../ChillMainBundle/Entity/Workflow/EntityWorkflow.php | 4 ++++ .../Workflow/WorkflowTransitionContextDTO.php | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php index b1a93534d..4b5da09db 100644 --- a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php @@ -437,6 +437,10 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface $newStep->addDestEmail($email); } + foreach ($transitionContextDTO->futurePersonSignatures as $personSignature) { + new EntityWorkflowStepSignature($newStep, $personSignature); + } + // copy the freeze if ($this->isFreeze()) { $newStep->setFreezeAfter(true); diff --git a/src/Bundle/ChillMainBundle/Workflow/WorkflowTransitionContextDTO.php b/src/Bundle/ChillMainBundle/Workflow/WorkflowTransitionContextDTO.php index 2a8253523..ba4cde51e 100644 --- a/src/Bundle/ChillMainBundle/Workflow/WorkflowTransitionContextDTO.php +++ b/src/Bundle/ChillMainBundle/Workflow/WorkflowTransitionContextDTO.php @@ -51,6 +51,11 @@ class WorkflowTransitionContextDTO */ public array $futureDestEmails = []; + /** + * a list of future @see{Person} with will sign the next step. + */ + public array $futurePersonSignatures = []; + public ?Transition $transition = null; public string $comment = ''; From fe6b4848e60deb3ce4f62001fba7d5f8415dc483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 9 Jul 2024 23:50:28 +0200 Subject: [PATCH 3/5] Implement workflow handlers for stored objects Added new interface, EntityWorkflowWithStoredObjectHandlerInterface, which provides methods to handle workflows associated with stored objects. Two classes, AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler and AccompanyingCourseDocumentWorkflowHandler, have been updated to implement this new interface. The EntityWorkflowManager class has also been updated to handle workflows associated with stored objects. --- ...ompanyingCourseDocumentWorkflowHandler.php | 15 ++++++++--- .../Workflow/EntityWorkflowManager.php | 12 +++++++++ ...rkflowWithStoredObjectHandlerInterface.php | 27 +++++++++++++++++++ ...dWorkEvaluationDocumentWorkflowHandler.php | 15 ++++++++--- 4 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Workflow/EntityWorkflowWithStoredObjectHandlerInterface.php diff --git a/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php b/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php index 55b823dcd..ad16ae722 100644 --- a/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php +++ b/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php @@ -12,15 +12,19 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Workflow; use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument; +use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter; use Chill\MainBundle\Entity\Workflow\EntityWorkflow; -use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface; +use Chill\MainBundle\Workflow\EntityWorkflowWithStoredObjectHandlerInterface; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Symfony\Contracts\Translation\TranslatorInterface; -class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandlerInterface +/** + * @implements EntityWorkflowWithStoredObjectHandlerInterface + */ +class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowWithStoredObjectHandlerInterface { private readonly EntityRepository $repository; @@ -73,8 +77,6 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler } /** - * @param AccompanyingCourseDocument $object - * * @return array[] */ public function getRelatedObjects(object $object): array @@ -121,4 +123,9 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler { return AccompanyingCourseDocument::class === $entityWorkflow->getRelatedEntityClass(); } + + public function getAssociatedStoredObject(EntityWorkflow $entityWorkflow): ?StoredObject + { + return $this->getRelatedEntity($entityWorkflow)?->getObject(); + } } diff --git a/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowManager.php b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowManager.php index 9a1f52280..a45abe081 100644 --- a/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowManager.php +++ b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowManager.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Workflow; +use Chill\DocStoreBundle\Entity\StoredObject; use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Workflow\Exception\HandlerNotFoundException; use Symfony\Component\Workflow\Registry; @@ -37,4 +38,15 @@ class EntityWorkflowManager { return $this->registry->all($entityWorkflow); } + + public function getAssociatedStoredObject(EntityWorkflow $entityWorkflow): ?StoredObject + { + foreach ($this->handlers as $handler) { + if ($handler instanceof EntityWorkflowWithStoredObjectHandlerInterface && $handler->supports($entityWorkflow)) { + return $handler->getAssociatedStoredObject($entityWorkflow); + } + } + + return null; + } } diff --git a/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowWithStoredObjectHandlerInterface.php b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowWithStoredObjectHandlerInterface.php new file mode 100644 index 000000000..a1c7561cd --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowWithStoredObjectHandlerInterface.php @@ -0,0 +1,27 @@ + + */ +interface EntityWorkflowWithStoredObjectHandlerInterface extends EntityWorkflowHandlerInterface +{ + public function getAssociatedStoredObject(EntityWorkflow $entityWorkflow): ?StoredObject; +} diff --git a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php index 2912d1c01..97d48e5b0 100644 --- a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php +++ b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php @@ -11,15 +11,19 @@ declare(strict_types=1); namespace Chill\PersonBundle\Workflow; +use Chill\DocStoreBundle\Entity\StoredObject; use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; -use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface; +use Chill\MainBundle\Workflow\EntityWorkflowWithStoredObjectHandlerInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument; use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocumentRepository; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkEvaluationDocumentVoter; use Symfony\Contracts\Translation\TranslatorInterface; -class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityWorkflowHandlerInterface +/** + * @implements EntityWorkflowWithStoredObjectHandlerInterface + */ +class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityWorkflowWithStoredObjectHandlerInterface { public function __construct(private readonly AccompanyingPeriodWorkEvaluationDocumentRepository $repository, private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly TranslatorInterface $translator) {} @@ -67,8 +71,6 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW } /** - * @param AccompanyingPeriodWorkEvaluationDocument $object - * * @return array[] */ public function getRelatedObjects(object $object): array @@ -123,4 +125,9 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW { return AccompanyingPeriodWorkEvaluationDocument::class === $entityWorkflow->getRelatedEntityClass(); } + + public function getAssociatedStoredObject(EntityWorkflow $entityWorkflow): ?StoredObject + { + return $this->getRelatedEntity($entityWorkflow)?->getStoredObject(); + } } From c1cf27c42dbfd9fd76c4119b5035475e930c34d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 9 Jul 2024 23:51:00 +0200 Subject: [PATCH 4/5] Refactor workflow handlers and update comments Changes include class refactoring for Workflow handlers, using `readonly` and better indentation in constructors for better readability. In addition, outdated comments are removed. Also, entity workflow handlers now implement the EntityWorkflowHandlerInterface type for better type safety. --- .../Authorization/WorkflowEntityDeletionVoter.php | 5 +++-- .../Workflow/EntityWorkflowHandlerInterface.php | 6 ++++++ ...mpanyingPeriodWorkEvaluationWorkflowHandler.php | 14 +++++++++----- .../AccompanyingPeriodWorkWorkflowHandler.php | 14 +++++++++----- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/WorkflowEntityDeletionVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/WorkflowEntityDeletionVoter.php index 9718cf013..3444a57a6 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/WorkflowEntityDeletionVoter.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/WorkflowEntityDeletionVoter.php @@ -12,13 +12,14 @@ declare(strict_types=1); namespace Chill\MainBundle\Security\Authorization; use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository; +use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; class WorkflowEntityDeletionVoter extends Voter { /** - * @param \Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface[] $handlers + * @param EntityWorkflowHandlerInterface[] $handlers */ public function __construct(private $handlers, private readonly EntityWorkflowRepository $entityWorkflowRepository) {} @@ -30,7 +31,7 @@ class WorkflowEntityDeletionVoter extends Voter foreach ($this->handlers as $handler) { if ($handler->isObjectSupported($subject) - && \in_array($attribute, $handler->getDeletionRoles($subject), true)) { + && \in_array($attribute, $handler->getDeletionRoles(), true)) { return true; } } diff --git a/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php index e79982e1c..edd1120a7 100644 --- a/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php +++ b/src/Bundle/ChillMainBundle/Workflow/EntityWorkflowHandlerInterface.php @@ -14,6 +14,9 @@ namespace Chill\MainBundle\Workflow; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\Workflow\EntityWorkflow; +/** + * @template T of object + */ interface EntityWorkflowHandlerInterface { /** @@ -25,6 +28,9 @@ interface EntityWorkflowHandlerInterface public function getEntityTitle(EntityWorkflow $entityWorkflow, array $options = []): string; + /** + * @return T|null + */ public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?object; public function getRelatedObjects(object $object): array; diff --git a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationWorkflowHandler.php b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationWorkflowHandler.php index a041b3c37..113d5f5c8 100644 --- a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationWorkflowHandler.php +++ b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationWorkflowHandler.php @@ -20,9 +20,16 @@ use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvalu use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkEvaluationVoter; use Symfony\Contracts\Translation\TranslatorInterface; -class AccompanyingPeriodWorkEvaluationWorkflowHandler implements EntityWorkflowHandlerInterface +/** + * @implements EntityWorkflowHandlerInterface + */ +readonly class AccompanyingPeriodWorkEvaluationWorkflowHandler implements EntityWorkflowHandlerInterface { - public function __construct(private readonly AccompanyingPeriodWorkEvaluationRepository $repository, private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly TranslatorInterface $translator) {} + public function __construct( + private AccompanyingPeriodWorkEvaluationRepository $repository, + private TranslatableStringHelperInterface $translatableStringHelper, + private TranslatorInterface $translator + ) {} public function getDeletionRoles(): array { @@ -53,9 +60,6 @@ class AccompanyingPeriodWorkEvaluationWorkflowHandler implements EntityWorkflowH return $this->repository->find($entityWorkflow->getRelatedEntityId()); } - /** - * @param AccompanyingPeriodWorkEvaluation $object - */ public function getRelatedObjects(object $object): array { $relateds = []; diff --git a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkWorkflowHandler.php b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkWorkflowHandler.php index ce146a887..837ee2aac 100644 --- a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkWorkflowHandler.php +++ b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkWorkflowHandler.php @@ -21,9 +21,16 @@ use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepos use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter; use Symfony\Contracts\Translation\TranslatorInterface; -class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInterface +/** + * @implements EntityWorkflowHandlerInterface + */ +readonly class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInterface { - public function __construct(private readonly AccompanyingPeriodWorkRepository $repository, private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly TranslatorInterface $translator) {} + public function __construct( + private AccompanyingPeriodWorkRepository $repository, + private TranslatableStringHelperInterface $translatableStringHelper, + private TranslatorInterface $translator + ) {} public function getDeletionRoles(): array { @@ -55,9 +62,6 @@ class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInte return $this->repository->find($entityWorkflow->getRelatedEntityId()); } - /** - * @param AccompanyingPeriodWork $object - */ public function getRelatedObjects(object $object): array { $relateds = []; From a887602f4fbb654a283da9b6019d577ebc7083a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 10 Jul 2024 00:21:07 +0200 Subject: [PATCH 5/5] Handle storing of new object in PdfSignedMessageHandler The PdfSignedMessageHandler has been updated to handle pdf signed messages: the content is now stored within the object. Also, a PdfSignedMessageHandlerTest has been created to ensure the correct functionality of the updated handler. --- .../BaseSigner/PdfSignedMessageHandler.php | 20 +++++ .../PdfSignedMessageHandlerTest.php | 84 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/Service/Signature/Driver/BaseSigner/PdfSignedMessageHandlerTest.php diff --git a/src/Bundle/ChillDocStoreBundle/Service/Signature/Driver/BaseSigner/PdfSignedMessageHandler.php b/src/Bundle/ChillDocStoreBundle/Service/Signature/Driver/BaseSigner/PdfSignedMessageHandler.php index 4002b107b..81fb97dbf 100644 --- a/src/Bundle/ChillDocStoreBundle/Service/Signature/Driver/BaseSigner/PdfSignedMessageHandler.php +++ b/src/Bundle/ChillDocStoreBundle/Service/Signature/Driver/BaseSigner/PdfSignedMessageHandler.php @@ -11,6 +11,9 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Service\Signature\Driver\BaseSigner; +use Chill\DocStoreBundle\Service\StoredObjectManagerInterface; +use Chill\MainBundle\Repository\Workflow\EntityWorkflowStepSignatureRepository; +use Chill\MainBundle\Workflow\EntityWorkflowManager; use Psr\Log\LoggerInterface; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; @@ -23,10 +26,27 @@ final readonly class PdfSignedMessageHandler implements MessageHandlerInterface public function __construct( private LoggerInterface $logger, + private EntityWorkflowManager $entityWorkflowManager, + private StoredObjectManagerInterface $storedObjectManager, + private EntityWorkflowStepSignatureRepository $entityWorkflowStepSignatureRepository, ) {} public function __invoke(PdfSignedMessage $message): void { $this->logger->info(self::P.'a message is received', ['signaturedId' => $message->signatureId]); + + $signature = $this->entityWorkflowStepSignatureRepository->find($message->signatureId); + + if (null === $signature) { + throw new \RuntimeException('no signature found'); + } + + $storedObject = $this->entityWorkflowManager->getAssociatedStoredObject($signature->getStep()->getEntityWorkflow()); + + if (null === $storedObject) { + throw new \RuntimeException('no stored object found'); + } + + $this->storedObjectManager->write($storedObject, $message->content); } } diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Service/Signature/Driver/BaseSigner/PdfSignedMessageHandlerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Service/Signature/Driver/BaseSigner/PdfSignedMessageHandlerTest.php new file mode 100644 index 000000000..62d50c03f --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/Service/Signature/Driver/BaseSigner/PdfSignedMessageHandlerTest.php @@ -0,0 +1,84 @@ +futurePersonSignatures[] = new Person(); + $entityWorkflow->setStep('new_step', $dto, 'new_transition', new \DateTimeImmutable(), new User()); + $step = $entityWorkflow->getCurrentStep(); + $signature = $step->getSignatures()->first(); + + $handler = new PdfSignedMessageHandler( + new NullLogger(), + $this->buildEntityWorkflowManager($storedObject), + $this->buildStoredObjectManager($storedObject, $expectedContent = '1234'), + $this->buildSignatureRepository($signature) + ); + + // we simply call the handler. The mocked StoredObjectManager will check that the "write" method is invoked once + // with the content "1234" + $handler(new PdfSignedMessage(10, $expectedContent)); + } + + private function buildSignatureRepository(EntityWorkflowStepSignature $signature): EntityWorkflowStepSignatureRepository + { + $entityWorkflowStepSignatureRepository = $this->createMock(EntityWorkflowStepSignatureRepository::class); + $entityWorkflowStepSignatureRepository->method('find')->with($this->isType('int'))->willReturn($signature); + + return $entityWorkflowStepSignatureRepository; + } + + private function buildEntityWorkflowManager(?StoredObject $associatedStoredObject): EntityWorkflowManager + { + $entityWorkflowManager = $this->createMock(EntityWorkflowManager::class); + $entityWorkflowManager->method('getAssociatedStoredObject')->willReturn($associatedStoredObject); + + return $entityWorkflowManager; + } + + private function buildStoredObjectManager(StoredObject $expectedStoredObject, string $expectedContent): StoredObjectManagerInterface + { + $storedObjectManager = $this->createMock(StoredObjectManagerInterface::class); + $storedObjectManager->expects($this->once()) + ->method('write') + ->with($this->identicalTo($expectedStoredObject), $expectedContent); + + return $storedObjectManager; + } +}