Search by

wilycode / validator

szymonpietrek-design

Framework-independent PHP validation contract with reusable validators and structured, nested errors.

Package info

github.com/wilycode/validator

pkg:composer/wilycode/validator

Statistics

Installs: 3

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-10-26 00:11 UTC

This package is auto-updated.

Last update: 2026-10-26 00:12:52 UTC


README

PHP 8.4+ MIT License Tests

A lightweight, framework-independent validation contract for PHP 8.4+. Define reusable validators, report structured validation errors and integrate validation into your own forms or applications. No built-in validation rules are included in this core package.

WilyCode PHP Ecosystem

Part of WilyCode — independent, reusable PHP components with separate repositories, releases and Composer installations.

Explore the WilyCode PHP Ecosystem →

Contents

Features

  • A small ValidatorInterface contract and extensible Validator base class.
  • Structured Error objects with machine-readable codes and contextual options.
  • Recursive ErrorBag for field, item and collection errors.
  • Error reset and safe cloning of the base validator state.
  • No runtime dependencies beyond PHP 8.4+.

Installation

composer require wilycode/validator

Requires PHP 8.4+. Install wilycode/validators separately for ready-made validation rules.

Quick start

Extend Validator and implement isValid(mixed $value): bool:

<?php

declare(strict_types=1);

require __DIR__ . '/vendor/autoload.php';

use WilyCode\Validator\Validator;

final class EvenNumberValidator extends Validator
{
    public function isValid(mixed $value): bool
    {
        // Validators may be reused for different values.
        $this->clearErrors();

        if (!is_int($value) || $value % 2 !== 0) {
            $this->addError('invalid', ['expected' => 'even integer']);

            return false;
        }

        return true;
    }
}

$validator = new EvenNumberValidator();
$validator->isValid(3); // false
$validator->getErrors()[0]->getCode(); // 'validation.even_number.invalid'
$validator->getErrors()[0]->getOptions(); // ['expected' => 'even integer']
$validator->isValid(4); // true; previous errors cleared

You can also implement ValidatorInterface directly when a custom base class is preferred. Unlike the base class, the interface does not define how errors are stored.

Structured and nested errors

getErrors() returns a list of Error objects directly attached to the current validator. Each error exposes getCode(): string and getOptions(): array.

getErrorBag() provides nested error storage, useful for fields, form groups and collection rows:

use WilyCode\Validator\Error;
use WilyCode\Validator\ErrorBag;

$bag = new ErrorBag();
$bag->addError(new Error('validation.required'));
$bag->getOrCreateItem(0)->addError(new Error('validation.invalid'));

$tree = $bag->toArray();
// [
//     'errors' => [Error('validation.required')],
//     'items' => [0 => ['errors' => [Error('validation.invalid')], 'items' => []]],
// ]

Each node uses distinct errors and items keys, so child names cannot overwrite direct errors. toArray() preserves Error objects, not translated messages. Translate codes in your application. hasErrors() and isEmpty() consider nested items as well.

Use addErrors() or merge() to combine multiple error sources. setItem() accepts a nested error bag; cycles are rejected to prevent infinite recursion. Merging a bag into itself is also rejected.

API reference

API Purpose
ValidatorInterface::isValid(mixed $value): bool Validate a value; implemented by the concrete validator.
$validator->getErrors(): array Return direct Error objects for the validator.
$validator->getErrorBag(): ErrorBag Access its structured error container.
$validator->clearErrors(): static Clear previous errors, including nested items.
$validator->setStop(bool $stop): static Set a flag that composite validators may use to stop after a failure.
$validator->isStop(): bool Read the stop flag.
$bag->addError(Error $error): self Add a direct error.
$bag->addErrors(iterable $errors): self Add several direct errors.
$bag->getOrCreateItem(int|string $key): ErrorBag Get/create a child bag.
$bag->setItem(int|string $key, ErrorBag $bag): self Attach a child bag, rejecting cycles.
$bag->merge(ErrorBag $bag): self Merge direct and nested errors.
$bag->toArray(): array Return a tree containing errors and items.
$bag->hasErrors(): bool Detect direct or nested errors.
$bag->isEmpty(): bool Check for no direct or nested errors.

Validation state and cloning

The base Validator stores errors until clearErrors() is called. Call clearErrors() at the start of isValid() in custom validators that can be used more than once. The ready-made validators in wilycode/validators and WilyCode Form follow this convention.

Cloning a Validator resets its error bag and preserves configuration such as the stop flag. If a composite validator has nested mutable validators, override __clone() and clone those children as well; you may call parent::__clone() to reset inherited validation state.

The stop flag is a signal, not an automatic early return: composite validation code decides whether to use it.

Related packages

Development

composer install
composer validate --strict
composer test

GitHub Actions runs validation and tests on PHP 8.4 and 8.5. Unit tests are included in the source repository and excluded from standard Composer distribution archives.

License

MIT © 2026 Simon Piter. See LICENSE.