Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
ChangeMyAvatarHandler
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
2 / 2
3
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 handle
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace App\Identity\Application\Command;
6
7use App\Identity\Application\AuthenticatedUserFinder;
8use App\Identity\Application\Query\CurrentUserView;
9use App\Identity\Domain\UserRepository;
10use App\Identity\Domain\ValueObject\UserId;
11use App\Shared\Infrastructure\Storage\ImageStorage;
12use App\Shared\Infrastructure\Storage\SignedImageUrl;
13
14/**
15 * ponytail: depends on the concrete ImageStorage rather than a port, for the same
16 * reason ChangeTripImageHandler does — it is a shared infrastructure utility with
17 * one implementation, and keeping the store/unlink pair here is what makes "one
18 * avatar per user, no orphans" true by construction.
19 */
20final readonly class ChangeMyAvatarHandler
21{
22    public function __construct(
23        private AuthenticatedUserFinder $finder,
24        private UserRepository $users,
25        private ImageStorage $storage,
26        private SignedImageUrl $imageUrls,
27    ) {
28    }
29
30    public function handle(ChangeMyAvatarCommand $command): CurrentUserView
31    {
32        // Self-scoped: the id comes from the JWT, so there is no ownership check
33        // to make beyond the user still existing.
34        $user = $this->finder->getOrFail(new UserId($command->userId));
35
36        $previous = $user->avatarName();
37        $user->changeAvatar($command->file === null ? null : $this->storage->store($command->file));
38        $this->users->save($user);
39
40        // Only once the new name is persisted, so a failure never leaves the user
41        // pointing at a file that is already gone.
42        $this->storage->delete($previous);
43
44        return CurrentUserView::fromUser($user, $this->imageUrls->for($user->avatarName()));
45    }
46}