EntityWorkflow::class])] #[ORM\Entity] #[ORM\Table('chill_main_workflow_entity')] #[EntityWorkflowCreation(groups: ['creation'])] class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface { use TrackCreationTrait; use TrackUpdateTrait; /** * a list of future cc users for the next steps. * * @var array|User[] */ public array $futureCcUsers = []; /** * a list of future dest emails for the next steps. * * This is in used in order to let controller inform who will be the future emails which will validate * the next step. This is necessary to perform some computation about the next emails, before they are * associated to the entity EntityWorkflowStep. * * @var array|string[] */ public array $futureDestEmails = []; /** * a list of future dest users for the next steps. * * This is in used in order to let controller inform who will be the future users which will validate * the next step. This is necessary to perform some computation about the next users, before they are * associated to the entity EntityWorkflowStep. * * @var array|User[] */ public array $futureDestUsers = []; /** * @var Collection */ #[ORM\OneToMany(targetEntity: EntityWorkflowComment::class, mappedBy: 'entityWorkflow', orphanRemoval: true)] private Collection $comments; #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)] private ?int $id = null; #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)] private string $relatedEntityClass = ''; #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)] private int $relatedEntityId; /** * @var Collection */ #[Assert\Valid(traverse: true)] #[ORM\OneToMany(targetEntity: EntityWorkflowStep::class, mappedBy: 'entityWorkflow', orphanRemoval: true, cascade: ['persist'])] #[ORM\OrderBy(['transitionAt' => 'ASC', 'id' => 'ASC'])] private Collection $steps; /** * @var array|EntityWorkflowStep[]|null */ private ?array $stepsChainedCache = null; /** * @var Collection */ #[ORM\ManyToMany(targetEntity: User::class)] #[ORM\JoinTable(name: 'chill_main_workflow_entity_subscriber_to_final')] private Collection $subscriberToFinal; /** * @var Collection */ #[ORM\ManyToMany(targetEntity: User::class)] #[ORM\JoinTable(name: 'chill_main_workflow_entity_subscriber_to_step')] private Collection $subscriberToStep; /** * a step which will store all the transition data. */ private ?EntityWorkflowStep $transitionningStep = null; #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)] private string $workflowName; public function __construct() { $this->subscriberToFinal = new ArrayCollection(); $this->subscriberToStep = new ArrayCollection(); $this->comments = new ArrayCollection(); $this->steps = new ArrayCollection(); $initialStep = new EntityWorkflowStep(); $initialStep ->setCurrentStep('initial'); $this->addStep($initialStep); } public function addComment(EntityWorkflowComment $comment): self { if (!$this->comments->contains($comment)) { $this->comments[] = $comment; $comment->setEntityWorkflow($this); } return $this; } /** * @internal You should prepare a step and run a workflow transition instead of manually adding a step */ public function addStep(EntityWorkflowStep $step): self { if (!$this->steps->contains($step)) { $this->steps[] = $step; $step->setEntityWorkflow($this); $this->stepsChainedCache = null; } return $this; } public function addSubscriberToFinal(User $user): self { if (!$this->subscriberToFinal->contains($user)) { $this->subscriberToFinal[] = $user; } return $this; } public function addSubscriberToStep(User $user): self { if (!$this->subscriberToStep->contains($user)) { $this->subscriberToStep[] = $user; } return $this; } public function getComments(): Collection { return $this->comments; } public function getCurrentStep(): ?EntityWorkflowStep { $step = $this->steps->last(); if (false !== $step) { return $step; } return null; } public function getCurrentStepChained(): ?EntityWorkflowStep { $steps = $this->getStepsChained(); $currentStep = $this->getCurrentStep(); foreach ($steps as $step) { if ($step === $currentStep) { return $step; } } return null; } public function getCurrentStepCreatedAt(): ?\DateTimeInterface { if (null !== $previous = $this->getPreviousStepIfAny()) { return $previous->getTransitionAt(); } return null; } public function getCurrentStepCreatedBy(): ?User { if (null !== $previous = $this->getPreviousStepIfAny()) { return $previous->getTransitionBy(); } return null; } public function getId(): ?int { return $this->id; } public function getRelatedEntityClass(): string { return $this->relatedEntityClass; } public function getRelatedEntityId(): int { return $this->relatedEntityId; } /** * Method used by MarkingStore. * * get a string representation of the step */ public function getStep(): string { return $this->getCurrentStep()->getCurrentStep(); } public function getStepAfter(EntityWorkflowStep $step): ?EntityWorkflowStep { $iterator = $this->steps->getIterator(); if ($iterator instanceof \Iterator) { $iterator->rewind(); while ($iterator->valid()) { $curStep = $iterator->current(); if ($curStep === $step) { $iterator->next(); if ($iterator->valid()) { return $iterator->current(); } return null; } $iterator->next(); } return null; } throw new \RuntimeException(); } public function getSteps(): ArrayCollection|Collection { return $this->steps; } public function getStepsChained(): array { if (\is_array($this->stepsChainedCache)) { return $this->stepsChainedCache; } $iterator = $this->steps->getIterator(); $current = null; $steps = []; $iterator->rewind(); do { $previous = $current; $current = $iterator->current(); $steps[] = $current; $current->setPrevious($previous); $iterator->next(); if ($iterator->valid()) { $current->setNext($iterator->current()); } else { $current->setNext(null); } } while ($iterator->valid()); $this->stepsChainedCache = $steps; return $steps; } public function getSubscriberToFinal(): ArrayCollection|Collection { return $this->subscriberToFinal; } public function getSubscriberToStep(): ArrayCollection|Collection { return $this->subscriberToStep; } /** * get the step which is transitionning. Should be called only by event which will * concern the transition. */ public function getTransitionningStep(): ?EntityWorkflowStep { return $this->transitionningStep; } /** * @return User[] */ public function getUsersInvolved(): array { $usersInvolved = []; $usersInvolved[spl_object_hash($this->getCreatedBy())] = $this->getCreatedBy(); foreach ($this->steps as $step) { foreach ($step->getDestUser() as $u) { $usersInvolved[spl_object_hash($u)] = $u; } } return $usersInvolved; } public function getWorkflowName(): string { return $this->workflowName; } public function isFinal(): bool { foreach ($this->getStepsChained() as $step) { if ($step->isFinal()) { return true; } } return false; } public function isFreeze(): bool { $steps = $this->getStepsChained(); foreach ($this->getStepsChained() as $step) { if ($step->isFreezeAfter()) { return true; } } return false; } public function isUserSubscribedToFinal(User $user): bool { return $this->subscriberToFinal->contains($user); } public function isUserSubscribedToStep(User $user): bool { return $this->subscriberToStep->contains($user); } public function prepareStepBeforeTransition(EntityWorkflowStep $step): self { $this->transitionningStep = $step; return $this; } public function removeComment(EntityWorkflowComment $comment): self { if ($this->comments->removeElement($comment)) { $comment->setEntityWorkflow(null); } return $this; } public function removeStep(EntityWorkflowStep $step): self { if ($this->steps->removeElement($step)) { $step->setEntityWorkflow(null); } return $this; } public function removeSubscriberToFinal(User $user): self { $this->subscriberToFinal->removeElement($user); return $this; } public function removeSubscriberToStep(User $user): self { $this->subscriberToStep->removeElement($user); return $this; } public function setRelatedEntityClass(string $relatedEntityClass): EntityWorkflow { $this->relatedEntityClass = $relatedEntityClass; return $this; } public function setRelatedEntityId(int $relatedEntityId): EntityWorkflow { $this->relatedEntityId = $relatedEntityId; return $this; } /** * Method use by marking store. * * @return $this */ public function setStep(string $step): self { $newStep = new EntityWorkflowStep(); $newStep->setCurrentStep($step); // copy the freeze if ($this->isFreeze()) { $newStep->setFreezeAfter(true); } $this->addStep($newStep); return $this; } public function setWorkflowName(string $workflowName): EntityWorkflow { $this->workflowName = $workflowName; return $this; } private function getPreviousStepIfAny(): ?EntityWorkflowStep { if (1 === \count($this->steps)) { return null; } return $this->steps->get($this->steps->count() - 2); } }