<?php
namespace Tionvel\WorkflowBundle\Entity;
use App\Entity\User;
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping as ORM;
use Exception;
use Symfony\Component\Security\Core\User\UserInterface;
use Tionvel\WorkflowBundle\Utils\AttributeResolver;
use Tionvel\WorkflowBundle\Utils\Dot;
/**
* Class WorkflowProcess
* @package Tionvel\WorkflowBundle\Entity
* @ORM\Entity(repositoryClass="Tionvel\WorkflowBundle\Repository\WorkflowProcessRepository")
* @ORM\Table(name="workflow_processes")
*/
class WorkflowProcess
{
/**
* @var int
* @ORM\Id @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="string", nullable=true)
*/
private $uuid;
/**
* @var string
* @ORM\Column(type="string")
*/
private $workflow;
/**
* @var string
* @ORM\Column(type="string", nullable=true)
*/
private $subWorkflow;
/**
* @var User
* @ORM\ManyToOne(targetEntity="App\Entity\User")
* @ORM\JoinColumn(onDelete="CASCADE")
*/
private $owner;
/**
* @var ArrayCollection
* @ORM\OneToMany(
* targetEntity="Tionvel\WorkflowBundle\Entity\WorkflowState",
* mappedBy="process",
* fetch="LAZY",
* cascade={"persist"},
* orphanRemoval=false
* )
*/
private $states;
/**
* @var ArrayCollection
* @ORM\OneToMany(
* targetEntity="Tionvel\WorkflowBundle\Entity\WorkflowAttribute",
* mappedBy="process",
* fetch="LAZY",
* cascade={"persist"},
* orphanRemoval=false
* )
*/
private $attributes;
/**
* @var Dot
*/
private $resolvedAttrs;
/**
* WorkflowProcess constructor.
* @throws Exception
*/
public function __construct()
{
$this->attributes = new ArrayCollection;
$this->states = new ArrayCollection;
$this->resolvedAttrs = new Dot;
}
/**
* @param $workflowId
* @param User $user
* @return WorkflowProcess
* @throws Exception
*/
public static function init($workflowId, User $user)
{
list (
$workflowId,
$subWorkflowId
) = explode('/', $workflowId);
$self = new self;
$self->setOwner($user);
$self->setWorkflow($workflowId);
$self->setSubWorkflow($subWorkflowId);
$self->addState(WorkflowState::start($self, $user));
$self->addAssignee('owner', $user);
return $self;
}
/**
* @param $state
* @param User $user
* @param string $transition
* @throws Exception
*/
public function transition($state, $user, $transition)
{
$newState = WorkflowState::start($this, $user, $state, $transition);
$currentState = $this->getCurrentState();
$currentState->finish($newState, $user, $transition);
$newState->setPrev($currentState);
$this->addState($newState);
}
/**
* @return string
*/
public function getId()
{
return $this->id;
}
public function getUuid()
{
return $this->uuid;
}
public function setUuid($uuid)
{
$this->uuid = $uuid;
}
/**
* @return string
*/
public function getWorkflow()
{
return $this->workflow;
}
/**
* @param string $workflow
*/
public function setWorkflow(string $workflow)
{
$this->workflow = $workflow;
}
/**
* @return User
*/
public function getOwner()
{
return $this->owner;
}
/**
* @param User $owner
*/
public function setOwner(User $owner)
{
$this->owner = $owner;
}
/**
* @return ArrayCollection
*/
public function getStates()
{
return $this->states;
}
/**
* @param ArrayCollection $states
*/
public function setStates($states)
{
$this->states = $states;
}
/**
* @param WorkflowState $state
*/
public function addState(WorkflowState $state)
{
if (!$this->states->contains($state)) {
$this->states[] = $state;
$state->setProcess($this);
}
}
/**
* @param string $key
* @return bool
*/
public function hasStateByKey(string $key)
{
return (bool) !$this->states->filter(function (WorkflowState $state) use ($key) {
return $state->isState($key);
})->isEmpty();
}
/**
* @param string $key
* @return ArrayCollection|Collection
*/
public function getStateByKey(string $key)
{
return $this->states->filter(function (WorkflowState $state) use ($key) {
return $state->isState($key);
})->last();
}
/**
* @return WorkflowState
* @throws Exception
*/
public function getCurrentState()
{
if ($state = $this->states->last()) {
return $state;
}
return WorkflowState::start($this, $this->owner);
}
/**
* @return string
* @throws Exception
*/
public function getState()
{
return $this->getCurrentState()->getState();
}
/**
* @param $state
* @return bool
*/
public function inState($state)
{
/** @var WorkflowState $current */
if (!$current = $this->states->last()) {
return false;
}
return $current->isState($state);
}
/**
* @return bool
*/
public function isStarted()
{
return !$this->inState(WorkflowState::START_STATE);
}
/**
* @return DateTime
* @throws Exception
*/
public function getStartedAt()
{
if ($state = $this->getStates()->first()) {
return $state->getStartedAt();
}
return WorkflowState::start($this, $this->owner)->getStartedAt();
}
/**
* @return DateTime
* @throws Exception
*/
public function getStartCompletedAt(): ?DateTime
{
if ($state = $this->getStates()->first()) {
return $state->getCompletedAt();
}
return null;
}
/**
* @return ArrayCollection
*/
public function getAttributes()
{
return $this->attributes;
}
/**
* @param ArrayCollection $attributes
*/
public function setAttributes(ArrayCollection $attributes)
{
$this->attributes = $attributes;
}
/**
* @return array
*/
public function getArrayAttributes()
{
$attributes = [];
/** @var WorkflowAttribute $attribute */
foreach ($this->attributes as $attribute) {
$attributes[$attribute->getPath()] = $attribute->toArray();
}
return $attributes;
}
/**
* @param array $attributes
*/
public function setResolvedAttributes(array $attributes = [])
{
$this->resolvedAttrs = Dot::create($attributes);
}
/**
* @return Dot
*/
public function getResolvedAttributes()
{
return $this->resolvedAttrs;
}
/**
* @return array
*/
public function attrs()
{
return $this->resolvedAttrs->all();
}
/**
* @param $path
* @param $default
* @return mixed
*/
public function attr($path, $default = null)
{
return $this->resolvedAttrs->get($path, $default);
}
/**
* @param $path
* @return array
*/
public function arrayAttr($path)
{
$attr = $this->attr($path, []);
if ((is_array($attr) && isset($attr['created_at']) || !is_array($attr))) {
return [$attr];
}
return $attr;
}
/**
* @param $path
* @return array
*/
public function arrayAttrList($path)
{
$attr = $this->attr($path, []);
if (count($attr) > 1 ) {
return $attr;
}
return [$attr];
}
/**
* @param $path
* @return bool
*/
public function has($path)
{
return $this->resolvedAttrs->has($path);
}
/**
* @param $path
* @param null $default
* @return mixed
*/
public function first($path, $default = null)
{
$attr = $this->attr($path, $default);
return is_array($attr) ? reset($attr) : $attr;
}
/**
* @param $path
* @param null $default
* @return mixed
*/
public function last($path, $default = null)
{
$attr = $this->attr($path, $default);
return is_array($attr) ? end($attr) : $attr;
}
/**
* @param WorkflowAttribute $attribute
*/
public function addAttribute(WorkflowAttribute $attribute)
{
if (!$this->attributes->contains($attribute)) {
$this->attributes->add($attribute);
$attribute->setProcess($this);
}
}
public function removeAttr($key)
{
$this->attributes->matching(
Criteria::create()->where(Criteria::expr()->eq('key0', $key))
)->map(function (WorkflowAttribute $attribute) {
$this->attributes->removeElement($attribute);
$attribute->setProcess(null);
});
}
public function removeAttrByPath($path)
{
$keys = explode('.',$path);
$criteria = Criteria::create();
foreach ($keys as $i => $key) {
$expr = Criteria::expr()->eq('key'.$i, $key);
$i == 0 ? $criteria->where($expr) : $criteria->andWhere($expr);
}
$this->attributes->matching($criteria)->map(function (WorkflowAttribute $attribute) {
$this->attributes->removeElement($attribute);
$attribute->setProcess(null);
});
}
/**
* @param User $user
* @return bool
* @throws Exception
*/
public function userIsAssignee(User $user)
{
return $user === $this->owner || $this->hasAssignee($user);
}
/**
* @param User $user
* @param $key
* @return bool
* @throws Exception
*/
public function userIs(UserInterface $user, $key)
{
return !$this->getCurrentState()
->getAssignees()
->filter(function (WorkflowAssignee $assignee) use ($user, $key) {
return $assignee->isUser($user) && $assignee->isType($key);
})
->isEmpty()
;
}
/**
*
* @param User $user
* @return array
* @throws Exception
*/
public function getUserRole(UserInterface $user)
{
$roles = $this->getCurrentState()
->getAssignees()
->filter(function (WorkflowAssignee $assignee) use ($user) {
return $assignee->isUser($user);
})
->map(function (WorkflowAssignee $assignee) {
return $assignee->getName();
})
->toArray()
;
return array_unique($roles);
}
/**
* @param User $user
* @return bool
* @throws Exception
*/
public function hasAssignee(User $user)
{
return !$this->getCurrentState()
->getAssignees()
->filter(function (WorkflowAssignee $assignee) use ($user) {
return $assignee->isUser($user);
})
->isEmpty()
;
}
/**
* @param $key
* @param User $user
* @throws Exception
*/
public function addAssignee($key, User $user)
{
$this->getCurrentState()->addAssignee(WorkflowAssignee::create($user, $key));
}
public function removeAssigneeAsRole(User $user, $role)
{
foreach ($this->getAssignees() as $assignee) {
if ($assignee->isType($role) && $assignee->isUser($user)) {
$this->getAssignees()->removeElement($assignee);
}
}
}
/**
* @param $key
* @throws Exception
*/
public function removeAssigneeByKey($key)
{
$this->getCurrentState()->removeAssigneeByKey($key);
}
/**
* @return ArrayCollection|WorkflowAssignee[]
* @throws Exception
*/
public function getAssignees()
{
return $this->getCurrentState()->getAssignees();
}
public function getOldAssigneesArray() {
$assignees = [];
$states = $this->getStates()->filter(function(WorkflowState $state) {
return !$state->isCurrent();
});
/** @var WorkflowState $state */
foreach ($states as $state) {
foreach ($state->getAssignees() as $assignee) {
$assignees[] = [
'username' => $assignee->getUser()->getUserIdentifier(),
'user' => $assignee->getUser()->getId(),
'role' => $assignee->getName()
];
}
}
return array_unique($assignees, SORT_REGULAR);
}
public function getCurrentAssigneesArray() {
$assignees = [];
$currentAssignees = $this->getCurrentState()->getAssignees();
foreach($currentAssignees as $assignee) {
$assignees[] = [
'username' => $assignee->getUser()->getUserIdentifier(),
'user' => $assignee->getUser()->getId(),
'role' => $assignee->getName()
];
}
return $assignees;
}
public function getDiffCurrentAssignees() {
$currentAssignees = $this->getCurrentAssigneesArray();
$oldAssignees = $this->getOldAssigneesArray();
foreach ($currentAssignees as $key => $a) {
foreach($oldAssignees as $b) {
if($a['role'] == $b['role'] && $a['user'] == $b['user']) {
unset($currentAssignees[$key]);
}
}
}
return $currentAssignees;
}
/**
* @return array
* @throws Exception
*/
public function getRolesMap()
{
$rolesMap = [];
/** @var WorkflowAssignee $assignee */
foreach ($this->getAssignees() as $assignee) {
$role = $assignee->getName();
$user = $assignee->getUser();
if (!isset($rolesMap[$role])) {
$rolesMap[$role] = [];
}
$rolesMap[$role][] = $user;
}
return array_map(function ($i) {
return count($i) === 1 ? $i[0] : $i;
}, $rolesMap);
}
/**
* @param DateTime $timeout
* @throws Exception
*/
public function setTimeout(DateTime $timeout)
{
$this->getCurrentState()->setDeadline($timeout);
}
/**
* @return string
*/
public function getSubWorkflow()
{
return $this->subWorkflow;
}
/**
* @param string $subWorkflow
*/
public function setSubWorkflow($subWorkflow)
{
$this->subWorkflow = $subWorkflow;
}
}