setInitialPlaces(['initial']) ->addPlaces(['initial', 'intermediate', 'step1', 'step2', 'step3']) ->addTransition(new Transition('intermediate', 'initial', 'intermediate')) ->addTransition($transition1 = new Transition('transition1', 'intermediate', 'step1')) ->addTransition($transition2 = new Transition('transition2', 'intermediate', 'step2')) ->addTransition($transition3 = new Transition('transition3', 'intermediate', 'step3')) ; $transitionMetadata = new \SplObjectStorage(); $transitionMetadata->attach($transition1, ['transitionGuard' => 'only-dest']); $transitionMetadata->attach($transition2, ['transitionGuard' => 'only-dest+system']); $transitionMetadata->attach($transition3, ['transitionGuard' => 'system']); $builder->setMetadataStore(new InMemoryMetadataStore(transitionsMetadata: $transitionMetadata)); if (null !== $eventSubscriber) { $eventDispatcher = new EventDispatcher(); $eventDispatcher->addSubscriber($eventSubscriber); } $workflow = new Workflow($builder->build(), new EntityWorkflowMarkingStore(), $eventDispatcher ?? null, 'dummy'); $registry = new Registry(); $registry->addWorkflow( $workflow, new class () implements WorkflowSupportStrategyInterface { public function supports(WorkflowInterface $workflow, object $subject): bool { return true; } } ); return $registry; } /** * @dataProvider provideBlockingTransition */ public function testTransitionGuardBlocked(EntityWorkflow $entityWorkflow, string $transition, ?User $user, bool $isGrantedAllTransition, string $uuid): void { $userRender = $this->prophesize(UserRender::class); $userRender->renderString(Argument::type(User::class), [])->willReturn('user-as-string'); $security = $this->prophesize(Security::class); $security->getUser()->willReturn($user); $security->isGranted(EntityWorkflowTransitionVoter::APPLY_ALL_TRANSITIONS, $entityWorkflow->getCurrentStep()) ->willReturn($isGrantedAllTransition); $transitionGuard = new EntityWorkflowGuardTransition($userRender->reveal(), $security->reveal()); $registry = self::buildRegistry($transitionGuard); $workflow = $registry->get($entityWorkflow, 'dummy'); $context = new WorkflowTransitionContextDTO($entityWorkflow); self::expectException(NotEnabledTransitionException::class); try { $workflow->apply($entityWorkflow, $transition, ['context' => $context, 'byUser' => $user, 'transition' => $transition, 'transitionAt' => new \DateTimeImmutable('now')]); } catch (NotEnabledTransitionException $e) { $list = $e->getTransitionBlockerList(); self::assertEquals(1, $list->count()); $list = iterator_to_array($list->getIterator()); self::assertEquals($uuid, $list[0]->getCode()); throw $e; } } /** * @dataProvider provideValidTransition */ public function testTransitionGuardValid(EntityWorkflow $entityWorkflow, string $transition, ?User $user, bool $isGrantedAllTransition, string $newStep): void { $userRender = $this->prophesize(UserRender::class); $userRender->renderString(Argument::type(User::class), [])->willReturn('user-as-string'); $security = $this->prophesize(Security::class); $security->getUser()->willReturn($user); $security->isGranted(EntityWorkflowTransitionVoter::APPLY_ALL_TRANSITIONS, $entityWorkflow->getCurrentStep()) ->willReturn($isGrantedAllTransition); $transitionGuard = new EntityWorkflowGuardTransition($userRender->reveal(), $security->reveal()); $registry = self::buildRegistry($transitionGuard); $workflow = $registry->get($entityWorkflow, 'dummy'); $context = new WorkflowTransitionContextDTO($entityWorkflow); $workflow->apply($entityWorkflow, $transition, ['context' => $context, 'byUser' => $user, 'transition' => $transition, 'transitionAt' => new \DateTimeImmutable('now')]); self::assertEquals($newStep, $entityWorkflow->getStep()); } public static function provideBlockingTransition(): iterable { yield [self::buildEntityWorkflow([new User()]), 'transition1', new User(), false, 'f3eeb57c-7532-11ec-9495-e7942a2ac7bc']; yield [self::buildEntityWorkflow([]), 'transition1', null, false, 'd9e39a18-704c-11ef-b235-8fe0619caee7']; yield [self::buildEntityWorkflow([new User()]), 'transition1', null, false, 'd9e39a18-704c-11ef-b235-8fe0619caee7']; yield [self::buildEntityWorkflow([$user = new User()]), 'transition3', $user, false, '5b6b95e0-704d-11ef-a5a9-4b6fc11a8eeb']; yield [self::buildEntityWorkflow([$user = new User()]), 'transition3', $user, true, '5b6b95e0-704d-11ef-a5a9-4b6fc11a8eeb']; } public static function provideValidTransition(): iterable { yield [self::buildEntityWorkflow([$u = new User()]), 'transition1', $u, false, 'step1']; yield [self::buildEntityWorkflow([$u = new User()]), 'transition2', $u, false, 'step2']; yield [self::buildEntityWorkflow([new User()]), 'transition2', null, false, 'step2']; yield [self::buildEntityWorkflow([]), 'transition2', null, false, 'step2']; yield [self::buildEntityWorkflow([new User()]), 'transition3', null, false, 'step3']; yield [self::buildEntityWorkflow([]), 'transition3', null, false, 'step3']; // transition allowed thanks to permission "apply all transitions" yield [self::buildEntityWorkflow([new User()]), 'transition1', new User(), true, 'step1']; yield [self::buildEntityWorkflow([new User()]), 'transition2', new User(), true, 'step2']; } public static function buildEntityWorkflow(array $futureDestUsers): EntityWorkflow { $registry = self::buildRegistry(null); $baseContext = ['transition' => 'intermediate', 'transitionAt' => new \DateTimeImmutable()]; // test a user not is destination is blocked $entityWorkflow = new EntityWorkflow(); $workflow = $registry->get($entityWorkflow, 'dummy'); $dto = new WorkflowTransitionContextDTO($entityWorkflow); $dto->futureDestUsers = $futureDestUsers; $workflow->apply($entityWorkflow, 'intermediate', ['context' => $dto, ...$baseContext]); return $entityWorkflow; } }