Lines 82.60% 19 / 23
Functions and Methods 66.66% 2 / 3
Classes and Traits 0.00% 0 / 1
Name Lines Functions and Methods CRAP Classes and Traits
AgentStatService 82.60% 19 / 23 66.66% 2 / 3 11.64 0.00% 0 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 create 63.63% 7 / 11 0.00% 0 / 1 4.77
 checkAgentName 100.00% 11 / 11 100.00% 1 / 1 6
1<?php
2
3declare(strict_types=1);
4
5namespace App\Service;
6
7use App\Dto\AgentStatInput;
8use App\Entity\AgentStat;
9use App\Entity\User;
10use App\Exception\AgentNameException;
11use App\Exception\StatsAlreadyAddedException;
12use App\Repository\AgentStatRepository;
13use App\Repository\UserRepository;
14use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
15use Doctrine\ORM\EntityManagerInterface;
16
17class AgentStatService
18{
19    public function __construct(
20        private readonly EntityManagerInterface $entityManager,
21        private readonly AgentStatRepository $agentStatRepository,
22        private readonly UserRepository $userRepository,
23    ) {
24    }
25
26    /** See AgentStat's #[UniqueConstraint]. */
27    private const string OWNER_DATETIME_CONSTRAINT = 'agent_stat_owner_datetime';
28
29    /**
30     * @throws AgentNameException         if the stats are for another agent, or the nickname is no agent name or taken
31     * @throws StatsAlreadyAddedException if the owner already imported stats with this datetime
32     */
33    public function create(User $owner, AgentStatInput $input): AgentStat
34    {
35        $this->checkAgentName($owner, \trim($input->nickname));
36
37        $agentStat = new AgentStat($owner, $input);
38
39        if ($this->agentStatRepository->has($owner, $agentStat->datetime)) {
40            throw new StatsAlreadyAddedException('Stat entry already added!');
41        }
42
43        $this->entityManager->persist($agentStat);
44
45        try {
46            $this->entityManager->flush();
47        } catch (UniqueConstraintViolationException $uniqueConstraintViolationException) {
48            // Another user took the agent name at the same moment
49            if (!\str_contains($uniqueConstraintViolationException->getMessage(), self::OWNER_DATETIME_CONSTRAINT)) {
50                throw new AgentNameException(User::AGENT_NAME_TAKEN, $uniqueConstraintViolationException->getCode(), previous: $uniqueConstraintViolationException);
51            }
52
53            // Same stats sent twice at the same moment
54            throw new StatsAlreadyAddedException('Stat entry already added!', $uniqueConstraintViolationException->getCode(), previous: $uniqueConstraintViolationException);
55        }
56
57        return $agentStat;
58    }
59
60    /**
61     * Without an agent name, the stats' nickname becomes the owner's agent name, unless another
62     * user has it (ignoring case).
63     *
64     * @throws AgentNameException if the stats are for another agent, or the nickname is no agent name or taken
65     */
66    private function checkAgentName(User $owner, string $nickname): void
67    {
68        $agentName = $owner->getAgentName();
69        if (null !== $agentName) {
70            if (!$owner->isAgentName($nickname)) {
71                throw new AgentNameException(\sprintf('These stats are for agent %s, your agent name is %s.', $nickname, $agentName));
72            }
73
74            return;
75        }
76
77        if (1 !== \preg_match(User::AGENT_NAME_PATTERN, $nickname)) {
78            throw new AgentNameException(User::AGENT_NAME_INVALID);
79        }
80
81        $other = $this->userRepository->findOneByAgentName($nickname);
82        if ($other instanceof User && $other !== $owner) {
83            throw new AgentNameException(User::AGENT_NAME_TAKEN);
84        }
85
86        $owner->setAgentName($nickname);
87    }
88}