Skip to content

Commit

Permalink
code challenge 6 solution
Browse files Browse the repository at this point in the history
  • Loading branch information
jschaedl committed Sep 28, 2023
1 parent fa6274a commit 72e1ed3
Show file tree
Hide file tree
Showing 13 changed files with 324 additions and 0 deletions.
38 changes: 38 additions & 0 deletions src/ArgumentValueResolver/CreateAttendeeModelResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace App\ArgumentValueResolver;

use App\Domain\Model\CreateAttendeeModel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\Serializer\SerializerInterface;

final class CreateAttendeeModelResolver implements ArgumentValueResolverInterface
{
public function __construct(
private readonly SerializerInterface $serializer,
) {
}

public function supports(Request $request, ArgumentMetadata $argument): bool
{
return CreateAttendeeModel::class === $argument->getType() && 'POST' === $request->getMethod();
}

/**
* @return iterable<CreateAttendeeModel>
*/
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
// it returns an iterable, because the controller method argument could be variadic

yield $this->serializer->deserialize(
$request->getContent(),
CreateAttendeeModel::class,
$request->getRequestFormat(),
);
}
}
36 changes: 36 additions & 0 deletions src/ArgumentValueResolver/UpdateAttendeeModelResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace App\ArgumentValueResolver;

use App\Domain\Model\UpdateAttendeeModel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\Serializer\SerializerInterface;

final class UpdateAttendeeModelResolver implements ArgumentValueResolverInterface
{
public function __construct(
private readonly SerializerInterface $serializer,
) {
}

public function supports(Request $request, ArgumentMetadata $argument): bool
{
return UpdateAttendeeModel::class === $argument->getType() && 'PUT' === $request->getMethod();
}

/**
* @return iterable<UpdateAttendeeModel>
*/
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
yield $this->serializer->deserialize(
$request->getContent(),
UpdateAttendeeModel::class,
$request->getRequestFormat(),
);
}
}
37 changes: 37 additions & 0 deletions src/Controller/Attendee/CreateController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace App\Controller\Attendee;

use App\Domain\AttendeeCreator;
use App\Domain\Model\CreateAttendeeModel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Serializer\SerializerInterface;

#[Route('/attendees', name: 'create_attendee', methods: ['POST'])]
final class CreateController
{
public function __construct(
private AttendeeCreator $attendeeCreator,
private SerializerInterface $serializer,
private UrlGeneratorInterface $urlGenerator,
) {
}

public function __invoke(Request $request, CreateAttendeeModel $createAttendeeModel)
{
$createdAttendee = $this->attendeeCreator->create($createAttendeeModel);

$serializedCreatedAttendee = $this->serializer->serialize($createdAttendee, $request->getRequestFormat());

return new Response($serializedCreatedAttendee, Response::HTTP_CREATED, [
'Location' => $this->urlGenerator->generate('read_attendee', [
'identifier' => $createdAttendee->getIdentifier(),
], UrlGeneratorInterface::ABSOLUTE_URL),
]);
}
}
28 changes: 28 additions & 0 deletions src/Controller/Attendee/UpdateController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace App\Controller\Attendee;

use App\Domain\AttendeeUpdater;
use App\Domain\Model\UpdateAttendeeModel;
use App\Entity\Attendee;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

#[Route('/attendees/{identifier}', name: 'update_attendee', methods: ['PUT'])]
final class UpdateController
{
public function __construct(
private AttendeeUpdater $attendeeUpdater,
) {
}

public function __invoke(Request $request, Attendee $attendee, UpdateAttendeeModel $updateAttendeeModel)
{
$this->attendeeUpdater->update($attendee, $updateAttendeeModel);

return new Response(null, Response::HTTP_NO_CONTENT);
}
}
Empty file removed src/Domain/.gitignore
Empty file.
32 changes: 32 additions & 0 deletions src/Domain/AttendeeCreator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace App\Domain;

use App\Domain\Model\CreateAttendeeModel;
use App\Entity\Attendee;
use App\Repository\AttendeeRepository;
use Ramsey\Uuid\Uuid;

final class AttendeeCreator
{
public function __construct(
private readonly AttendeeRepository $attendeeRepository,
) {
}

public function create(CreateAttendeeModel $createAttendeeModel): Attendee
{
$newAttendee = new Attendee(
Uuid::uuid4()->toString(),
$createAttendeeModel->firstname,
$createAttendeeModel->lastname,
$createAttendeeModel->email,
);

$this->attendeeRepository->save($newAttendee);

return $newAttendee;
}
}
36 changes: 36 additions & 0 deletions src/Domain/AttendeeUpdater.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace App\Domain;

use App\Domain\Model\UpdateAttendeeModel;
use App\Entity\Attendee;
use App\Repository\AttendeeRepository;

final class AttendeeUpdater
{
public function __construct(
private readonly AttendeeRepository $attendeeRepository,
) {
}

public function update(Attendee $attendee, UpdateAttendeeModel $createAttendeeModel): Attendee
{
if ($firstname = $createAttendeeModel->firstname) {
$attendee->changeFirstname($firstname);
}

if ($lastname = $createAttendeeModel->lastname) {
$attendee->changeLastname($lastname);
}

if ($email = $createAttendeeModel->email) {
$attendee->changeEmail($email);
}

$this->attendeeRepository->save($attendee);

return $attendee;
}
}
12 changes: 12 additions & 0 deletions src/Domain/Model/CreateAttendeeModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Domain\Model;

final class CreateAttendeeModel
{
public string $firstname;
public string $lastname;
public string $email;
}
12 changes: 12 additions & 0 deletions src/Domain/Model/UpdateAttendeeModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Domain\Model;

final class UpdateAttendeeModel
{
public ?string $firstname = null;
public ?string $lastname = null;
public ?string $email = null;
}
6 changes: 6 additions & 0 deletions src/Repository/AttendeeRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,10 @@ public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Attendee::class);
}

public function save(Attendee $attendee): void
{
$this->getEntityManager()->persist($attendee);
$this->getEntityManager()->flush();
}
}
41 changes: 41 additions & 0 deletions tests/Controller/Attendee/CreateControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace App\Tests\Controller\Attendee;

use App\Entity\Attendee;
use App\Repository\AttendeeRepository;
use App\Tests\ApiTestCase;

class CreateControllerTest extends ApiTestCase
{
public function test_it_should_create_an_attendee(): void
{
$attendeesBefore = static::getContainer()->get(AttendeeRepository::class)->findAll();
static::assertCount(0, $attendeesBefore);

$this->browser->request('POST', '/attendees', [], [], [],
<<<'EOT'
{
"firstname": "Paul",
"lastname": "Paulsen",
"email": "[email protected]"
}
EOT
);

static::assertResponseStatusCodeSame(201);
static::assertResponseHasHeader('Content-Type');
static::assertResponseHasHeader('Location');

$attendeesAfter = static::getContainer()->get(AttendeeRepository::class)->findAll();
static::assertCount(1, $attendeesAfter);

/** @var Attendee $expectedAttendee */
$expectedAttendee = $attendeesAfter[0];
static::assertSame('Paul', $expectedAttendee->getFirstname());
static::assertSame('Paulsen', $expectedAttendee->getLastname());
static::assertSame('[email protected]', $expectedAttendee->getEmail());
}
}
43 changes: 43 additions & 0 deletions tests/Controller/Attendee/UpdateControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace App\Tests\Controller\Attendee;

use App\Entity\Attendee;
use App\Repository\AttendeeRepository;
use App\Tests\ApiTestCase;

class UpdateControllerTest extends ApiTestCase
{
public function test_it_should_update_an_attendee(): void
{
$this->loadFixtures([
__DIR__.'/fixtures/update_attendee.yaml',
]);

$attendeesBefore = static::getContainer()->get(AttendeeRepository::class)->findAll();
static::assertCount(1, $attendeesBefore);

$this->browser->request('PUT', '/attendees/b38aa3e4-f9de-4dca-aaeb-3ec36a9feb6c', [], [], [],
<<<'EOT'
{
"firstname": "Paul",
"lastname": "Paulsen",
"email": "[email protected]"
}
EOT
);

static::assertResponseStatusCodeSame(204);

$attendeesAfter = static::getContainer()->get(AttendeeRepository::class)->findAll();
static::assertCount(1, $attendeesAfter);

/** @var Attendee $expectedAttendee */
$expectedAttendee = $attendeesAfter[0];
static::assertSame('Paul', $expectedAttendee->getFirstname());
static::assertSame('Paulsen', $expectedAttendee->getLastname());
static::assertSame('[email protected]', $expectedAttendee->getEmail());
}
}
3 changes: 3 additions & 0 deletions tests/Controller/Attendee/fixtures/update_attendee.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
App\Entity\Attendee:
attendee_1:
__construct: [ 'b38aa3e4-f9de-4dca-aaeb-3ec36a9feb6c', 'Jan', 'Schädlich', '[email protected]' ]

0 comments on commit 72e1ed3

Please sign in to comment.