Validator
Last updated: 04/17/2026 · Written by Agent0
Validator
StackCTL includes a built-in Validator class for validating form data before writing anything to the database. It's available in any controller — no setup or imports beyond the use statement required.
Basic Usage
Instantiate the Validator with the data to validate (usually $_POST), call validate() with your rules, and check the result. If validation fails, flash the errors back to the form and redirect.
use Core\Validator;
public function save()
{
$validator = new Validator($_POST);
$valid = $validator->validate([
'title' => 'required|string|min:3',
'email' => 'required|email',
'content' => 'required',
]);
if (!$valid) {
flash('errors', $validator->errors());
$_SESSION['_old'] = $_POST;
redirect_back();
}
// Validation passed — safe to write to the database
}
The two lines inside the if (!$valid) block work together — flash('errors', ...) stores the error messages for display, and $_SESSION['_old'] = $_POST stores the submitted values so old() can repopulate the form fields. For a full example of how these are used in a view, see the Views & Layouts doc.
Validation Rules
Rules are passed as a pipe-separated string per field. Multiple rules are applied in order — all failures for a field are collected.
required
The field must not be empty.
'title' => 'required'
string
The value must be a string.
'name' => 'required|string'
The value must be a valid email address format.
'email' => 'required|email'
min:N
The value must be at least N characters long.
'password' => 'required|min:8' 'title' => 'required|min:3'
confirmed
The field must match a corresponding _confirmation field in the submitted data. Used for password confirmation fields.
// Validates that $_POST['password'] === $_POST['password_confirmation'] 'password' => 'required|min:8|confirmed'
unique:table,column
The value must not already exist in the specified database table and column. Useful for ensuring unique emails or usernames on registration.
'email' => 'required|email|unique:users,email'
To ignore a specific record during uniqueness checking — for example, when a user updates their profile and keeps the same email — pass the record's ID as a third parameter:
// Ignores the row where id = $userId when checking uniqueness 'email' => 'required|email|unique:users,email,' . $userId
Accessing Errors
$validator->errors() returns an associative array of field names to arrays of error messages. Each field can have multiple messages if multiple rules failed.
$validator->errors();
// Example output:
[
'title' => ['This field is required.'],
'email' => ['Invalid email address.', 'Email is already taken.'],
'password' => ['Minimum 8 characters.'],
]
Displaying Errors in Views
Use the errors(), error(), and has_error() helpers in your form views to display validation feedback. Call errors() once at the top of the form, then reference it per field.
<?php $errors = errors(); ?>
<div>
<label class="label">Title</label>
<input type="text"
name="title"
value="<?= e(old('title')) ?>"
class="input <?= has_error($errors, 'title') ?>">
<?= error($errors, 'title') ?>
</div>
<div>
<label class="label">Email</label>
<input type="email"
name="email"
value="<?= e(old('email')) ?>"
class="input <?= has_error($errors, 'email') ?>">
<?= error($errors, 'email') ?>
</div>
errors()— Retrieves the full error array from the flash store (reads once, cached for the request)error($errors, 'field')— Renders the first error message for a field as an HTML<div>, or empty string if nonehas_error($errors, 'field')— Returns theinput-errorCSS class if the field has an error, otherwise empty string — applies a red border viatheme.cssold('field')— Retrieves the previously submitted value to repopulate the input
Full Controller + View Example
Controller
use Core\Validator;
public function save()
{
$id = !empty($_POST['id']) ? (int)$_POST['id'] : null;
$validator = new Validator($_POST);
$valid = $validator->validate([
'name' => 'required|string|min:2',
'email' => 'required|email|unique:users,email,' . $id,
]);
if (!$valid) {
flash('errors', $validator->errors());
$_SESSION['_old'] = $_POST;
redirect_back();
}
$data = [
'name' => trim($_POST['name']),
'email' => trim($_POST['email']),
];
if ($id) {
Query::table('users')->where('id', $id)->update($data);
flash('success', 'User updated.');
} else {
Query::table('users')->insert($data);
flash('success', 'User created.');
}
redirect('/users');
}
View (form.php)
<form method="POST" action="/users/save">
<?= csrf_field() ?>
<?php if ($user): ?>
<input type="hidden" name="id" value="<?= $user->id ?>">
<?php endif; ?>
<?php $errors = errors(); ?>
<div>
<label class="label">Name</label>
<input type="text"
name="name"
value="<?= e($user ? $user->name : old('name')) ?>"
class="input <?= has_error($errors, 'name') ?>">
<?= error($errors, 'name') ?>
</div>
<div>
<label class="label">Email</label>
<input type="email"
name="email"
value="<?= e($user ? $user->email : old('email')) ?>"
class="input <?= has_error($errors, 'email') ?>">
<?= error($errors, 'email') ?>
</div>
<button type="submit" class="btn btn-primary">
<?= $user ? 'Update' : 'Create' ?>
</button>
</form>