Object-oriented, composable, fluent API for writing validations in Laravel

Overview

Laravel Hyrule

Hyrule provides an object-oriented, fluent API for building validation rules for use w/ Laravel's Validation component. This unlocks patterns that make it easier to define set of rules to enforce complex, nested data structures that is typical in API development.

Why:

Defining validation rules in Laravel involves manually building arrays. As business logic evolves and validation rules become more complex, those arrays grow in size, and building them also becomes more complex. Before long, you find yourself manipulating arrays: adding or removing rules based on conditions, refactor segments to be re-used, etc. and over time, this pattern can feel really clunky. It doesn't take a lot to make managing validation rule definitions feel like it's getting out of control. This library aims to fix that by offers a better API that helps you for the long-term:

  • Fluent API that allows you to define rules ergonomically e.g. add conditionals with ease, no more error-prone array manipulations.
  • Composable: Simplifies rule-building logic that can be reused multiple times, at multiple nesting levels. No more passing down & reconstructing dot-notated prefixes.
  • Strictness means less surprises: Promote enforcement of data-types, and reject unknown fields by default.

Installation

composer require square/laravel-hyrule:^2.0

For PHP 7.4 support, install the 1.* versions. See 1.x README

Setup

1.) If you do not have package discovery enabled, you will have to manually register the service provider:

// config/app.php

return [
    // ...
    'providers' => [
       // ...
       Square\Hyrule\HyruleServiceProvider::class,
       // etc.
     ],
];

2.) Publish the config:

php artisan vendor:publish --provider="Square\Hyrule\HyruleServiceProvider"

Using the service provider & the default config will allow your app to use StrictValidator.

API Basics

Initializing a rule-builder and adding your first field:

// Initialize a new builder: it will help you build up your validation rule-set.
$builder = Hyrule::create();

// Describe your expected input:
// It needs the field name *and* the data-type.
// This creates a *Node* that you can then attach rules to:
$builder->string('first_name')
    ->required() // Attach rules. This one marks the field as required.
    ->max(255); // ...and this one says it cannot be greater than 255 chars in length.

Fleshing out the rest of your fields & their rules:

$builder
    ->string('first_name')
        ->required()
        ->max(255)
        ->end() // Tell the builder you are done w/ this field...
    ->string('last_name') // ...so you can start a new one!
        ->required()
        ->max(255)
        ->end()
    ->integer('age') // ...This field is an integer.
        ->required()
        ->min(21)
        ->max(60)
        ->end();


$rules = $builder->build();

// $rules:
[
    '' => ['required', 'array:first_name,last_name,age'],
    'first_name' => ['string', 'required', 'max:255'],
    'last_name' => ['string', 'required', 'max:255'],
    'age' => ['integer', 'required', 'min:21', 'max:60'],
]

Start validating!

// Compile into an array Illuminate\Validation\Validator understands.
$rules = $builder->build();

// Use it e.g.
$validator = Validator::make($data, $rules);

// ...or
$request->validate($rules);

// etc.

Fields API

Hyrule forces you to define the expected data-type for each field. It supports all ranges of types, from scalar types to non-scalar types.

Scalar Types

Adding scalar fields are as easy as:

$builder->string('product_name');
$builder->integer('quantity');
$builder->float('rating');
$builder->numeric('display_price')
$builder->boolean('on_sale');

Non-Scalar Types

No matter how deep and complex your validation rules go, you can use the same set of APIs:

Objects

Use ->object(...) to start defining nested fields e.g.

$builder
    // "nutritional_facts" is a required field w/ a bunch of nested fields.
    ->object('nutritional_facts')
        ->required()
        // Describe the fields:
        ->integer('servings_per_container')
            ->required()
            ->min(1)
            ->end()
        ->string('serving_size')
            ->required()
            ->min(1)
            ->max(30)
            ->end()
        // "fat", a nested field, has a bunch of nested fields, too.
        ->object('fat')
            ->integer('saturated_fat_grams')->end()
            ->integer('saturated_fat_percent')
                ->max(100)
                ->end();
            ->end();
Unknown fields

By default, Hyrule helps you build robust & secure applications by only allowing fields you explicitly defined via the Fields API. This is specifically designed to help you be intentional w/ what you expect from your data. For example, this is another mechanism by which your API can further sanitize user input.

If you expect a field to come through, the library would still require you to specify the data-type. But you don't have to specify other rules:

Hyrule::create()
    ->string('name')
      ->end()
    // etc.

If you'd like to allow unknown fields through, use this method on the appropriate node(s):

Hyrule::create()
  ->allowUnknownProperties() // <- Allows unknown fields at root-level.
  ->object('data')
    ->allowUnknownProperties() // <- It does not carry-over. Add it to everywhere you wish to skip this.
    // etc.

Arrays of scalar values

You guessed it: Start with ->array():

// Defines an array field named "tags"...
$builder
    ->array('tags')
        // Array length must be between 1-10 elements long:
        ->min(1)
        ->max(10)
        // ...and each element (i.e. a tag) must be a string between 3-100 chars in length.
        ->each('string')
            ->min('3')
            ->max('100');
        // etc.

As you can see in this example, Hyrule promotes strictness even for what goes in arays.

Arrays of objects

Just define it like any other array field, and use the exact same API to define the nested fields:

$builder
    // Required "skus" must be between 1-10 items.
    ->array('skus')
        ->required()
        ->min(1)
        ->max(10)
        // Each SKU in the array are objects of their own:
        ->each('object')            
            // Each SKU has these fields:
            ->string('name')
                ->required()
                ->max(255)
                ->end()
            ->integer('quantity')
                ->min(0)
                ->end()
            // etc.

Rules API

First let's talk about what happens when you use the Fields API described above. When you define a field, a node is created & returned by the builder. You can then use the Rules API to add validation rules on a node.

Basic Example
// Adding built-in validation rules in Laravel
$builder
    ->string('foobar') // Returns a `StringNode` for the "foobar" field.
    ->required() // Adds the "required" validation rule.
    // Supports rules that accepts parameters like:
    ->min(1) // Adds "min:1"
    ->max(255) // Adds "max:1"
    ->requiredIf('vehicle_type', 'car') // Adds "required_if:vehicle_type,car"

    // Supports rules that access multiple parameters like:
    ->in('A', 'B', 'C') // Adds "in:A,B,C"
    // etc.
Custom Rules Support

This library helps you build validation rule definitions & does not limit you from using custom rules that doesn't come w/ Laravel:

$builder
    ->string('foobar')
        // Converts camel-case to snake-case notation:
        ->barBaz('olives', 'on', 'pizza') // Adds "bar_baz:olives,on,pizza"

        // Supports raw rule definitions:
        ->rule('required_without:another_field')

        // ... as well as custom Rule objects:
        ->rule(new MyCustomRule());

What's up with ->end()?

Once you understand that Square\Hyrule\Builder manages a tree of nodes and that the Fields APIs return child nodes, all you have to know is that ->end() returns the parent of the node, and it is the fluent way of traversing back up the tree:

$builder = Hyrule::create() // The root
    ->string('name')
        ->required()
        ->end() // Brings us back to $builder, the root node
        
    // New field on root:
    ->object('characteristics')
        ->required()
        ->string('eye_color')
            ->in(...EyeColors::all())
            ->end() // Back to "characteristics"
        ->numeric('height_cm')
            ->end() // Back to "characteristics"
        ->with(...)
    ->end() // Back to $builder, the root node.
    
    // Another field on root:
    ->array('siblings')
        ->max(10)
        ->each('object') // Starts the "*" ObjectNode
            ->string('name')
            ->end() // Back to the "*" ObjectNode
        ->end() // Back to "siblings"
    ->end() // Back to $builder, the root node.

    // etc.

If you are not a fan of this, you can use the Fields-With API.

Advanced Topics

Comments
  • using ->end() doesn't give RootNode's typehint to the IDE

    using ->end() doesn't give RootNode's typehint to the IDE

    since ->end() return the CompoundNode, instead of RootNode, it didn't give access to method like ->string(), ->numeric etc

    Maybe we can use something like public function end(): CompoundNode|RootNode so we can have access to that method.

    Thoughts?

    opened by sawirricardo 5
  • No support for file nodes

    No support for file nodes

    Hi!

    Today I noticed that there is no way to add validation for files. Perhaps some more universal Node class could be used as a fallback for unforeseen types?

    opened by dziurka 2
  • Expand `@return` annotation of AbstractNode#end() to help auto-complete w/o `@mixin` support

    Expand `@return` annotation of AbstractNode#end() to help auto-complete w/o `@mixin` support

    Apparently VSCode's intellisense cannot auto-complete well because it cannot not understand @mixin, even with the relevant PHP plugins. See https://github.com/square/laravel-hyrule/issues/6

    This should help.

    opened by bezhermoso 0
  • [Feature] Implement `FileNode`

    [Feature] Implement `FileNode`

    Addresses https://github.com/square/laravel-hyrule/issues/7

    Example 1: Images

    $builder = Hyrule::create()
        ->file('avatar')
           ->image()
           ->dimensions()
               ->ratio(1)
               ->maxWidth(1000)
               ->end()
           ->end()
        ->string('username')
           // etc.
    
    

    Example 2: Other MIME types

    $builder = Hyrule::create()
        ->file('scan')
           ->mimeType()
               ->allow('application/pdf')
               ->image('jpg', 'png') // allows image/jpg & image/png
           ->end()
        ->string('username')
           // etc.
    
    

    Example 3: Array of files

    $builder = Hyrule::create()
        ->array('attachments')
           ->between(1, 10)
           ->each('file')
              ->mimeType()
                ->allow('application/pdf')
                ->image('jpg', 'png') // allows image/jpg & image/png
                ->text('plain', 'html') // allows text/plain & text/html
                ->video('mp4') // allows video/mp4
                ->end()
            ->end()
          ->end()
        ->string('username')
           // etc.
    
    
    • [x] Node implementation
    • [x] Tests
    • [x] Static analysis
    • [x] Documentation
    opened by bezhermoso 0
  • [Feature] Implement `StrictValidator`, capable of applying rules to entire data array itself.

    [Feature] Implement `StrictValidator`, capable of applying rules to entire data array itself.

    Introduces a service provider which configures the Validator factory to return instances of Square\Hyrule\Validator\StrictValidator.

    I was hoping that this would be built-in behavior in Laravel already, but that PR was rejected: https://github.com/laravel/framework/pull/41962

    In the meantime, the ValidatesTopLevelRules trait should bring this capability to any Validator class that uses it.

    This will be the default behavior, but can be disabled by setting hyrule.strict_validator_class to a falsy value.

    opened by bezhermoso 0
  • Setting CI running in PR trigger in any branch

    Setting CI running in PR trigger in any branch

    Changed log

    • I think it should let CI running about PR trigger can run any of branches because it's easy for same contributor to create two or more PRs at same time.
    opened by peter279k 2
  • Improve rule normalization

    Improve rule normalization

    Take AbstractNode#requiredIf:

    In order to properly validate an optionally required field based on another boolean field, you'd need to do something like this:

    $builder = Hyrule::create()
        ->boolean('needs_verification')
           ->end()
        ->file('proof')
           ->requiredIf('needs_verification', '1')
           ->end()
        ->end();
    

    But that doesn't work correctly. This does:

    $builder = Hyrule::create()
        ->boolean('needs_verification')
           ->end()
        ->file('proof')
           ->requiredIf('needs_verification', 'true') // <- Must use "true"
           ->end()
        ->end();
    

    But ideally, it should just allow using a true boolean value:

    $builder = Hyrule::create()
        ->boolean('needs_verification')
           ->end()
        ->file('proof')
           ->requiredIf('needs_verification', true) // <- Makes the most sense.
           ->end()
        ->end();
    

    This PR loosens up the type requirements for requiredIf. It also brings proper normalization of true|false => "true"|"false" etc to other places e.g.

        ->anotherRule(false, null)
        // Before: another_rule:0,
        // Now: another_rule:false,NULL
    
    opened by bezhermoso 0
  • Support for psalm

    Support for psalm

    This package ships with a phpstan extension, would be great for one to be made for psalm as well.

    IE

    Magic method Square\Hyrule\Nodes\AbstractNode::string does not exist
    

    Workaround:

    For now, just tell psalm to ignore magic methods in your requests directory.

    <!-- hyrule doesn't have support for psalm yet. @see https://github.com/square/laravel-hyrule/issues/5 -->
            <UndefinedMagicMethod>
                <errorLevel type="suppress">
                    <directory name="app/Http/Requests" />
                </errorLevel>
            </UndefinedMagicMethod>
    
    opened by mr-feek 0
Releases(1.2.2)
  • 1.2.2(Jun 30, 2022)

    • Fixed: AbstractNode#rule(...) rejected Rule objects erroneously.

    Full Changelog: https://github.com/square/laravel-hyrule/compare/1.2.0...1.2.2

    Source code(tar.gz)
    Source code(zip)
  • 2.3.0(Jul 9, 2022)

    What's Changed

    • [Feature] Implement FileNode by @bezhermoso in https://github.com/square/laravel-hyrule/pull/8

    Full Changelog: https://github.com/square/laravel-hyrule/compare/2.2.0...2.3.0

    Source code(tar.gz)
    Source code(zip)
  • 2.2.0(May 18, 2022)

    What's Changed

    • Laravel 9.x & package auto-discovery support by @bezhermoso in https://github.com/square/laravel-hyrule/pull/4

    Full Changelog: https://github.com/square/laravel-hyrule/compare/2.1.0...2.2.0

    Source code(tar.gz)
    Source code(zip)
  • 1.2.0(May 18, 2022)

    What's Changed

    • Laravel 9.x & package auto-discovery support by @bezhermoso in https://github.com/square/laravel-hyrule/pull/4

    Full Changelog: https://github.com/square/laravel-hyrule/compare/1.1.0...1.2.0

    Source code(tar.gz)
    Source code(zip)
  • 1.1.0(May 18, 2022)

    What's Changed

    • See changes in https://github.com/square/laravel-hyrule/releases/tag/2.1.0. This backports those for PHP 7.4.

    Full Changelog: https://github.com/square/laravel-hyrule/compare/1.0...1.1.0

    Source code(tar.gz)
    Source code(zip)
  • 2.1.0(May 9, 2022)

    What's Changed

    • [Feature] Implement StrictValidator, capable of applying rules to entire data array itself. by @bezhermoso in https://github.com/square/laravel-hyrule/pull/2
    • Code-base now passes level 8 PHPStan analysis checks

    Full Changelog: https://github.com/square/laravel-hyrule/compare/2.0...2.1.0

    Upgrade Guide

    • Register Square\Hyrule\HyruleServiceProvider in your app.
    • Publish the config:
    php artisan vendor:publish --provider="Square\Hyrule\HyruleServiceProvider"
    
    Source code(tar.gz)
    Source code(zip)
  • 2.0(Apr 27, 2022)

    • First stable release.
    • PHP 8.0 support
    • Fluent API that allows you to define Laravel validation rules ergonomically.
    • Supports composable & reusable rule-building logic that can be reused multiple times, at multiple nesting levels.
    • Promote enforcement of data-types, and reject unknown fields by default.
    Source code(tar.gz)
    Source code(zip)
  • 1.0(Apr 27, 2022)

Owner
Square
Square
Provides an object-oriented API to generate and represent UIDs.

Uid Component The UID component provides an object-oriented API to generate and represent UIDs. Resources Documentation Contributing Report issues and

Symfony 343 Jan 5, 2023
Html-sanitizer - The HtmlSanitizer component provides an object-oriented API to sanitize untrusted HTML input for safe insertion into a document's DOM.

HtmlSanitizer Component The HtmlSanitizer component provides an object-oriented API to sanitize untrusted HTML input for safe insertion into a documen

Symfony 201 Dec 23, 2022
PHP 5.3 Object Oriented image manipulation library

Imagine Tweet about it using the #php_imagine hashtag. Image manipulation library for PHP 5.3 inspired by Python's PIL and other image libraries. Requ

null 4.3k Dec 29, 2022
Laravel 5 package for reading and writing CSV files.

CSV Laravel 5 package for reading and writing CSV files. Warning The package has been updated to PHP 7. If you can't update to PHP 7 use version 0.6.x

Maciej Wilgucki 48 Nov 29, 2022
Generate robust laravel athorization without writing a single line of code.

Implement robust laravel authorization logic without writing a single line of code This package helps you to quickly create strong policy authorizatio

Flixtechs 29 Oct 15, 2022
Simplifies writing DocBlock comments in Javascript, PHP, CoffeeScript, Actionscript, C & C++

DocBlockr DocBlockr is a package for Sublime Text 2 & 3 which makes writing documentation a breeze. DocBlockr supports JavaScript (including ES6), PHP

Nick Fisher 3.1k Nov 25, 2022
Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

Introduction Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services. It handles almost all of the boilerpl

The Laravel Framework 2.2k Jan 4, 2023
Laravel Cashier Paddle provides an expressive, fluent interface to Paddle's subscription billing services.

Introduction Laravel Cashier Paddle provides an expressive, fluent interface to Paddle's subscription billing services. It handles almost all of the b

The Laravel Framework 189 Jan 5, 2023
The fluent laravel marketplace

the marketplace CheatSheet will be availble soon marketplace provides you the following : 1. Product & Product Variation System Create simple products

Security Theater 189 Nov 24, 2022
A fluent Laravel package for Plaid

A fluent Laravel package for Plaid

Abivia 12 Sep 25, 2022
These are simple array and object collections that provide convinient methods to manipulate them.

Simple Collections These are simple array and object collections that provide convinient methods to manipulate collections; To install this package ty

Artem 4 Nov 19, 2021
A Laravel Wrapper for the CoinDCX API. Now easily connect and consume the CoinDCX Public API in your Laravel apps without any hassle.

This package provides a Laravel Wrapper for the CoinDCX API and allows you to easily communicate with it. Important Note This package is in early deve

Moinuddin S. Khaja 2 Feb 16, 2022
Laravel Package for TMDB ( The Movie Database ) API. Provides easy access to the wtfzdotnet/php-tmdb-api library.

Laravel Package for TMDB API Wrapper A Laravel package that provides easy access to the php-tmdb/api TMDB (The Movie Database) API wrapper. This packa

PHP - The Movie Database 151 Nov 1, 2022
The fastest way to make a powerful JSON:API compatible Rest API with Laravel.

The first fully customizable Laravel JSON:API builder. "CRUD" and protect your resources with 0 (zero) extra line of code. Installation You can instal

BinarCode 288 Aug 8, 2022
Lumen rest api demo with Dingo/Api, JWT, CORS, PHPUNIT

lumen-api-demo 这是一个比较完整用 lumen 5.7 写的的 REST API 例子。使用了 dingo/api ,jwt 实现登录,功能上很简单,登录,注册,发帖,评论,单元测试(正在补充)。 lumen5.x 请看对应的分支 有需要随时联系我 lumen/laravel/rest

Yu Li 859 Oct 25, 2022
List of 77 languages for Laravel Framework 4, 5, 6, 7 and 8, Laravel Jetstream , Laravel Fortify, Laravel Breeze, Laravel Cashier, Laravel Nova and Laravel Spark.

Laravel Lang In this repository, you can find the lang files for the Laravel Framework 4/5/6/7/8, Laravel Jetstream , Laravel Fortify, Laravel Cashier

Laravel Lang 6.9k Jan 2, 2023
Laravel Backend API for the tutorial (Granular permissions with Laravel APIs & React frontend)

Laravel Granular Permissions Backend Getting Started Clone the repository. Install the dependencies composer install Update .env database credentials

Munaf Aqeel Mahdi 4 May 10, 2022
Mollie API client wrapper for Laravel & Mollie Connect provider for Laravel Socialite

Mollie for Laravel Laravel-Mollie incorporates the Mollie API and Mollie Connect into your Laravel or Lumen project. Accepting iDEAL, Apple Pay, Banco

Mollie 289 Nov 24, 2022
Laravel Responder - a package for building API responses, integrating Fractal into Laravel and Lumen

A Laravel Fractal package for building API responses, giving you the power of Fractal with Laravel's elegancy.

Alexander Tømmerås 776 Dec 25, 2022