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'

email

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 none
  • has_error($errors, 'field') — Returns the input-error CSS class if the field has an error, otherwise empty string — applies a red border via theme.css
  • old('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>
Was this helpful?