Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
OwnedExpenseFinder
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
4 / 4
7
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
 findReadable
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getForWrite
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 expenseOnTrip
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace App\Expense\Application;
6
7use App\Expense\Domain\Exceptions\ExpenseNotFound;
8use App\Expense\Domain\Expense;
9use App\Expense\Domain\ExpenseRepository;
10use App\Expense\Domain\ValueObject\ExpenseId;
11use App\Expense\Domain\ValueObject\TripId as ExpenseTripId;
12use App\Trip\Application\TripAccess;
13use App\Trip\Domain\ValueObject\TripId;
14
15/**
16 * Loads an expense scoped to the trip it belongs to AND the caller's access to
17 * that trip. Reads need any role; writes need owner/editor. A trip the caller
18 * can't see, or an expense not on it, is treated as absent (404); a viewer who
19 * tries to write is denied (403) — TripAccess enforces the role, this only adds
20 * the expense-belongs-to-trip check.
21 */
22final readonly class OwnedExpenseFinder
23{
24    public function __construct(
25        private ExpenseRepository $expenses,
26        private TripAccess $access,
27    ) {
28    }
29
30    public function findReadable(TripId $tripId, string $userId, ExpenseId $expenseId): ?Expense
31    {
32        if ($this->access->findReadable($tripId, $userId) === null) {
33            return null;
34        }
35
36        return $this->expenseOnTrip($tripId, $expenseId);
37    }
38
39    public function getForWrite(TripId $tripId, string $userId, ExpenseId $expenseId): Expense
40    {
41        // Owner/editor only: stranger → TripNotFound (404), viewer → TripAccessDenied (403).
42        $this->access->getForExpenseWrite($tripId, $userId);
43
44        return $this->expenseOnTrip($tripId, $expenseId)
45            ?? throw ExpenseNotFound::withId($expenseId);
46    }
47
48    private function expenseOnTrip(TripId $tripId, ExpenseId $expenseId): ?Expense
49    {
50        $expense = $this->expenses->ofId($expenseId);
51
52        return $expense !== null && $expense->belongsToTrip(new ExpenseTripId($tripId->value()))
53            ? $expense
54            : null;
55    }
56}