Coder Social home page Coder Social logo

schemer's Introduction

Schemer Build Status

composer require i-am-tom/schemer

Schemer is a Joi-inspired library for validating and formatting data structures. Complex structures' validators can be constructed by composition.

Quickstart

The full API can be accessed via the Schemer\Validator and Schemer\Formatter sets of static functions. We'll bring them in with an alias:

<?php

include 'vendor/autoload.php';

use Schemer\Validator as V;
use Schemer\Formatter as F;

Now, we'll declare a validator and a formatter:

$validator = V::assoc([
    'username' => V::text()
        ->min(3)
        ->max(10)
        ->alphanum(),

    'age' => V::integer()
        ->positive(),

    'email' => V::text()
        ->email(),

    'friends' => V::collection(
        V::text()
            ->min(3)
            ->max(20)
    )
]);

$formatter = F::assoc([
    'age' => F::integer(),

    'friends' => F::collection(
        F::text()
    )
])->only([
    'username',
    'age',
    'email',
    'friends'
]);

We've used the Assoc validator and formatter to create more complicated validators that nest data. The Collection interfaces can do similar things for sequential arrays. So, let's look at the formatter:

// $_GET = [
//     'username' => 'agilebear',
//     'email' => '[email protected]',
//     'age' => '40'
// ];

$formatted = $formatter->format($_GET);
// $formatted = [
//     'username' => 'agilebear',
//     'email' => '[email protected]',
//     'age' => 40,
//     'friends' => []
// ];

We see that age is now an integer, and the friends key has been created with an empty array, as per the formatting instructions. When handling data from other sources, you'll probably want to attempt to format the data before validating it.

Validation comes in two flavours. First, we'll start with simple validation:

$validator
    ->validate($formatted)
    ->isError(); // false

$result = $validator->validate([
    'username' => 'criminal',
    'friends' => 3
]);

$result->isError(); // true
$result->errors();
// [
//     'age: missing key',
//     'email: missing key',
//     'friends: not an array'
// ]

This flattens all the errors down to one Result whose errors method will return them in an array. While this may be suitable for validation of API requests, for example, it's often helpful for the structure of the errors to mimic the structure of the data. This can be achieved using nestedValidate:

$validator->nestedValidate([
    'username' => 'criminal',
    'friends' => 3
]);
// [
//     'age' => Result::failure('missing key'),
//     'email' => Result::failure('missing key'),
//     'friends' => Result::failure('not an array')
// ]

Although the above represents the result as an array, it is actually an instance of Schemer\NestedResult, which implements both ArrayAccess and Traversable, which allows you to use foreach syntax as well as array access to get at the results (e.g. $output['age']->errors() === ['missing key']). If there's a nestable (Assoc or Collection) within your structure, nestedValidate will recurse down through these in the same pattern, too!

Well, that's all there is to it! The API will fill in the rest of the blanks.

API

All methods are immutable. Calling a method will return a new object, and not change the previous one in any way:

<?php

$integer = Schemer\Validator::integer();
$integer->min(2); // This returns a NEW validator.

$integer->validate(1)->isError(); // false

Schemer\Formatter

<?php

// Format the value as an associative array with a [key => Formatter] schema.
Schemer\Formatter::assoc(array $schema)
    ->only(array $keys) // Strip unmentioned keys.
    ->rename(string $from, string $to) // Rename a key.
    ->renameMany(array $map) // Rename keys according to a [key => value] map.
    ->strip(array $keys) // Strip mentioned keys.

Schemer\Formatter::boolean() // Format as boolean.

// Format the value to an array of elements formatted accordingly.
Schemer\Formatter::collection(Schemer\Formatter\FormatterInterface $formatter)
    // Sort the values, either with sort() or a given comparator.
    ->sort(callable $comparator = null)
    ->truncate(int $maximum) // Strip values after a given length.
    ->unique() // Strip duplicates.

Schemer\Formatter::integer()
    ->abs() // Make the value positive if negative.
    ->max(int $boundary) // Cap the value at a maximum.
    ->min(int $boundary) // Make the value at least a minimum.

Schemer\Formatter::real()
    ->abs() // Make the value positive if negative.
    ->max(int $boundary) // Cap the value at a maximum.
    ->min(int $boundary) // Make the value at least a minimum.

Schemer\Formatter::text()
    ->lowercase() // Transform to lowercase.

    // Replace according to a regular expression.
    ->replace(string $regex, string $replacement)
    ->translate(string $from, string $to) // Translate characters.
    ->trim(string $mask = " \t\n\r\0\x0B") // Trim the string ends.
    ->truncate(int $maximum) // Cut the string to a maximum length.
    ->uppercase() // Transform to uppercase.

Schemer\Validator

Many of these methods accept an optional custom $error string, which overrides the default error.

<?php

// Included on every validator.
Schemer\ValidatorAbstract
  // Create a non-fatal validator from a bool-returning predicate function.
  ->should(callable $predicate, string $error = 'unsatisfied predicate')

  // Create a fatal validator from a bool-returning predicate function.
  ->must(callable $predicate, string $error = 'unsatisfied predicate')

  // Validate a given value against this validator.
  ->validate($value)

// Included on Assoc and Collection. Extends ValidatorAbstract.
Schemer\NestableAbstract

  // Validate a given value to produce a NestableResult.
  ->nestedValidate($value)

Schemer\Validator::any() // Accept all values of any type.

 // Allowed values. This is a specialised Any.
Schemer\Validator::allow(array $whitelist)

// Validate according to a schema. This is a [key => Validator] set,
// where the Validator can be any ValidatorInterface implementation.
Schemer\Validator::assoc(array $schema = [])
    ->length(int $count) // Set the required number of entries.
    ->max(int $count) // Set the maximum number of entries.
    ->min(int $count) // Set the minimum number of entries.
    ->never(array $keys) // Add the key blacklist.
    ->optional(array $schema) // A schema of optional keys.
    ->only(array $keys) // Add the key whitelist.

Schemer\Validator::boolean([string $error]) // Accept only boolean values, with an optional custom error.
    ->true([string $error]) // Accept only 'true', with an optional custom error.
    ->false([string $error]) // Accept only 'false', with an optional custom error.

// Accept an array of items all matching a given validator.
Schemer\Validator::collection(Schemer\Validator\ValidatorInterface $validator)
    ->length(int $count) // Set the required array length.
    ->max(int $count) // Set the maximum array length.
    ->min(int $count) // Set the minimum array length.

    // The elements must be sorted, either with sort() or a given
    // comparison function.
    ->ordered(callable $comparator = null)
    ->unique() // All values must be unique.

// Accept objects of a given class. This is a specialised Any.
Schemer\Validator::instanceOf(string $comparison)

Schemer\Validator::integer() // Accept integer values. Specialised Real.

// Accept a value that matches one of an array of validators.
// This is a specialised Any.
Schemer\Validator::oneOf(array $validators)

Schemer\Validator::real([string $error]) // Accept floating-point values.
    ->exactly(float $value[, string $error]) // Set the required value.
    ->max(float $value[, string $error]) // Set the maximum value.
    ->min(float $value[, string $error]) // Set the minimum value.
    ->negative([string $error]) // Allow only values less than or equal to zero.
    ->positive([string $error]) // Allow only values greater than or equal to zero.

 // Allow all values accept those from a given set. Specialised Any.
Schemer\Validator::reject(array $blacklist)

Schemer\Validator::text() // Accept string values.
    ->alphanum() // Allow only alphanumeric strings.
    ->email() // Allow only email addresses.
    ->length(int $length) // Set the allowed string length.
    ->lowercase() // Allow only lowercase strings.
    ->max(int $length) // Set the maximum string length.
    ->min(int $length) // Set the minimum string length.
    ->regex(string $regex) // Set the regex to be matched.
    ->uppercase() // Allow only uppercase strings.

Contributing

Get involved! PRs are cool.

schemer's People

Contributors

aaronjameslang avatar imjoehaines avatar mattroylloyd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

schemer's Issues

Smart concatenation of `NestedResult` objects' errors

Currently, when concatenating two NestedResult objects, the inner errors will overwrite one another according to + on arrays. Ideally, because these sub-errors should match types, this merge could be smarter, combining errors that occur in both results for the same key. What might be trickier is combining NestedResult and Result objects, so this would need some planning beforehand.

Grouped error messages

I would love to see errors (from an API standpoint) where I would have a field with an array of errors for that field.

[
    'username' => [
        'Is blank',
        'Needs at least 6 characters'
    ]
]

Add custom validator error messages

At the moment, all validation messages are fixed and really designed to be parsed easily.

It'd be cool to be able to add custom error messages to all the validators, rather than just the should/must predicate converters. Realistically, this will be sticking an extra parameter in all the functions (a la test suite assert functions), and updating the documentation in a way that doesn't make it look ugly.

Implement `Traversable` for `Schemer\Result`

This would allow for foreach to be used to loop through the errors within the structure. It may also open the doors to some sort of tie-in with the RecursiveIteratorIterator in the SPL.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.