Skip to content

marko/validation

Input validation with built-in rules, string-based rule parsing, and custom rule support — validate data before it reaches your domain. Validation provides a ValidatorInterface for checking data against rules. Rules can be specified as pipe-delimited strings ('required|email|max:255'), arrays, or RuleInterface objects. Validation errors are collected into a ValidationErrors bag with per-field messages. Use validateOrFail() to throw on invalid data.

Terminal window
composer require marko/validation

Inject ValidatorInterface and pass data with rules:

use Marko\Validation\Contracts\ValidatorInterface;
class UserController
{
public function __construct(
private readonly ValidatorInterface $validator,
) {}
public function store(
array $input,
): void {
$errors = $this->validator->validate($input, [
'name' => 'required|string|max:100',
'email' => 'required|email',
'age' => 'nullable|integer|min:18',
]);
if ($errors->isNotEmpty()) {
// handle errors
}
}
}

Use validateOrFail() to throw ValidationException when data is invalid:

$this->validator->validateOrFail($input, [
'title' => 'required|string|max:200',
'body' => 'required|string',
]);
// Throws ValidationException with errors() method

The ValidationException includes rich context --- call errors() to get the ValidationErrors bag, or getContext() and getSuggestion() for diagnostic details.

if ($this->validator->passes($input, ['email' => 'required|email'])) {
// data is valid
}
if ($this->validator->fails($input, ['email' => 'required|email'])) {
// data is invalid
}

ValidationErrors provides methods for inspecting failures:

$errors = $this->validator->validate($input, $rules);
$errors->has('email'); // true if email has errors
$errors->get('email'); // ['The email field must be a valid email.']
$errors->first('email'); // 'The email field must be a valid email.'
$errors->all(); // ['email' => [...], 'name' => [...]]
$errors->toFlatArray(); // ['email: ...', 'name: ...']
$errors->isEmpty(); // true if no errors
$errors->count(); // total error count across all fields
RuleString SyntaxDescription
RequiredrequiredMust be present and non-empty
NullablenullableSkips other rules if null/empty
StringstringMust be a string
IntegerintegerMust be an integer
NumericnumericMust be numeric
BooleanbooleanMust be boolean-like
EmailemailMust be a valid email
URLurlMust be a valid URL
AlphaalphaLetters only
AlphaNumericalpha_numLetters and numbers only
Minmin:5Minimum value/length
Maxmax:255Maximum value/length
Betweenbetween:1,100Value/length within range
Inin:draft,publishedMust be one of the listed values
NotInnot_in:admin,rootMust not be one of the listed values
Samesame:other_fieldMust match another field
Differentdifferent:other_fieldMust differ from another field
ConfirmedconfirmedMust have a matching _confirmation field
Regexregex:/^\d{3}$/Must match the pattern
Datedate or date:Y-m-dMust be a valid date
ArrayarrayMust be an array

Rules can be strings, arrays, or RuleInterface instances:

use Marko\Validation\Rules\Max;
use Marko\Validation\Rules\Required;
$this->validator->validate($input, [
'name' => 'required|string', // String format
'email' => ['required', 'email'], // Array of strings
'bio' => [new Required(), new Max(500)], // Rule objects
]);

Implement RuleInterface for custom validation logic:

use Marko\Validation\Contracts\RuleInterface;
class UniqueEmail implements RuleInterface
{
public function passes(
string $field,
mixed $value,
array $data,
): bool {
// Check uniqueness against database
return !$this->emailExists($value);
}
public function message(
string $field,
mixed $value,
): string {
return "The $field has already been taken.";
}
}
// Usage:
$this->validator->validate($input, [
'email' => ['required', 'email', new UniqueEmail()],
]);
interface ValidatorInterface
{
public function validate(array $data, array $rules): ValidationErrors;
public function validateOrFail(array $data, array $rules): void;
public function passes(array $data, array $rules): bool;
public function fails(array $data, array $rules): bool;
}
interface RuleInterface
{
public function passes(string $field, mixed $value, array $data): bool;
public function message(string $field, mixed $value): string;
}
class ValidationErrors implements Countable, IteratorAggregate
{
public function add(string $field, string $message): self;
public function has(string $field): bool;
public function get(string $field): array;
public function first(string $field): ?string;
public function all(): array;
public function keys(): array;
public function isEmpty(): bool;
public function isNotEmpty(): bool;
public function count(): int;
public function toFlatArray(): array;
}
class ValidationException extends Exception
{
public static function withErrors(ValidationErrors $errors): self;
public function errors(): ValidationErrors;
public function getContext(): string;
public function getSuggestion(): string;
}