This document discusses data validation models and different approaches to validation. It covers syntactic validation to check data type and format, as well as semantic validation to check if data makes sense. Various PHP libraries and frameworks are benchmarked for validation performance. Deferred validation using argument resolvers is recommended to ensure objects are always valid. Domain models should encapsulate semantic validation rules. Validation should occur as early as possible to catch errors quickly.
5. <?php
use SymfonyComponentValidatorConstraints as Assert;
class User
{
/**
* @AssertNotBlank
* @AssertString
* @AssertEmail
*/
public $email
/**
* @AssertNotBlank
* @AssertLength(min=“3”)
*/
public $username;
}
6. <?php
use AssertAssert;
class User
{
private string $username;
private string $email;
/* @throw InvalidArgumentException */
public function __construct(string $username, string $email)
{
Assert::stringNotEmpty($username);
Assert::minLength($username, 3);
Assert::stringNotEmpty($email);
Assert::email($email);
$this->username = $username;
$this->email = $email;
}
}
7. create table users
(
id bigint not null primary key auto_increment,
email varchar(100) not null,
username varchar(100) not null,
constraint email_unique unique (email),
constraint username_unique unique (username)
)
9. Optional section title
Syntactic
Checks if data has the proper
data type and format
2020-02-30
Invalid date
2030-02-03
Looks like a valid date!
Semantic
Checks if data makes sense in
our use case
2030-02-03
Seems to be invalid DoB
1903-01-02
??? ??? ??? ??? ??? ???
18. When should I be more careful about syntax
validation?
➔ Integrations with external APIs (for example oAuth)
➔ It is a core of our application
Optional section title
Usually it will be just an edge case
21. I got mczarnecki@example.com as input, but such
user already exists. Is it valid?
➔ Register user context
● Definitely NO!
Optional section title
22. I got mczarnecki@example.com as input, but such
user already exists. Is it valid?
➔ Register user context
● Definitely NO!
➔ Login user context
● Yes, it is valid
Optional section title
23. I got mczarnecki@example.com as input, but such
user already exists. Is it valid?
➔ Register user context
● Definitely NO!
➔ Login user context
● Yes, it is valid
➔ Search for friend
● No such validation rule.
Optional section title
24. I got mczarnecki@example.com as input, but such
user already exists. Is it valid?
➔ Register user context
● Definitely NO!
➔ Login user context
● Yes, it is valid
➔ Search for friend
● No such validation rule.
➔ Send order confirmation email
● Yes, it is required to exist in database
Optional section title
29. <?php
use SymfonyComponentValidatorConstraints as Assert;
class RegisterUserRequest {
/**
* @AssertNotBlank
* @AssertString
*/
public $email
/**
* @AssertNotBlank
* @AssertLength(min=“3”)
*/
public $username;
}
30. Make sure that you will
always operate on valid object
Use Argument Resolver and kernel.exception listener to handle
invalid requests
Optional section title
31. <?php
use SymfonyComponentValidatorConstraints as Assert;
class RegisterUserRequestResolver implements ArgumentValueResolverInterface
{
private SerializerInterface $serializer;
private ValidatorInterface $validator;
public function __construct(
SerializerInterface $serializer,
ValidatorInterface $validator
)
{
$this->serializer = $serializer;
$this->validator = $validator;
}
32. <?php
class RegisterUserRequestResolver implements ArgumentValueResolverInterface
{
public function supports(Request $request, ArgumentMetadata $argument)
{
return RegisterUserRequest::class === $argument->getType();
}
public function resolve(Request $request, ArgumentMetadata $argument)
{
$dto = $this->serializer->deserialize($request->getContent(),
RegisterUserRequest::class, 'json');
$errors = $this->validator->validate($dto);
if($errors->count()){
throw new Exception((string)$errors);
}
yield $dto;
}
}
33. <?php
class ExceptionListener
{
public function onKernelException(ExceptionEvent $event)
{
$exception = $event->getThrowable();
$response = new Response();
$response->setContent($exception->getMessage());
$response->setStatusCode(Response::HTTP_BAD_REQUEST);
$event->setResponse($response);
}
}
34. <?php
class UserController
{
public function index(RegisterUserRequest $request)
{
$this->usersService->createUser(
$request->email,
$request->username,
);
return new Response('', Response::HTTP_CREATED);
}
}
36. Domain model should be
always valid
To keep your code DRY most of the semantic validation should take
place in the application / domain layer.
Optional section title
37. <?php
class UserController
{
public function index(RegisterUserRequest $request)
{
$this->usersService->createUser(
$request->email,
$request->username,
);
return new Response('', Response::HTTP_CREATED);
}
}
38. <?php
class UserController
{
public function index(RegisterUserRequest $request)
{
$this->usersService->createUser(
new Username($request->username),
new Email($request->email),
);
return new Response('', Response::HTTP_CREATED);
}
}
49. Take away
➔Syntactic validation should be done as soon as possible
➔Semantic validation should be implemented in the
application / domain level
➔We should avoid of operating on incorrect objects
➔Always validate your input :)
Optional section title