Lines 96.77% 30 / 31
Methods 80.00% 4 / 5
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 process 94.44% 17 / 18 0.00% 0 / 1 5.00
 saveParts 100.00% 6 / 6 100.00% 1 / 1 6
 saveBannergress 100.00% 3 / 3 100.00% 1 / 1 1
 sentFields 100.00% 3 / 3 100.00% 1 / 1 3
28final readonly class MeProcessor implements ProcessorInterface
29{
30    public function __construct(
31        private EntityManagerInterface $entityManager,
32        private UserRepository $userRepository,
33        private Security $security,
34        private DoneBannerSync $doneBannerSync,
35    ) {
36    }
37
38    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): Me
39    {
40        $user = $this->security->getUser();
41        if (!$user instanceof User) {
42            throw new \LogicException('The operation requires ROLE_USER.');
43        }
44
45        if (null !== $data->agentName) {
46            $agentName = \trim($data->agentName);
47
48            $owner = $this->userRepository->findOneByAgentName($agentName);
49            if ($owner instanceof User && $owner !== $user) {
50                throw new ValidationException(new ConstraintViolationList([new ConstraintViolation(User::AGENT_NAME_TAKEN, null, [], $data, 'agentName', $agentName)]));
51            }
52
53            $user->setAgentName($agentName);
54            $data->agentName = $agentName;
55        }
56
57        $this->saveParts($data, $user, $this->sentFields($context));
58
59        $this->entityManager->flush();
60
61        // What is stored, also for fields sent as null
62        $data->agentName = $user->getAgentName();
63        $data->customMedals = CustomMedals::fromArray($user->getCustomMedals());
64        $data->visibleParts = $user->getVisibleParts();
65        $data->visibilityChosen = $user->getVisibilityChosenAt() instanceof \DateTimeImmutable;
66        $data->bannergress = Bannergress::fromStored($user->getBannergress());
67
68        return $data;
69    }
70
71    /**
72     * The custom medals, the Bannergress sync and the visible parts, those sent. A merge patch
73     * starts from the stored user, so the sync and the parts are only saved when [$sent] has them:
74     * else a custom medals save would count as choosing the parts and bump the sync time.
75     *
76     * @param list<string> $sent
77     */
78    private function saveParts(Me $data, User $user, array $sent): void
79    {
80        if ($data->customMedals instanceof CustomMedals) {
81            $user->setCustomMedals($data->customMedals->toArray());
82        }
83
84        if ($data->bannergress instanceof Bannergress && \in_array('bannergress', $sent, true)) {
85            $this->saveBannergress($data->bannergress, $user);
86        }
87
88        if (null !== $data->visibleParts && \in_array('visibleParts', $sent, true)) {
89            $user->chooseVisibleParts($data->visibleParts, new \DateTimeImmutable());
90        }
91    }
92
93    /** The counts and the to-do banners on the user, the done list as DoneBanner. */
94    private function saveBannergress(Bannergress $bannergress, User $user): void
95    {
96        $bannergress->syncedAt = new \DateTimeImmutable()->format(\DATE_ATOM);
97        $user->setBannergress($bannergress->toArray());
98        $this->doneBannerSync->replace($user, $bannergress->doneBanners);
99    }
100
101    /**
102     * The fields of the request body.
103     *
104     * @param array<string, mixed> $context
105     *
106     * @return list<string>
107     */
108    private function sentFields(array $context): array
109    {
110        $request = $context['request'] ?? null;
111        $body = $request instanceof Request ? \json_decode($request->getContent(), true) : null;
112
113        return \is_array($body) ? \array_map(strval(...), \array_keys($body)) : [];
114    }
115}