Skip to content

Commit

Permalink
Added cakephp validator and started implementation [SLE-194]
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelgfeller committed Oct 19, 2023
1 parent deba0d5 commit d695fe3
Show file tree
Hide file tree
Showing 12 changed files with 402 additions and 68 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"fig/http-message-util": "^1.1",
"ext-gettext": "*",
"ext-intl": "*",
"php": "^8.1"
"php": "^8.1",
"cakephp/validation": "^5.0"
},
"autoload": {
"psr-4": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,6 @@ class NewPasswordResetSubmitAction
{
private LoggerInterface $logger;

/**
* The constructor.
*
* @param Responder $responder
* @param SessionInterface $session
* @param PasswordChanger $passwordChanger
* @param MalformedRequestBodyChecker $malformedRequestBodyChecker
* @param LoggerFactory $loggerFactory
*/
public function __construct(
private readonly Responder $responder,
private readonly SessionInterface $session,
Expand Down
72 changes: 35 additions & 37 deletions src/Application/Actions/Client/Ajax/ClientCreateAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,22 @@ public function __construct(
* @param ResponseInterface $response The response
* @param array $args
*
* @return ResponseInterface The response
* @throws \JsonException
*
* @return ResponseInterface The response
*/
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
if (($loggedInUserId = $this->session->get('user_id')) !== null) {
$clientValues = $request->getParsedBody();
$clientValues = $request->getParsedBody();

// If html form names change they have to be adapted in the data class attributes too (e.g. ClientData)
// Check that request body syntax is formatted right (tested in ClientCreateActionTest - malformedRequest)
if ($this->malformedRequestBodyChecker->requestBodyHasValidKeys($clientValues, [
// If html form names change they have to be adapted in the data class attributes too (e.g. ClientData)
// Check that request body syntax is formatted right (tested in ClientCreateActionTest - malformedRequest)
if ($this->malformedRequestBodyChecker->requestBodyHasValidKeys(
$clientValues,
[
'client_status_id',
'user_id',
'first_name',
Expand All @@ -66,39 +67,36 @@ public function __invoke(
'birthdate',
'email',
], // Html radio buttons and checkboxes are not sent over by the client if they are not set hence optional
['sex', 'client_message', 'vigilance_level'])) {
try {
$insertId = $this->clientCreator->createClient($clientValues);
} catch (ValidationException $exception) {
return $this->responder->respondWithJsonOnValidationError(
$exception->getValidationResult(),
$response
);
} catch (ForbiddenException $forbiddenException) {
return $this->responder->respondWithJson(
$response,
[
'status' => 'error',
'message' => 'Not allowed to create client.',
],
StatusCodeInterface::STATUS_FORBIDDEN
);
}

if (0 !== $insertId) {
return $this->responder->respondWithJson($response, ['status' => 'success', 'data' => null], 201);
}
$response = $this->responder->respondWithJson($response, [
'status' => 'warning',
'message' => 'Client not created',
]);
['sex', 'client_message', 'vigilance_level']
)) {
try {
$insertId = $this->clientCreator->createClient($clientValues);
} catch (ValidationException $exception) {
return $this->responder->respondWithJsonOnValidationError(
$exception->getValidationResult(),
$response
);
} catch (ForbiddenException $forbiddenException) {
return $this->responder->respondWithJson(
$response,
[
'status' => 'error',
'message' => 'Not allowed to create client.',
],
StatusCodeInterface::STATUS_FORBIDDEN
);
}

return $response->withAddedHeader('Warning', 'The post could not be created');
if (0 !== $insertId) {
return $this->responder->respondWithJson($response, ['status' => 'success', 'data' => null], 201);
}
throw new HttpBadRequestException($request, 'Request body malformed.');
}
$response = $this->responder->respondWithJson($response, [
'status' => 'warning',
'message' => 'Client not created',
]);

// Handled by AuthenticationMiddleware
return $response;
return $response->withAddedHeader('Warning', 'The post could not be created');
}
throw new HttpBadRequestException($request, 'Request body malformed.');
}
}
2 changes: 1 addition & 1 deletion src/Domain/Client/Service/ClientCreator.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
class ClientCreator
{
public function __construct(
private readonly ClientValidator $clientValidator,
private readonly ClientValidatorVanilla $clientValidator,
private readonly ClientCreatorRepository $clientCreatorRepository,
private readonly ClientAuthorizationChecker $clientAuthorizationChecker,
private readonly NoteCreator $noteCreator,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
class ClientCreatorFromClientSubmit
{
public function __construct(
private readonly ClientValidator $clientValidator,
private readonly ClientValidatorVanilla $clientValidator,
private readonly ClientCreatorRepository $clientCreatorRepository,
private readonly UserActivityManager $userActivityManager,
private readonly ClientStatusFinderRepository $clientStatusFinderRepository,
Expand Down
2 changes: 1 addition & 1 deletion src/Domain/Client/Service/ClientUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ClientUpdater

public function __construct(
private readonly ClientUpdaterRepository $clientUpdaterRepository,
private readonly ClientValidator $clientValidator,
private readonly ClientValidatorVanilla $clientValidator,
private readonly ClientFinder $clientFinder,
LoggerFactory $logger,
private readonly ClientAuthorizationChecker $clientAuthorizationChecker,
Expand Down
96 changes: 84 additions & 12 deletions src/Domain/Client/Service/ClientValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,98 @@
use App\Domain\Client\Enum\ClientVigilanceLevel;
use App\Domain\Factory\LoggerFactory;
use App\Domain\Validation\ValidationResult;
use App\Domain\Validation\Validator;
use App\Domain\Validation\ValidatorNative;
use App\Infrastructure\Client\ClientStatus\ClientStatusFinderRepository;
use Cake\Validation\Validator;

/**
* Client user input validator.
*/
class ClientValidator
{
/**
* PostValidator constructor.
*
* @param LoggerFactory $logger
* @param Validator $validator
*/
public function __construct(
LoggerFactory $logger,
private readonly Validator $validator,
private readonly ValidatorNative $validator,
private readonly ClientStatusFinderRepository $clientStatusFinderRepository,
) {
$logger->addFileHandler('error.log')->createLogger('post-validation');
}

public function validateClientCreation()
{
$validator = new Validator();

$validator
// Require presence indicates that the key is required in the request body
->requirePresence('first_name')
->minLength('first_name', 3, __('Minimum length is 3'))
->maxLength('first_name', 100, __('Maximum length is 100'))
->requirePresence('last_name')
->minLength('last_name', 3, __('Minimum length is 3'))
->maxLength('last_name', 100, __('Maximum length is 100'))
->requirePresence('email')
->email('email', false, __('Invalid e-mail'))
->requirePresence('birthdate')
->date('birthdate', ['ymd', 'mdy', 'dmy'], 'Invalid date value')
->add('birthdate', 'validateNotInFuture', [
'rule' => function ($value, $context) {
$today = new \DateTime();
$birthdate = new \DateTime($value);

return $birthdate <= $today;
},
'message' => __('Cannot be in the future')
])
->add('birthdate', 'validateOldestAge', [
'rule' => function ($value, $context) {
$birthdate = new \DateTime($value);
// check that birthdate is not older than 130 years
$oldestBirthdate = new \DateTime('-130 years');
return $birthdate >= $oldestBirthdate;
},
'message' => __('Cannot be older than 130 years')
])
->requirePresence('location')
->minLength('location', 2, __('Minimum length is 2'))
->maxLength('location', 100, __('Maximum length is 100'))
->requirePresence('phone')
->minLength('phone', 3, __('Minimum length is 3'))
->maxLength('phone', 20, __('Maximum length is 20'))
// Sex should not have requirePresence as it's not required and the client won't send the key over if not set
->inList('sex', ['M', 'F', 'O', ''], 'Invalid option')
// This is too complex and will have to be changed in the future. Enums are not ideal in general.
// No requirePresence for vigilance_level
->add('vigilance_level', 'validateBackedEnum', [
'rule' => function ($value, $context) {
return $this->isBackedEnum($value, ClientVigilanceLevel::class, 'vigilance_level');
},
'message' => __('Invalid option')
])
->requirePresence('client_message')
->minLength('client_message', 3, __('Minimum length is 3'))
->maxLength('client_message', 1000, __('Maximum length is 1000'))
->requirePresence('client_status_id', true, __('Required'))
->numeric('client_status_id', __('Invalid option format'))
->add('client_status_id', 'valid', [
'rule' => function ($value, $context) {
return $this->clientStatusFinderRepository->clientStatusExists((int)$value);
},
'message' => 'Invalid option'
]);
}

/**
* Check if value is a specific backed enum case or string
* that can be converted into one.
*
* @param \BackedEnum|string|null $value
* @param string $enum
* @return bool
*/
public function isBackedEnum(\BackedEnum|string|null $value, string $enum): bool
{
// If $value is already an enum case, it means that its valid otherwise try to convert it to enum case
return is_a($value, $enum, true) || is_a($enum::tryFrom($value), $enum, true);
}


/**
* Validate client creation.
* Validate client values as array and not object to prevent exception on
Expand All @@ -34,7 +106,7 @@ public function __construct(
*
* @param array $clientValues
*/
public function validateClientCreation(array $clientValues): void
public function validateClientCreationOld(array $clientValues): void
{
// Validation error message asserted in ClientCreateActionTest
$validationResult = new ValidationResult('There is something in the client data that couldn\'t be validated');
Expand Down
Loading

0 comments on commit d695fe3

Please sign in to comment.