Skip to content

Commit

Permalink
Add symfony validator
Browse files Browse the repository at this point in the history
  • Loading branch information
odan committed Jun 23, 2022
1 parent 922ce3d commit 4084624
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 151 deletions.
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
"nyholm/psr7-server": "^1.0",
"php-di/php-di": "^6",
"selective/basepath": "^2",
"selective/validation": "^2",
"slim/php-view": "^3.0",
"slim/slim": "^4",
"symfony/cache": "6.0.*",
"symfony/uid": "6.0.*",
"symfony/yaml": "6.0.*",
"symfony/cache": "^6",
"symfony/uid": "^6",
"symfony/validator": "^6",
"symfony/yaml": "^6",
"tuupola/slim-basic-auth": "^3.3"
},
"require-dev": {
Expand Down
2 changes: 1 addition & 1 deletion config/middleware.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

use App\Middleware\ValidationExceptionMiddleware;
use Selective\BasePath\BasePathMiddleware;
use Selective\Validation\Middleware\ValidationExceptionMiddleware;
use Slim\App;
use Slim\Middleware\ErrorMiddleware;

Expand Down
96 changes: 58 additions & 38 deletions src/Domain/Customer/Service/CustomerValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,67 +3,87 @@
namespace App\Domain\Customer\Service;

use App\Domain\Customer\Repository\CustomerRepository;
use App\Support\Validation;
use Cake\Validation\Validator;
use Selective\Validation\Exception\ValidationException;
use DomainException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Exception\ValidationFailedException;
use Symfony\Component\Validator\Validation;

final class CustomerValidator
{
private CustomerRepository $repository;

private Validation $validation;

public function __construct(CustomerRepository $repository, Validation $validation)
public function __construct(CustomerRepository $repository)
{
$this->repository = $repository;
$this->validation = $validation;
}

public function validateCustomerUpdate(int $customerId, array $data): void
{
if (!$this->repository->existsCustomerId($customerId)) {
throw new ValidationException(sprintf('Customer not found: %s', $customerId));
throw new DomainException(sprintf('Customer not found: %s', $customerId));
}

$this->validateCustomer($data);
}

public function validateCustomer(array $data): void
{
$validator = $this->createValidator();
$validationResult = $this->validation->validate($validator, $data);
$validator = Validation::createValidator();
$violations = $validator->validate($data, $this->createConstraints());

if ($validationResult->fails()) {
throw new ValidationException('Please check your input', $validationResult);
if ($violations->count()) {
throw new ValidationFailedException('Please check your input', $violations);
}
}

private function createValidator(): Validator
private function createConstraints(): Constraint
{
$validator = $this->validation->createValidator();

return $validator
->requirePresence('number', 'Input required')
->notEmptyString('number', 'Input required')
->maxLength('number', 10, 'Too long')
->numeric('number', 'Invalid number')
->requirePresence('name', 'Input required')
->notEmptyString('name', 'Input required')
->maxLength('name', 255, 'Too long')
->requirePresence('street', 'Input required')
->notEmptyString('street', 'Input required')
->maxLength('street', 255, 'Too long')
->requirePresence('postal_code', 'Input required')
->notEmptyString('postal_code', 'Input required')
->maxLength('postal_code', 10, 'Too long')
->requirePresence('city', 'Input required')
->notEmptyString('city', 'Input required')
->maxLength('city', 255, 'Too long')
->requirePresence('country', 'Input required')
->notEmptyString('country', 'Input required')
->minLength('country', 2, 'Too short')
->maxLength('country', 2, 'Too long')
->requirePresence('email', 'Input required')
->email('email', false, 'Input required');
return new Assert\Collection(
[
'number' => new Assert\Required(
[
new Assert\NotBlank(),
new Assert\Length(['min' => 0, 'max' => 10]),
new Assert\Positive(),
]
),
'name' => new Assert\Required(
[
new Assert\NotBlank(),
new Assert\Length(['max' => 255]),
]
),
'street' => new Assert\Required(
[
new Assert\NotBlank(),
new Assert\Length(['max' => 255]),
]
),
'postal_code' => new Assert\Required(
[
new Assert\NotBlank(),
new Assert\Length(['max' => 10]),
]
),
'city' => new Assert\Required(
[
new Assert\NotBlank(),
new Assert\Length(['max' => 255]),
]
),
'country' => new Assert\Required(
[
new Assert\NotBlank(),
new Assert\Length(['min' => 2, 'max' => 2]),
]
),
'email' => new Assert\Required(
[
new Assert\Email(),
]
),
]
);
}
}
65 changes: 65 additions & 0 deletions src/Middleware/ValidationExceptionMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace App\Middleware;

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\Exception\ValidationFailedException;

final class ValidationExceptionMiddleware implements MiddlewareInterface
{
private ResponseFactoryInterface $responseFactory;

public function __construct(ResponseFactoryInterface $responseFactory)
{
$this->responseFactory = $responseFactory;
}

public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
try {
return $handler->handle($request);
} catch (ValidationFailedException $exception) {
$response = $this->responseFactory->createResponse();
$data = $this->transform($exception);
$json = (string)json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$response->getBody()->write($json);

return $response
->withStatus(422)
->withHeader('Content-Type', 'application/json');
}
}

private function transform(ValidationFailedException $exception): array
{
$error = [];
$violations = $exception->getViolations();

if ($exception->getValue()) {
$error['message'] = $exception->getValue();
}

if ($violations->count()) {
$details = [];

/** @var ConstraintViolation $violation */
foreach ($violations as $violation) {
$details[] = [
'message' => $violation->getMessage(),
'field' => $violation->getPropertyPath(),
];
}

$error['details'] = $details;
}

return ['error' => $error];
}
}
36 changes: 0 additions & 36 deletions src/Support/Validation.php

This file was deleted.

File renamed without changes.
78 changes: 42 additions & 36 deletions tests/TestCase/Action/Customer/CustomerCreatorActionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,44 +95,50 @@ public function testCreateCustomerValidation(): void
// Check response
$this->assertSame(StatusCodeInterface::STATUS_UNPROCESSABLE_ENTITY, $response->getStatusCode());
$this->assertJsonContentType($response);
$this->assertJsonData(
[
'error' => [
'message' => 'Please check your input',
'code' => 422,
'details' => [
[
'message' => 'Input required',
'field' => 'number',
],
[
'message' => 'Input required',
'field' => 'name',
],
[
'message' => 'Input required',
'field' => 'street',
],
[
'message' => 'Input required',
'field' => 'postal_code',
],
[
'message' => 'Input required',
'field' => 'city',
],
[
'message' => 'Input required',
'field' => 'country',
],
[
'message' => 'Input required',
'field' => 'email',
],

$expected = [
'error' => [
'message' => 'Please check your input',
'details' => [
[
'message' => 'This value should not be blank.',
'field' => '[number]',
],
[
'message' => 'This value should be positive.',
'field' => '[number]',
],
[
'message' => 'This value should not be blank.',
'field' => '[name]',
],
[
'message' => 'This value should not be blank.',
'field' => '[street]',
],
[
'message' => 'This value should not be blank.',
'field' => '[postal_code]',
],
[
'message' => 'This value should not be blank.',
'field' => '[city]',
],
[
'message' => 'This value should not be blank.',
'field' => '[country]',
],
[
'message' => 'This value should have exactly 2 characters.',
'field' => '[country]',
],
[
'message' => 'This value is not a valid email address.',
'field' => '[email]',
],
],
],
$response
);
];
$this->assertJsonData($expected, $response);
}
}

0 comments on commit 4084624

Please sign in to comment.