PHPackages                             jkbennemann/laravel-foliage - 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. [Validation &amp; Sanitization](/categories/validation)
4. /
5. jkbennemann/laravel-foliage

ActiveLibrary[Validation &amp; Sanitization](/categories/validation)

jkbennemann/laravel-foliage
===========================

This packages allows you to validate arbitrary business requirements within your application against a defined set of rules.

0.1.1(2y ago)05MITPHPPHP ^8.1|^8.2

Since Feb 25Pushed 2y ago1 watchersCompare

[ Source](https://github.com/jkbennemann/laravel-foliage)[ Packagist](https://packagist.org/packages/jkbennemann/laravel-foliage)[ Docs](https://github.com/jkbennemann/laravel-foliage)[ GitHub Sponsors](https://github.com/jkbennemann)[ RSS](/packages/jkbennemann-laravel-foliage/feed)WikiDiscussions main Synced 2d ago

READMEChangelog (2)Dependencies (15)Versions (3)Used By (0)

Laravel Foliage
===============

[](#laravel-foliage)

This packages allows you to validate arbitrary business requirements within your application.

[![Laravel Foliage Logo](public/foliage-logo.png)](public/foliage-logo.png)

[![Latest Version on Packagist](https://camo.githubusercontent.com/4361de3b00d9eead14c16fd8f4e0152f69db25edfad5e515813f5186982c3cae/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6a6b62656e6e656d616e6e2f6c61726176656c2d666f6c696167652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/jkbennemann/laravel-foliage)[![GitHub Tests Action Status](https://camo.githubusercontent.com/e98f44e54364471a64523a0c77fe2d7587c68f7afa027699e0f3e9318831ef11/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6a6b62656e6e656d616e6e2f6c61726176656c2d76616c69646174652d627573696e6573732d726571756972656d656e74732f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/jkbennemann/laravel-foliage/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/8fcb2e8e58916cbc5c2752b73adfbd3ed56739cf8fd0c7f1cff1bde90fe8a6b3/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6a6b62656e6e656d616e6e2f6c61726176656c2d76616c69646174652d627573696e6573732d726571756972656d656e74732f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/jkbennemann/laravel-foliage/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/f4b2d2e70686db558a20497576385c000f1021d7a77c2361e460f7bc419f23e1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6a6b62656e6e656d616e6e2f6c61726176656c2d666f6c696167652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/jkbennemann/laravel-foliage)

What can be validated?
----------------------

[](#what-can-be-validated)

You can basically validate anything as long as you can make a logical expression out of it.

Examples:

- Extend Laravel Gates/Policies with extensive validations based on your business rules
- Validate rules stored on your database models, eg. Coupon Code availability, Subscription validations, ..
- Perform any other arbitrary data validation within your classes based on e.g. request input, ...
- ...

*Your options are basically endless*

Installation
------------

[](#installation)

You can install the package via composer:

```
composer require jkbennemann/laravel-foliage
```

You can publish the config file with:

```
php artisan vendor:publish --tag="foliage-config"
```

This is the contents of the published config file:

```
return [
    'available_rules' => [
        //add your rules here
        //SampleRule::class,
    ],

    'rule_parser' => \Jkbennemann\Foliage\Core\RuleParser::class,

    'payload_namespace' => 'App\Services\Foliage\Payloads',

    'rules_namespace' => 'App\Services\Foliage\Rules',

    'validator' => \Jkbennemann\Foliage\Validator\TreeValidator::class,

    'validation_data_builder' => \Jkbennemann\Foliage\Validator\ValidationDataBuilder::class,
];
```

How it works
------------

[](#how-it-works)

The whole concept of this package is to enable arbitrary validations of business rules.
To achieve this we will create a binary tree from your set of rules.

The resulting tree can then be validated by a given input of data.

For each rule the required data to validate against will automatically be taken from the provided payload.

Each tree node later will be expressed as an array, containing the following data.

```
[
    'alias' => null,
    'children' => [
        //sub nodes
    ],
    'data' => [
        //data of your rule
    ],
    'name' => 'rule_key',
    'operation' => 'AND|OR|NOT|null',
    'type' => 'node|leaf',
]
```

Usage
-----

[](#usage)

### Instantiating new rules

[](#instantiating-new-rules)

To create new you can either use the class provided by the package or just make use of the Facade for an easy and expressive API.

#### Basic instantiation

[](#basic-instantiation)

```
$availableRules = config('foliage.available_rules');

$ruleParser = new \Jkbennemann\Foliage\Core\RuleParser($availableRules);

$foliage = new Jkbennemann\Foliage\Core\Rule($ruleParser);

$rule = $foliage->single(SampleRule::class, ['sample' => 'data']);
$rule = $foliage->and([
    [SampleRule::class, ['sample' => 'data']),
    [AnotherRule::class, ['another_sample' => 'data']),
]);
$rule = $foliage->or([
    [SampleRule::class, ['sample' => 'data']),
    [AnotherRule::class, ['another_sample' => 'data']),
]);
```

#### Facade usage

[](#facade-usage)

```
use Jkbennemann\Foliage\Facades\Rule;

//Single rule usage
$rule = Rule::single(
    SampleRule::class, ['sample' => 'data']
);

$rule = Rule::not(
    SampleRule::class, ['sample' => 'data']
);

//Multi rule usage
$rule = Rule::and(
    Rule::single(SampleRule::class, ['sample' => 'data']),
    Rule::not(AnotherRule::class, ['another_sample' => 'data']),
);

$rule = Rule::or(
    Rule::single(SampleRule::class, ['sample' => 'data']),
    Rule::not(AnotherRule::class, ['another_sample' => 'data']),
    Rule::and(
        Rule::not(ThirdRule::class, ['another_sample' => 'data']),
        Rule::not(ThirdRule::class, ['another_sample' => 'data']),
    )
);
```

A sample representation of a simple `and` rule will look like this

```
$rule = Rule::and(
    Rule::single(SampleRule::class, ['sample' => 'data']),
    Rule::not(AnotherRule::class, ['another_sample' => 'data']),
);
$structure = $rule->toArray();

echo $rule->jsonSerialize();
```

```
{
    "alias": null,
    "children":
    [
        {
            "alias": null,
            "children":
            [],
            "data":
            {
                "sample": "data"
            },
            "name": "sample_rule",
            "operation": null,
            "type": "leaf"
        },
        {
            "alias": null,
            "children":
            [],
            "data":
            {
                "another_sample": "data"
            },
            "name": "another_rule",
            "operation": null,
            "type": "leaf"
        }
    ],
    "data": null,
    "name": null,
    "operation": "AND",
    "type": "node"
}
```

### Use Aliases for rule payload

[](#use-aliases-for-rule-payload)

If you want to use the same rule multiple times, with different options you will need to specify an alias to the rule.
A alias basically overrides the argument name for the payload to validate against.

Assuming you as an administrator want to perform an action on behalf of a user of your application.
You as the administrator have the right to do so, but the user under consideration itself has not.

```
$payload = [
    'user_is_admin' => $user->isAdmin(),
    'performing_user_is_admin' => $currentLoggedInUser->isAdmin(),
];

$rule = Rule::or(
    Rule::single(IsAdminRule::class)->alias('performing_user_is_admin'),
    Rule::and(
        Rule::not(IsAdminRule::class)->alias('user_is_admin'),
        Rule::not(IsAllowedUser::class, ['user' => 'allowed_user']),
    ),
);
```

### Creating rule by structured tree data

[](#creating-rule-by-structured-tree-data)

Eventually you want to store the tree structure inside your database to validate against eloquent models.

In this case you model should implement the `HasValidationRules` trait provided by the package.
This gives you access to those rules.

Your database field should be a `json` field if you're using MySQL/MariaDB.

```
use Jkbennemann\Foliage\Core\HasValidationRules;

class CouponCode extends Model {

    use HasValidationRules;

    protected $casts = [
        'database_field_name' => 'array',
    ];

    //..

    protected function rulesFieldName(): string
    {
        return 'database_field_name';
    }
}
```

Your model now has access to

```
$node = $coupon->validationNode();  //returns a Node object for validation
```

#### From existing array

[](#from-existing-array)

If you have an array, already in the tree structure, you can create a node from it like so

```
$ruleData = [
    'alias' => null,
    'children' => [
        [
            'alias' => null,
            'children' => [],
            'data' => [
                'foo' => 'bar',
            ],
            'name' => 'rule_1',
            'operation' => null,
            'type' => 'leaf',
        ],
        [
            'alias' => null,
            'children' => [],
            'data' => [
                'bar' => 'baz',
            ],
            'name' => 'rule_2',
            'operation' => null,
            'type' => 'leaf',
        ],
    ],
    'data' => null,
    'name' => null,
    'operation' => 'AND',
    'type' => 'node',
]

$builder = app(\Jkbennemann\Foliage\Core\TreeBuilder::class);
$node = $builder->build($ruleData);
```

### Validating rules

[](#validating-rules)

To validate a created set of rules you can

- invoke the `validate()` method of the Foliage class
- create your own validator
- call the `validate()` method on a `Node` instance

```
$rule = Rule::single(SampleRule::class, ['name' => 'John Doe']);
$node = $rule->node();

//payload constructed during your application's request lifecycle.
$payload = [
    'name' => 'John Doe'
];

//manual instantiation
//returns `Result` object
$foliage = new Foliage($validator, $treeBuilder);
$result = $foliage->validateSilently($node, $payload);  // does not throw an exception on error
$foliage->validate($node, $payload);                    // throws exception on error

//using Facade
//returns `Result` object
$result = \Jkbennemann\Foliage\Facades\Foliage::validate($node, $payload)
$result->isValid();  //true
$result->errors();   //empty collection
```

#### Exception Handling

[](#exception-handling)

By default, the validator throws an exception on first occurring validation error.

If you want to change this behaviour you can instruct the validator not to raise an exception.

```
$validator = new TreeValidator(new ValidationDataBuilder(), new PostOrderEvaluator());
$validator->withoutExceptions();

$isValid = $validator->evaluate($node, $payload);
$errors = $validator->errors();

$validator->evaluate($node, $payload)
```

### Container resolution

[](#container-resolution)

The package makes use of the Laravel container, by taking the settings from the config file `config/foliage.php`

Because of this you can also instantiate a validator by calling the container.

```
use Jkbennemann\Foliage\Validator\Contracts\BaseValidator;

$validator = app(BaseValidator::class);
$validator->withoutExceptions();
$validator->withExceptions();
```

### Create a new Validation Rule

[](#create-a-new-validation-rule)

If you want to create a new rule, you can run the `artisan` command.

```
php artisan validation:create-rule SampleRule

```

This command creates a new rule within the namespace, specified inside the config file.
The content will be

```
