Validation
Installation
The validation library for validating PSR-7 Request data.
Usage
There are two ways to validating data: 1) use the make() method to create a validation object, then validate it using the validate() method, 2) or just use validate().
Examples
Using make():
<?php
use Qubus\Validation\Validator;
$validator = new Validator();
// make it
$validation = $validator->make($_POST + $_FILES, [
'name' => 'required',
'email' => 'required|email',
'password' => 'required|min:6',
'confirm_password' => 'required|same:password',
'avatar' => 'required|uploaded_file:0,500K,png,jpeg',
'skills' => 'array',
'skills.*.id' => 'required|numeric',
'skills.*.percentage' => 'required|numeric'
]);
// then validate
$validation->validate();
if ($validation->fails()) {
// handling errors
$errors = $validation->errors();
echo "<pre>";
print_r($errors->firstOfAll());
echo "</pre>";
exit;
} else {
// validation passes
echo "Success!";
}
Using validate():
<?php
use Qubus\Validation\Validator;
$validator = new Validator();
$validation = $validator->validate($_POST + $_FILES, [
'name' => 'required',
'email' => 'required|email',
'password' => 'required|min:6',
'confirm_password' => 'required|same:password',
'avatar' => 'required|uploaded_file:0,500K,png,jpeg',
'skills' => 'array',
'skills.*.id' => 'required|numeric',
'skills.*.percentage' => 'required|numeric'
]);
if ($validation->fails()) {
// handling errors
$errors = $validation->errors();
echo "<pre>";
print_r($errors->firstOfAll());
echo "</pre>";
exit;
} else {
// validation passes
echo "Success!";
}
Note
Both examples will output the same results. But, you may want to use the make() method for access to custom messages, custom attribute alias, etc. before looping the validation.
Attribute Alias
You can transform your attribute into readable text. For example confirm_password will be displayed as Confirm password. But you can set it to anything you want with the setAlias() or setAliases() methods.
Example
<?php
use Qubus\Validation\Validator;
$validator = new Validator();
// To set attribute alias, you should use `make` instead of `validate`.
$validation->make([
'state_id' => $_POST['state_id'],
'zip_code' => $_POST['zip_code']
], [
'state_id' => 'required|alpha',
'zip_code' => 'required|numeric'
]);
// now you can set aliases using this way:
$validation->setAlias('state_id', 'State');
$validation->setAlias('zip_code', 'Postal Code');
// or this way:
$validation->setAliases([
'state_id' => 'State',
'zip_code' => 'Postal Code'
]);
// then validate it
$validation->validate();
Now if state_id value is empty, the error message will be 'State is required'.
Custom Validation Message
Before registering or setting custom messages, you can use the following placeholders:
:attribute: will replace into attribute alias.:value: will replace into a stringified value of the attribute. For arrays or objects, will replace into JSON.
Here are some ways to register/set your custom message(s):
Custom Messages for Validator
Anytime you use make or validate you can set your custom message(s). This is useful for localization.
To accomplish this, pass an array to the constructor:
<?php
use Qubus\Validation\Validator;
$validator = new Validator([
'required' => ':attribute debe llenarse',
'email' => ':email inválido',
// etc
]);
// then validation will use these custom messages
$validation_a = $validator->validate(inputs: $dataset_a, rules: $rules_for_a);
$validation_b = $validator->validate(inputs: $dataset_b, rules: $rules_for_b);
or use the setMessages() method:
<?php
use Qubus\Validation\Validator;
$validator = new Validator();
$validator->setMessages([
'required' => ':attribute debe llenarse',
'email' => ':email inválido',
// etc
]);
// then validation will use these custom messages
$validation_a = $validator->validate(inputs: $dataset_a, rules: $rules_for_a);
$validation_b = $validator->validate(inputs: $dataset_b, rules: $rules_for_b);
Custom Messages for Validation
Sometimes, you may need to set custom messages for a specific validation. To do this, you can set your custom messages as a 3rd argument of $validator->make() or $validator->validate():
<?php
use Qubus\Validation\Validator;
$validator = new Validator();
// then validation will use the custom messages
$validation = $validator->validate(
inputs: $dataset_a,
rules: $rules_for_a,
messages: [
'required' => ':attribute debe llenarse',
'email' => ':email inválido',
// etc
]
);
Or you use $validation->setMessages():
<?php
use Qubus\Validation\Validator;
$validator = new Validator();
$validation = $validator->make($dataset_a, $rules_for_dataset_a);
$validation->setMessages([
'required' => ':attribute debe llenarse',
'email' => ':email inválido',
// etc
]);
//
$validation->validate();
Custom Message for Specific Attribute Rule
Sometimes you may need to set a custom message for a specific rule attribute. To do so, you can use : as a message separator or by chaining methods.
Examples
<?php
use Qubus\Validation\Validator;
$validator = new Validator();
$validation = $validator->make($dataset_a, [
'age' => 'required|min:18'
]);
$validation->setMessages([
'age:min' => '18+ only',
]);
$validation->validate();
or chain methods:
<?php
use Qubus\Validation\Validator;
$validator = new Validator();
$validation = $validator->make($dataset_a, [
'photo' => [
'required',
$validator('uploaded_file')->fileTypes('jpeg|png')->message('Photo must be a jpeg/png image')
]
]);
$validation->validate();
Data Validators
If you are using one of the starter templates for CodefyPHP, this becomes much easier with using Data Validators.
Let's create a StoreUserValidator with 2 methods (authorize and rules):
<?php
declare(strict_types=1);
namespace Domain\User\Validator;
use Codefy\Framework\Validation\HttpInputValidator;
use Domain\User\Enum\UserRole;
use function Codefy\Framework\Helpers\gate;
use function implode;
final class StoreUserValidator extends HttpInputValidator
{
public function authorize(): bool
{
return (bool) gate('admin:create:user');
}
/**
* @return array<string, string>
* @throws \Exception
*/
public function rules(): array
{
$roles = implode(separator: ',', array: UserRole::values());
return [
'first_name' => 'required|string|min:3',
'middle_name' => 'string|min:3',
'last_name' => 'required|string|min:3',
'email' => 'required|email',
'role' => 'required|string|in:' . $roles,
];
}
}
Now, we need a DTO to go with our validator using the same verb Store as a prefix. This naming convention isn't necessary, but it does add clarity:
<?php
declare(strict_types=1);
namespace Domain\User\Dto;
use Codefy\Framework\Dto\DataTransformer;
use Codefy\Framework\Support\Password;
use Codefy\Framework\Validation\DataValidator;
use Domain\User\ValueObject\Username;
use Domain\User\ValueObject\UserRole;
use Domain\User\ValueObject\UserToken;
use Qubus\ValueObjects\StringLiteral\StringLiteral;
use Qubus\ValueObjects\Web\EmailAddress;
final readonly class StoreUserData implements DataTransformer
{
private function __construct(
public Username $username,
public UserToken $token,
public StringLiteral $firstName,
public StringLiteral $middleName,
public StringLiteral $lastName,
public EmailAddress $email,
public UserRole $role,
public StringLiteral $password,
) {
}
/**
* @throws \Exception
*/
public static function fromValidatedData(DataValidator $data): self
{
return new self(
username: new Username($data->string(key: 'username')),
token: new UserToken(),
firstName: new StringLiteral($data->string(key: 'first_name')),
middleName: new StringLiteral($data->string(key: 'middle_name', default: '')),
lastName: new StringLiteral($data->string(key: 'last_name')),
email: new EmailAddress($data->string(key: 'email')),
role: new UserRole($data->string(key: 'role')),
password: new StringLiteral(Password::hash($data->string(key: 'password'))),
);
}
}
Usage in Controller
<?php
declare(strict_types=1);
namespace Application\Http\Controller;
use Codefy\Framework\Http\BaseController;
use Domain\User\Command\CreateUserCommand;
use Domain\User\Validator\StoreUserValidator;
use Domain\User\Service\UserService;
use Psr\Http\Message\ResponseInterface;
use Qubus\Http\ServerRequest;
use Qubus\Routing\Exceptions\NamedRouteNotFoundException;
use Qubus\Routing\Exceptions\RouteParamFailedConstraintException;
use function Codefy\Framework\Helpers\command;
final class AdminController extends BaseController
{
/**
* @throws RouteParamFailedConstraintException
* @throws NamedRouteNotFoundException
* @throws \Exception
* @throws \ReflectionException
*/
public function store(ServerRequest $request, UserService $service): ResponseInterface
{
$userData = StoreUserData::fromValidatedData(
StoreUserValidator::make($request)
);
$command = new CreateUserCommand([
$userData->username,
$userData->token,
$userData->firstName,
$userData->middleName,
$userData->lastName,
$userData->email,
$userData->role,
$userData->password,
]);
command(command: $command);
return $this->redirect(
url: $this->router->url(
name: 'admin.users'
)
);
}
}
Custom DTO Class with UseDto Attribute
You can enhance your Validators by implementing the Codefy\Framework\Dto\HasDto interface, adding the Codefy\Framework\Dto\Trait\DtoAware trait and by using the Codefy\Framework\Dto\Attribute\UseDto attribute
<?php
declare(strict_types=1);
namespace Domain\User\Validator;
use Codefy\Framework\Dto\Attribute\UseDto;
use Codefy\Framework\Dto\HasDto;
use Codefy\Framework\Dto\Trait\DtoAware;
use Codefy\Framework\Validation\HttpInputValidator;
use Domain\User\Dto\StoreUserData;
use Domain\User\Enum\UserRole;
use function Codefy\Framework\Helpers\gate;
use function implode;
#[UseDto(StoreUserData::class)]
final class StoreUserValidator extends HttpInputValidator implements HasDto
{
use DtoAware;
public function authorize(): bool
{
return (bool) gate('admin:create:user');
}
/**
* @return array<string, string>
* @throws \Exception
*/
public function rules(): array
{
$roles = implode(separator: ',', array: UserRole::values());
return [
'first_name' => 'required|string|min:3',
'middle_name' => 'string|min:3',
'last_name' => 'required|string|min:3',
'email' => 'required|email',
'role' => 'required|string|in:' . $roles,
];
}
}
The Codefy\Framework\Dto\Trait\DtoAware trait automatically:
- Resolves the correct DTO class name based on the
UseDtoattribute - Validates that the DTO class exists
- Calls the
fromValidatedData()method to create the DTO - Provides helpful error messages if something goes wrong
Benefits of using the UseDto attribute:
✅ Custom naming conventions
✅ Reuse existing DTO classes
✅ Better organization of DTOs
✅ Type safety with class-string parameter
<?php
declare(strict_types=1);
namespace Domain\User\Service;
use Codefy\CommandBus\Exceptions\CommandCouldNotBeHandledException;
use Codefy\CommandBus\Exceptions\CommandPropertyNotFoundException;
use Codefy\CommandBus\Exceptions\UnresolvableCommandHandlerException;
use Codefy\Framework\Factory\FileLoggerFactory;
use Codefy\Framework\Proxy\Codefy;
use Codefy\QueryBus\UnresolvableQueryHandlerException;
use Domain\User\Command\CreateUserCommand;
use Domain\User\Validator\StoreUserValidator;
use function Codefy\Framework\Helpers\command;
use function Codefy\Framework\Helpers\trans;
final readonly class UserService
{
/**
* @throws \ReflectionException
* @throws \Exception
*/
public function createUser(StoreUserValidator $data): void
{
try {
command(
command: new CreateUserCommand(
data: $data->toDtoArray()
)
);
Codefy::$PHP->flash->success(
message: trans('User added successfully.'),
);
} catch (CommandPropertyNotFoundException|
\ReflectionException|
UnresolvableCommandHandlerException|
CommandCouldNotBeHandledException $e
) {
Codefy::$PHP->flash->error(
message: trans('Could not create user. Please try again later.'),
);
FileLoggerFactory::getLogger()->error(message: $e->getMessage(), context: ['AdminController' => 'create']);
}
}
}
$data->toDtoArray(), it will return a data array of validated values from StoreUserValidator. <?php
// In your controller
public function store(ServerRequest $request, UserService $service): ResponseInterface
{
$service->createUser(
StoreUserValidator::make($request)
);
return $this->redirect(
url: $this->router->url(
name: 'admin.users'
)
);
}