4 - Extend SubjectPresenter with aggregate fields + add show endpoint

This commit is contained in:
myrmidex 2026-06-05 23:06:52 +00:00
parent dc6c4a082d
commit 17331526aa
4 changed files with 75 additions and 1 deletions

View file

@ -54,6 +54,22 @@ class SubjectController extends AbstractController
return $this->json(null, 201);
}
#[Route('/api/subjects/{id}', methods: ['GET'])]
public function show(
int $id,
SubjectRepository $repository,
SubjectPresenter $presenter,
OutputService $output,
): JsonResponse {
$subject = $repository->find($id);
if ($subject === null) {
return $this->json($output->error(['UNKNOWN_SUBJECT']), 404);
}
return $this->json($output->success(['subject' => $presenter->present($subject)]));
}
#[Route('/api/subjects/{id}', methods: ['PATCH'])]
public function update(
int $id,

View file

@ -32,6 +32,8 @@ final class RatingFactory extends PersistentObjectFactory
protected function defaults(): array|callable
{
return [
'score' => self::faker()->numberBetween(1, 5),
'note' => self::faker()->optional()->sentence() ?? '',
'subject' => RestaurantFactory::new(),
'user' => UserFactory::new(),
];

View file

@ -15,6 +15,10 @@ class SubjectPresenter
*/
public function present(Subject $subject): array
{
$ratings = $subject->getRatings()->toArray();
usort($ratings, fn($a, $b) => $b->getCreatedAt() <=> $a->getCreatedAt());
return [
'id' => $subject->getId(),
'name' => $subject->getName(),
@ -24,13 +28,32 @@ class SubjectPresenter
'id' => $subject->getCreatedBy()->getId(),
'email' => $subject->getCreatedBy()->getEmail(),
],
'ratingCount' => count($ratings),
'averageScore' => $this->computeAverage($ratings),
'ratings' => array_map(
fn($r) => $this->ratingPresenter->present($r),
$subject->getRatings()->toArray()
$ratings
),
];
}
/**
* @param array<\App\Entity\Rating> $ratings
*/
private function computeAverage(array $ratings): ?float
{
$scores = array_filter(
array_map(fn($r) => $r->getScore(), $ratings),
fn($s) => $s !== null
);
if (count($scores) === 0) {
return null;
}
return round(array_sum($scores) / count($scores), 1);
}
/**
* @param iterable<Subject> $subjects
* @return array<int, array<string, mixed>>

View file

@ -4,6 +4,7 @@ namespace App\Tests\Application\Controller\Api;
use App\Entity\Restaurant;
use App\Entity\Subject;
use App\Factory\RatingFactory;
use App\Factory\RestaurantFactory;
use App\Factory\UserFactory;
use App\Repository\SubjectRepository;
@ -39,6 +40,38 @@ class SubjectControllerTest extends WebTestCase
$this->assertNotNull($subject['longitude']);
}
public function testShow(): void
{
$client = static::createClient();
$user = UserFactory::createOne();
$client->loginUser($user);
$subject = RestaurantFactory::createOne(['createdBy' => $user]);
RatingFactory::createOne(['subject' => $subject, 'score' => 4, 'user' => $user]);
RatingFactory::createOne(['subject' => $subject, 'score' => 2, 'user' => UserFactory::createOne()]);
$client->request('GET', '/api/subjects/' . $subject->getId(), server: ['CONTENT_TYPE' => 'application/json']);
$data = json_decode($client->getResponse()->getContent(), true);
$this->assertResponseIsSuccessful();
$this->assertSame($subject->getId(), $data['payload']['subject']['id']);
$this->assertSame($subject->getName(), $data['payload']['subject']['name']);
$this->assertCount(2, $data['payload']['subject']['ratings']);
$this->assertSame(2, $data['payload']['subject']['ratingCount']);
$this->assertEquals(3.0, $data['payload']['subject']['averageScore']);
$rating = $data['payload']['subject']['ratings'][0];
$this->assertArrayHasKey('id', $rating);
$this->assertArrayHasKey('score', $rating);
$this->assertArrayHasKey('note', $rating);
$this->assertArrayHasKey('createdAt', $rating);
$this->assertArrayHasKey('user', $rating);
$this->assertArrayHasKey('id', $rating['user']);
}
public function testCreate(): void
{
$client = static::createClient();