Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
RemoveMemberHandler
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
2 / 2
6
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%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3declare(strict_types=1);
4
5namespace App\Trip\Application\Command;
6
7use App\Trip\Application\TripAccess;
8use App\Trip\Domain\Exceptions\MemberNotFound;
9use App\Trip\Domain\Exceptions\TripAccessDenied;
10use App\Trip\Domain\Exceptions\TripNotFound;
11use App\Trip\Domain\TripMemberRepository;
12use App\Trip\Domain\ValueObject\MemberId;
13use App\Trip\Domain\ValueObject\TripId;
14use App\Trip\Domain\ValueObject\TripRole;
15
16/**
17 * Owner removes any member; a member removes themselves (leave). Same endpoint,
18 * DELETE /trips/{tripId}/members/{userId}: owner passes any member's id, a member
19 * passes their own. The owner is not a member row, so "removing the owner" 404s.
20 */
21final readonly class RemoveMemberHandler
22{
23    public function __construct(
24        private TripAccess $access,
25        private TripMemberRepository $members,
26    ) {
27    }
28
29    public function handle(RemoveMemberCommand $command): void
30    {
31        $tripId = new TripId($command->tripId);
32        [$trip, $actingRole] = $this->access->readableWithRole($tripId, $command->actingUserId);
33        if ($trip === null || $actingRole === null) {
34            throw TripNotFound::withId($tripId); // stranger: no existence leak
35        }
36
37        $isOwner = $actingRole === TripRole::OWNER;
38        $isSelf = $command->actingUserId === $command->targetUserId;
39        if (!$isOwner && !$isSelf) {
40            throw TripAccessDenied::cannotManageTrip(); // a member may only remove themselves
41        }
42
43        $target = $this->members->find($tripId, new MemberId($command->targetUserId))
44            ?? throw MemberNotFound::withId(new MemberId($command->targetUserId));
45
46        $this->members->remove($target);
47    }
48}