<?php
declare(strict_types=1);
namespace App\Order\Security\Voter;
use App\Program\Doctrine\Entity\Program;
use App\Order\Doctrine\Entity\Order;
use App\Security\Doctrine\Entity\User;
use App\Security\Security\Store\Roles;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Workflow\WorkflowInterface;
final class OrderVoter extends Voter
{
public const CANCEL = 'cancel';
public const PREPARE = 'prepare';
public const PROCESS = 'process';
public const SHIP = 'ship';
public const DELIVER = 'deliver';
public const ITEM = 'item';
public function __construct(
private WorkflowInterface $orderStateMachine,
private AccessDecisionManagerInterface $accessDecisionManager
) {
}
protected function supports(string $attribute, mixed $subject): bool
{
return $subject instanceof Order
&& in_array($attribute, [self::ITEM, self::CANCEL, self::PREPARE, self::PROCESS, self::SHIP, self::DELIVER], true);
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
/** @var Order $order */
$order = $subject;
if (($program = $token->getUser()) instanceof Program) {
return $attribute === self::ITEM && $order->getAccount()->getProfile()->getProgram() === $program;
}
if (!($user = $token->getUser()) instanceof User) {
return false;
}
return match ($attribute) {
self::DELIVER => $this->accessDecisionManager->decide($token, [Roles::ORDER_PROCESS])
&& $this->orderStateMachine->getMarking($order)->has('prepared')
&& $order->getDetailsToDeliver()->count() > 0,
self::SHIP => $this->accessDecisionManager->decide($token, [Roles::ORDER_PROCESS])
&& $this->orderStateMachine->getMarking($order)->has('prepared')
&& $order->getDetailsToShip()->count() > 0,
self::PROCESS => $this->accessDecisionManager->decide($token, [Roles::ORDER_PROCESS])
&& $this->orderStateMachine->getMarking($order)->has('prepared')
&& $order->getDetailsToProcess()->count() > 0,
self::PREPARE => $this->orderStateMachine->can($order, self::PREPARE)
&& $this->accessDecisionManager->decide($token, [Roles::ORDER_PREPARE]),
self::CANCEL => !$this->orderStateMachine->getMarking($order)->has('pending')
&& $this->accessDecisionManager->decide($token, [Roles::ORDER_CANCEL]),
default => false
};
}
}