Skip to content

Commit

Permalink
code challenge 7 solution
Browse files Browse the repository at this point in the history
  • Loading branch information
jschaedl committed Sep 28, 2023
1 parent 72e1ed3 commit c5b6b56
Show file tree
Hide file tree
Showing 12 changed files with 333 additions and 11 deletions.
13 changes: 13 additions & 0 deletions CODING-CHALLENGE-8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# RESTful Webservices in Symfony

## Coding Challenge 8 - Error Handling

### Tasks

- centralize the error handling

### Solution

- throw a `ValidationFailedException` on validation errors
- introduce an `ExceptionListener` to catch the `ValidationFailedException`
- in the `ExceptionListener` create an error representation of the caught exception and fill the response content with it
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"symfony/property-info": "6.3.*",
"symfony/runtime": "6.3.*",
"symfony/serializer": "6.3.*",
"symfony/validator": "6.3.*",
"symfony/yaml": "6.3.*",
"webmozart/assert": "^1.11",
"willdurand/negotiation": "^3.1"
Expand Down
176 changes: 175 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions config/packages/validator.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
framework:
validation:
email_validation_mode: html5

# Enables validator auto-mapping support.
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
#auto_mapping:
# App\Entity\: []

when@test:
framework:
validation:
not_compromised_password: false
8 changes: 8 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@ services:

# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones



when@test:
services:
Psr\Log\NullLogger: ~
# Disable logger to avoid showing errors during tests
logger: '@Psr\Log\NullLogger'
26 changes: 21 additions & 5 deletions src/ArgumentValueResolver/CreateAttendeeModelResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;

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

Expand All @@ -29,10 +32,23 @@ 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(),
);
try {
$model = $this->serializer->deserialize(
$request->getContent(),
CreateAttendeeModel::class,
$request->getRequestFormat(),
);
} catch (\Exception $exception) {
throw new UnprocessableEntityHttpException();
}

$validationErrors = $this->validator->validate($model);

if (\count($validationErrors) > 0) {
// throw a UnprocessableEntityHttpException for now, we will introduce proper ApiExceptions later
throw new UnprocessableEntityHttpException();
}

yield $model;
}
}
26 changes: 21 additions & 5 deletions src/ArgumentValueResolver/UpdateAttendeeModelResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;

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

Expand All @@ -27,10 +30,23 @@ public function supports(Request $request, ArgumentMetadata $argument): bool
*/
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
yield $this->serializer->deserialize(
$request->getContent(),
UpdateAttendeeModel::class,
$request->getRequestFormat(),
);
try {
$model = $this->serializer->deserialize(
$request->getContent(),
UpdateAttendeeModel::class,
$request->getRequestFormat(),
);
} catch (\Exception $exception) {
throw new UnprocessableEntityHttpException();
}

$validationErrors = $this->validator->validate($model);

if (\count($validationErrors) > 0) {
// throw a UnprocessableEntityHttpException for now, we will introduce proper ApiExceptions later
throw new UnprocessableEntityHttpException();
}

yield $model;
}
}
9 changes: 9 additions & 0 deletions src/Domain/Model/CreateAttendeeModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@

namespace App\Domain\Model;

use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\NotBlank;

final class CreateAttendeeModel
{
#[NotBlank]
public string $firstname;

#[NotBlank]
public string $lastname;

#[NotBlank]
#[Email]
public string $email;
}
9 changes: 9 additions & 0 deletions src/Domain/Model/UpdateAttendeeModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@

namespace App\Domain\Model;

use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\NotBlank;

final class UpdateAttendeeModel
{
#[NotBlank(allowNull: true)]
public ?string $firstname = null;

#[NotBlank(allowNull: true)]
public ?string $lastname = null;

#[NotBlank(allowNull: true)]
#[Email]
public ?string $email = null;
}
12 changes: 12 additions & 0 deletions symfony.lock
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,18 @@
"config/routes.yaml"
]
},
"symfony/validator": {
"version": "6.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.3",
"ref": "c32cfd98f714894c4f128bb99aa2530c1227603c"
},
"files": [
"config/packages/validator.yaml"
]
},
"theofidry/alice-data-fixtures": {
"version": "1.6",
"recipe": {
Expand Down
Loading

0 comments on commit c5b6b56

Please sign in to comment.