PHPackages                             guide42/plan - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Search &amp; Filtering](/categories/search)
4. /
5. guide42/plan

ActiveLibrary[Search &amp; Filtering](/categories/search)

guide42/plan
============

Fast and simple validation library

0.5.0(5y ago)940ISCPHPPHP &gt;=7.2

Since Aug 29Pushed 5y agoCompare

[ Source](https://github.com/guide42/plan)[ Packagist](https://packagist.org/packages/guide42/plan)[ RSS](/packages/guide42-plan/feed)WikiDiscussions master Synced 2mo ago

READMEChangelog (9)Dependencies (1)Versions (14)Used By (0)

Plan
====

[](#plan)

Plan is a data validation library for PHP. It's planed to be used for validating data from external sources.

It has two core design goals:

1. Simple: use language own features: construct schema from literals and function composition, errors are exceptions;
2. Lightweight: lots of validations included without 3rd party libraries;

Usage
-----

[](#usage)

Simplest way would be requiring `guide42/plan` with composer.

```
use plan\{Schema, MultipleInvalid, assert as v, filter as f};

$userSchema = new Schema(array(
    'id' => v\int(),
    'type' => v\any('user', 'admin'),
    'name' => v\all(
        v\length(4, 20),
        f\intl\alnum()
    ),
));

$_POST = ['id' => 3004, 'type' => 'user', 'name' => 'John'];

try {
    $user = $userSchema($_POST);
} catch (MultipleInvalid $errors) {
    $messages = $errors->getMessages();
}
```

Concepts
--------

[](#concepts)

Defining schemas is the first step to validate an input data. An schema is a tree of *validators* that can be manually defined or created based on literals and arrays.

With the schema defined, a plan must be make to validate data. This is made through the `\plan\Schema` class. This object, when called like a function, will validate the data and return the modified (or not) data. If any error occurs and exception will be thrown.

Schema information will be always be trusted, therefore will not be validate. Contrary input data will be never be trusted.

### Literals

[](#literals)

Scalars are treated as literals that are matched using the identity operator.

```
$plan = new Schema('Hello World');

assert('Hello World' === $plan('Hello World'));
```

As any plan validator it throws an `\plan\Invalid` when fails.

```
$plan = new Schema(42);
$plan(42);

try {
    $plan(10);
} catch (MultipleInvalid $errors) {
    assert('[ 10 is not 42 ]' === $errors->getMessage());
}
```

### Arrays

[](#arrays)

Plan will distinguish between indexed and associative arrays. If an array has all indexes numeric and sequential will be considerer a sequence. If not will be considerer a dictionary.

#### Sequences

[](#sequences)

A sequence will be treated as a list of possible valid values. Will require that the input data is sequence that contains one or more elements of the schema. Elements can be repeated.

An array of possible values:

```
$plan = new Schema([1, 'one']);
$plan([1]);
$plan([1, 'one', 1, 1, 'one', 1]);
```

An empty array will be a sequence that accept any value.

```
$plan = new Schema([]);
$plan(['anything', 123, true]);
```

#### Dictionaries

[](#dictionaries)

A dictionary will be used to validate structures. Each key in data will be checked with the *validator* of the same key in the schema.

Validators
----------

[](#validators)

All core *validators* live in `\plan\assert` *namespace*.

### `literal`

[](#literal)

See [Literals](#literals).

### `type`

[](#type)

Will validate the type of data. The data type will be not casted.

```
$plan = new Schema(v\type('integer'));
$plan(123);

try {
    $plan('123');
} catch (MultipleInvalid $errors) {
    assert('[ "123" is not integer ]' === $errors->getMessage());
}
```

Aliases of this *validator* are: `bool`, `int`, `float`, `str`.

### `scalar`

[](#scalar)

Wrapper around `is_scalar` function.

### `instance`

[](#instance)

Wrapper around `instanceof` type operator.

### `iterable`

[](#iterable)

Given data must be an array or implement `Iterable` interface.

### `required`

[](#required)

Rejected values are `null` and `''`.

### `optional`

[](#optional)

Accepts `null` and empty string without calling the given schema.

```
$plan = new Schema(v\dict(array(
    'id' => v\optional(v\int()),
    'name' => v\optional(v\str()),
)));
$plan(array('id' => 3004));
$plan(array('name' => 'John'));
$plan(array());
```

### `seq`

[](#seq)

See [Sequences](#sequences).

This is normally accepted as "a list of something (or something else)".

- A list of email? `new Schema([v\email()])`.
- A list of people, but some of them are in text and some as a dictionary?

    ```
    $plan = new Schema([v\str(), array(
        'name'  => v\str(),
        'email' => v\email(),
    )]);
    $plan([
        array('name' => 'Kevin', 'email' => 'k@viewaskew.com'),
        array('name' => 'Jane', 'email' => 'jane@example.org'),
        'John Doe ',
    ]);
    ```

### `dict`

[](#dict)

See [Dictionaries](#dictionaries).

Elements of the dictionary not found in data will be called with `null`; any additional data key will throw an exception.

```
$dict = array('name' => 'John', 'age' => 42);
$plan = new Schema($dict);

try {
    $plan(array('age' => 42));
} catch (MultipleInvalid $errors) {
    assert('{ [name]: null is not string }' === $errors->getMessage());
}

try {
    $plan(array('name' => 'John', 'age' => 42, 'sex' => 'male'));
} catch (MultipleInvalid $errors) {
    assert('{ Extra key sex not allowed }' === $errors->getMessage());
}
```

The *validator* `dict` accept two more parameters to change this behavior.

```
$required = false; // Accept any keys
$extra    = true;  // Accept extra keys

$dict = array('name' => 'John', 'age' => 42);
$plan = new Schema(v\dict($dict, $required, $extra));
$plan(array(
    'name' => 'John',
    'sex'  => 'male', // This could be whatever
                      // as it would not be validated
));
```

Both parameters (`required` and `extra`) could be arrays, so only the given keys will be taken in account.

```
$dict = array('name' => 'John', 'age' => 42);
$plan = new Schema(v\dict($dict, ['age'], ['sex']));
$plan(array('name' => 'John', 'age' => 42, 'sex' => 'male'));

try {
    $plan(array('name' => 'John', 'hobby' => 'sailing'));
} catch (MultipleInvalid $errors) {
    assert('{ Extra key hobby not allowed, [age]: Required age not provided }' === $errors->getMessage());
}
```

If the `extra` parameter is a dictionary it will be compiled and treat it as a validator for each extra key.

```
$extra = array('dob' => v\instance('\\DateTime'));

$dict = array('name' => 'John', 'age' => 42);
$plan = new Schema(v\dict($dict, true, $extra));
$plan(array('name' => 'John', 'age' => 42, 'dob' => new \DateTime));

try {
    $plan(array('name' => 'John', 'age' => 42, 'dob' => '1970-01-01'));
} catch (MultipleInvalid $errors) {
    assert('{ Extra key dob is not valid: Expected \DateTime (is not an object) }' === $errors->getMessage());
}
```

There is no way of treat all items with the same validator. Nor having a default validator for extra keys.

### `keys`

[](#keys)

Is also possible to validate and/or filter the list of keys of a dictionary.

```
$schema = v\keys(function(array $keys) {
    return array_filter($keys, function($key) {
        return $key === 'two';
    });
});

$result = $schema([
    'one' => 1,
    'two' => 2,
]);

assert($result === [ 'two' => 2 ]);
```

### `object`

[](#object)

The structure of an object can also be validated.

```
$structure = array('name' => v\str());
$class     = 'stdClass';
$byref     = true;

$plan = new Schema(v\object($structure, $class, $byref));
$plan((object) array('name' => 'John'));

try {
    $plan((object) array('name' => false));
} catch (MultipleInvalid $errors) {
    assert('{ [name]: false is not string }' === $errors->getMessage());
}
```

### `any`

[](#any)

Accept any of the given list of *validators*, as a valid value.

```
$plan = new Schema(array(
    'Connection' => v\any('ethernet', 'wireless'),
));
$plan(array('Connection' => 'ethernet'));
$plan(array('Connection' => 'wireless'));

try {
    $plan(array('Connection' => 'any'));
} catch (MultipleInvalid $errors) {
    assert('{ [Connection]: No valid value found }' === $errors->getMessage());
}
```

This is useful when you only need one choice a of set of values. If you need any quantity of choices use a [sequence](#sequence) instead.

### `all`

[](#all)

Require all *validators* to be valid.

```
$plan = new Schema(v\all(v\str(), v\length(3, 17)));
$plan('Hello World');

try {
    $plan('No');
} catch (MultipleInvalid $errors) {
    assert('[ Value must be at least 3 ]' === $errors->getMessage());
}
```

### `not`

[](#not)

Negative the given *validator*.

```
$plan = new Schema(v\not(v\str()));
$plan(true);
$plan(123);

try {
    $plan('fail');
} catch (MultipleInvalid $errors) {
    assert('[ Validator passed ]' === $errors->getMessage());
}
```

### `iif`

[](#iif)

Simple conditional.

```
$class = 'stdClass';
$plan = new Schema(v\iif(null !== $class,
    v\instance($class),
    v\type('object')
));

$plan(new stdClass);

try {
    $plan(new Exception('Arr..'));
} catch (MultipleInvalid $errors) {
    assert('[ Expected stdClass (is Exception) ]' === $errors->getMessage());
}
```

### `length`

[](#length)

The given data length is between some minimum and maximum value. This works with strings using `strlen` or `count` for everything else.

```
$plan = new Schema(v\length(2, 4));
$plan('abc');
$plan(['a', 'b', 'c']);

try {
    $plan('hello');
} catch (MultipleInvalid $errors) {
    assert('[ Value must be at most 4 ]' === $errors->getMessage());
}
```

### `validate`

[](#validate)

A wrapper for validate filters using `filter_var`.

```
$plan = new Schema(v\validate('email'));
$plan('john@example.org');

try {
    $plan('john(@)example.org');
} catch (MultipleInvalid $errors) {
    assert('[ Expected email ]' === $errors->getMessage());
}
```

It accepts the [name of the filter](http://php.net/manual/en/filter.filters.validate.php).

Aliases are: `url`, `email`, `ip`. And the "like-type": `boolval`, `intval`, `floatval`. Note that this will check that a string resemble to a boolean/int/float; for checking if the input data **IS** a boolean/int/float use the `type` *validator*. None of this will modify the input data.

### `datetime`

[](#datetime)

Validates if given datetime in string can be parsed by given format.

### `match`

[](#match)

Value must be a string that matches the regular expression.

Filters
-------

[](#filters)

The input data can also be filtered, and the validation will return the modified data. By convention is called *validator* when it will not modified the input data; and *filter* when modification to the data are performed.

Core *filters* will be found in the `\plan\filter` *namespace*.

### `type`

[](#type-1)

Will cast the data into the given type.

```
$plan = new Schema(f\type('int'));
$data = $plan('123 users');

assert(123 === $data);
```

Note that `boolval`, `strval`, `intval`, `floatval` are not aliases of this filter but wrappers of the homonymous functions.

### `sanitize`

[](#sanitize)

Sanitization [filters](http://php.net/manual/en/filter.filters.sanitize.php).

```
$plan = new Schema(f\sanitize('email'));
$data = $plan('(john)@example.org');

assert('john@example.org' === $data);
```

Aliases are: `url`, `email`.

### `datetime`

[](#datetime-1)

Will parse a datetime formated string into a `\DateTimeImmutable` object.

```
$plan = new Schema(f\datetime('Y-m-d H:i:s'));
$data = $plan('2009-02-23 23:59:59')->format('m-d');

assert('02-23' === $data);
```

String Filters
--------------

[](#string-filters)

All string transformations fall under the `\plan\filter\str` namespace.

### `nullempty`

[](#nullempty)

Change empty strings into nulls.

```
$plan = new Schema(f\str\nullempty());
$data = $plan('');

assert(null === $data);
```

### `strip`

[](#strip)

Wrapper of `trim` function.

```
$plan = new Schema(f\str\strip("\t"));
$data = $plan('\t  Hello World\t');

assert('  Hello World' === $data);
```

Internationalization
--------------------

[](#internationalization)

This library supports some *filters* to be language dependant. Before using any of them make sure that the correct locale is set (ex. by using `setlocale`).

### `chars`

[](#chars)

Will keep only characters in the current language and numbers. Optionally white-space could be keeped too.

```
$lower      = true; // all lower-case characters
$upper      = true; // all upper-case characters
$number     = true; // all numbers
$whitespace = true; // the only one not language dependant

$plan = new Schema(f\intl\chars($lower, $upper, $number, $whitespace));
$data = $plan('Hello World ☃!!1');

assert('Hello World 1' === $data);
```

Aliases are: `alpha`, `alnum`.

Writing Validators
------------------

[](#writing-validators)

A simple *callable* can be a *validator*.

Any validation error is thrown with the `Invalid` exception. If several errors must be reported, `MultipleInvalid` is an exception that could contain multiple exceptions. All other exceptions are considerer as errors in the *validator*.

```
$passwordStrength = function($data, $path = null)
{
    $type = v\str(); // Use another validator to check that `$data` is
    $data = $type($data); // an string, if not will throw an exception.

    // Because we are going to throw more than one error, we will
    // accumulate in this variable.
    $errors = [];

    if (strlen($data) < 8) {
        $errors[] = new Invalid('Must be at least 8 characters');
    }

    if (!preg_match('/[A-Z]/', $data)) {
        $errors[] = new Invalid('Must have at least one uppercase letter');
    }

    if (!preg_match('/[a-z]/', $data)) {
        $errors[] = new Invalid('Must have at least one lowercase letter');
    }

    if (!preg_match('/\d/', $data)) {
        $errors[] = new Invalid('Must have at least one digit');
    }

    if (count($errors) > 0) {
        throw new MultipleInvalid($errors);
    }

    // If everything went OK, we return the data so it can continue to be
    // checked by the chain.
    return $data;
};

$validator = new Schema(v\all(v\str(), $passwordStrength, v\not('hunter2')));
$validated = $validator('heLloW0rld');
```

Acknowledgments
---------------

[](#acknowledgments)

This library is heavily inspired in [Voluptuous](https://github.com/alecthomas/voluptuous) by Alec Thomas.

Badges
------

[](#badges)

[![Build Status](https://camo.githubusercontent.com/1476bdd8195c2438b2e7370134822b69cd50bca223e5ab324eae21d74dfb2066/68747470733a2f2f7472617669732d63692e6f72672f677569646534322f706c616e2e737667)](https://travis-ci.org/guide42/plan)[![Total Downloads](https://camo.githubusercontent.com/aba297ad689da429ddb2f059f3c17df595359995c3053d7f0c199cd5b42c614e/68747470733a2f2f706f7365722e707567782e6f72672f677569646534322f706c616e2f646f776e6c6f6164732e737667)](https://packagist.org/packages/guide42/plan)[![Coverage Status](https://camo.githubusercontent.com/10e7feb756e6b643fb19f7a5c57f564ee4859e8017dcf04e77214f7428798b93/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f677569646534322f706c616e2f62616467652e737667)](https://coveralls.io/github/guide42/plan)

###  Health Score

27

—

LowBetter than 49% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity14

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity55

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~232 days

Recently: every ~286 days

Total

10

Last Release

2152d ago

Major Versions

v0.2.1 → v3.x-dev2019-01-21

PHP version history (3 changes)v0.1.0PHP &gt;=5.3.8

v0.2.0PHP &gt;=5.6

v3.x-devPHP &gt;=7.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/c439f8b528698cc845fd4ffac60724c0533f58a235155ce2e775870e83937fdf?d=identicon)[joksnet](/maintainers/joksnet)

![](https://www.gravatar.com/avatar/09e7c752bfa311dd5ade20eb21d9320327f87fafe7fd829a8d973c2e9871369e?d=identicon)[guide42](/maintainers/guide42)

---

Top Contributors

[![jm42](https://avatars.githubusercontent.com/u/3297150?v=4)](https://github.com/jm42 "jm42 (254 commits)")

---

Tags

filtervalidationvalidatorvalidatorvalidationsecurityfilter

### Embed Badge

![Health badge](/badges/guide42-plan/health.svg)

```
[![Health](https://phpackages.com/badges/guide42-plan/health.svg)](https://phpackages.com/packages/guide42-plan)
```

###  Alternatives

[aura/filter

Filters to validate and sanitize objects and arrays.

173561.0k10](/packages/aura-filter)[ircmaxell/filterus

A library for filtering variables in PHP

44813.4k5](/packages/ircmaxell-filterus)[wandersonwhcr/romans

A Simple PHP Roman Numerals Library

44395.1k8](/packages/wandersonwhcr-romans)[soosyze/kses

An HTML/XHTML filter written in PHP. Checks on attribute values. Can be used to avoid Cross-Site Scripting (XSS), Buffer Overflows and Denial of Service attacks, among other things.

1258.5k1](/packages/soosyze-kses)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
