{"id":13616458,"url":"https://github.com/marcosh/php-validation-dsl","last_synced_at":"2025-04-14T00:32:18.228Z","repository":{"id":47007734,"uuid":"150417864","full_name":"marcosh/php-validation-dsl","owner":"marcosh","description":"A DSL for validating data in a functional fashion","archived":false,"fork":false,"pushed_at":"2021-09-17T15:03:29.000Z","size":158,"stargazers_count":48,"open_issues_count":3,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-10-06T04:22:29.530Z","etag":null,"topics":["dsl","functional","immutability","php","validation"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marcosh.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-26T11:45:31.000Z","updated_at":"2024-04-15T06:03:43.000Z","dependencies_parsed_at":"2022-08-26T10:20:31.406Z","dependency_job_id":null,"html_url":"https://github.com/marcosh/php-validation-dsl","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcosh%2Fphp-validation-dsl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcosh%2Fphp-validation-dsl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcosh%2Fphp-validation-dsl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcosh%2Fphp-validation-dsl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marcosh","download_url":"https://codeload.github.com/marcosh/php-validation-dsl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223612046,"owners_count":17173566,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dsl","functional","immutability","php","validation"],"created_at":"2024-08-01T20:01:28.778Z","updated_at":"2024-11-08T00:31:39.204Z","avatar_url":"https://github.com/marcosh.png","language":"PHP","funding_links":[],"categories":["PHP"],"sub_categories":[],"readme":"# php-validation-dsl\n\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/marcosh/php-validation-dsl/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/marcosh/php-validation-dsl/?branch=master)\n[![Code Climate](https://codeclimate.com/github/marcosh/php-validation-dsl/badges/gpa.svg)](https://codeclimate.com/github/marcosh/php-validation-dsl)\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/bdc36ac4877946bfa4bfb85666bf4323)](https://www.codacy.com/app/marcosh/php-validation-dsl?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=marcosh/php-validation-dsl\u0026amp;utm_campaign=Badge_Grade)\n\nA library for validating generic data in a functional fashion.\n\n## Basic idea\n\nThe idea is pretty simple. All goes around the following interface\n\n```php\ninterface Validation\n{\n    public function validate($data): ValidationResult;\n}\n```\n\nwhere some `$data` comes in and a `ValidationResult` comes out.\n\nA `ValidationResult` is a sum type which could be either valid, containing some\nvalid `$data`, or invalid, containing some error messages.\n\nThis means that a validation could either succeed, and it that case you have\nthe valid result at your disposition, or if fails, and you have the error\nmessages to handle.\n\n### Immutability\n\nEverything is immutable, so once you have created a validator you can not\nmodify it, you can just create a new one.\n\nOn the other hand, immutability implies statelessness, and therefore you can\nreuse safely the same validator multiple times with different data.\n\n### Compositionality\n\nThe library provides two different mechanisms, which could be combined, which\nallow creating complex validators just putting simple ones together.\n\nIt goes without saying that you can create new validators to be used together\nwith the existing ones.\n\n## Basic validators\n\nThe library provides several basic validators to validate native data\nstructures. They all implement the `Validation` interface described above.\n\n| Name | Description |\n| ---- | ----------- |\n| `Errors` | always fails with a given set of errors |\n| `HasKey` | checks whether a given key is present in an array |\n| `HasNotKey`| checks whether a given key is not present in an array |\n| `InArray` | checks if a value in contained in an array |\n| `IsArray` | checks whether the provided data is an array |\n| `IsAsAsserted` | checks whether the data satisfies a provided assertion |\n| `IsBool` | checks if the data is a boolean |\n| `IsCallable` | checks if the data is a callable |\n| `IsFloat` | checks whether the data is a floating point number |\n| `IsGreaterThan` | checks if the data is greater than a provided lower bound |\n| `IsInstanceOf` | checks whether the data is an instance of a given class or interface |\n| `IsInteger` | checks if the data is an integer |\n| `IsIterable` | checks whether the data is iterable |\n| `IsLessThan` | checks if the data is lower than a provided upper bound |\n| `IsNotNull` | checks whether the data is not null |\n| `IsNull` | checks if the data is null |\n| `IsNumeric` | checks whether the data is numeric |\n| `IsObject` | checks if the data is an object |\n| `IsResource` | checks if the data is a resource |\n| `IsString` | checks whether the data is a string |\n| `NonEmpty` | checks whether the data is not empty |\n| `Regex` | checks if the data is a string which matches a provided regular expression |\n| `Valid`| always succeeds | \n\n## Context\n\nHow can you use a runtime value to validate some data, while you are creating\nyour validators at build time? Well, that's what the `context` is there for.\n\nLet's provide a motivating example for this: suppose we want to write a\nvalidator to check whether we are violating a uniqueness condition while\nupdating a member in a collection. We will need to check it the data already\nexist in our collection, excluding the record we are currently updating.\nSo we will need, beyond the data themselves, an identifier of the record which\nwe are currently updating. We can pass this information in the context. Then\nthe validator could look like\n\n```php\nclass CheckDuplicateExceptCurrentRecord\n{\n    private $recordRepository;\n    \n    public function __construct($recordRepository)\n    {\n        $this-\u003erecordRepository = $recordRepository;\n    }\n\n    public function validate($data, $context)\n    {\n        if ($recordRepository-\u003econtainsDataExcludingRecord($data, $context['id'])) {\n            return ValidationResult::errors(['DUPLICATE RECORD']);\n        }\n        \n        return ValidationResult::valid($data);\n    }\n}\n```\n\nThen we can use our validator as follows\n\n```php\n$validator = new CheckDuplicateExceptCurrentRecord($recordRepository);\n\n$validator-\u003evalidate($data, ['id' =\u003e $currentRecordId]);\n```\n\n## Custom error formatters\n\nThe library itself does not want to impose how error messages should be\nstructured and formatted. Therefore it allows the user to define his own error\nmessages and his own error messages structure.\n\nTo do this every validator included in the library may be built using a custom\nerror formatter.\n\nThe error formatter is nothing else but a callable which receives as input all\nthe data known by the validator. Often this arguments will be just the data\nwhich need to be validated, but sometimes the error formatter could receive\nalso the configuration parameters of the validator.\n\nFor example, the `IsInstanceOf` validator has a named constructor\n\n```php\npublic static function withClassNameAndFormatter(\n    string $className,\n    callable $errorFormatter\n):\n```\n\nwhere the `$errorFormatter` needs to be a callable receiving as parameters the\n`$className` and the validation `$data`. For example we could use it as follows\n\n```php\n$myValidator = IsInstanceOf::withClassNameAndFormatter(\n    Foo::class,\n    function ($className, $data) {\n        return [\n            sprintf(\n                'The data %s is not an instance of %s',\n                json_encode($data),\n                $className\n            )\n        ];\n    }\n);\n```\n\n### Translators\n\nOne specific usage of custom error formatters it translating the error\nmessages.\n\nEvery validator has also a named constructor receiving a `Translator` to\ntranslate the library-defined error messages.\n\nIf you don't want to specify the translator for every single validator you\ndefine, there is also a `TranslateErrors` combinators which translates all the\nstrings present in the return message.\n\n## Creating more complex validators\n\nWe mentioned above that compositionality is one one the main ideas beyond this\nlibrary. In fact, we provide not one but two mechanisms to create complex\nvalidators starting from basic ones:\n\n- using [combinators](/src/Combinator); a combinator is simply a function which\ncreates a new validator starting from simpler ones in a very well defined way.\nThe combinators this library provides are\n\n| Name | Description |\n| ---- | ----------- |\n| `All` | checks whether all the provided validators on the same data |\n| `Any` | checks if at least one of the provided validators succeed |\n| `AnyElement` | checks if at least one element of an array satisfies a validator |\n| `Apply` | applies a validated function |\n| `Bind` | applies a function which returns a `ValidationResult` |\n| `EveryElement` | checks if every element of an array satisfies a validator |\n| `Focus` | changes the focus of the validation |\n| `Map` | applies a function to the validated result |\n| `MapErrors` | applies a function to the error messages |\n| `Sequence` | checks one validator after the other, exiting as soon as one fails |\n| `TranslateErrors` | translates the error messages |\n\n- using the `lift`, the `sdo` and the `fdo` functions provided in\n[Result/functions.php](src/Result/functions.php) to mimic applicative and\nmonadic validation used in functional programming.\n\nIn the following sections we provide examples on how to use these two\napproaches.\n\n## Using combinators\n\nSuppose you want to validate some data with the following format\n\n```php\n[\n    'name' =\u003e ... // non empty string\n    'age' =\u003e ... // non-negative integer\n]\n```\n\nTo describe what we would like to check in plain text, we need to verify that:\n\n- the data we receive are an array\n- we have a `name` field and it should be a non-empty string\n- we have an `age` field and it should be a positive integer\n\nThe validator which does this check should look like:\n\n```php\nSequence::validations([\n    new IsArray(),\n    All::validations([\n        Sequence::validations([\n            HasKey::withKey('name'),\n            Focus::on(\n                function ($data) {\n                    return $data['name'];\n                },\n                Sequence::validations([\n                    new IsString(),\n                    new NonEmpty()\n                ])\n            )\n        ]),\n        Sequence::validations([\n            HasKey::withKey('age'),\n            Focus::on(\n                function ($data) {\n                    return $data['age'];\n                },\n                Sequence::validations([\n                    new IsInteger(),\n                    IsGreaterThan::withBound(0)\n                ])\n            )\n        ])\n    ])\n]);\n```\n\nLet's go through it step by step to understand every single piece.\n\nWe start with a `Sequence` validator. Its semantic is that it is going to\nperform a series of validation sequentially, one after the other, returning an\nerror as soon as one fails.\n\nIn our case we start by checking that our data are an array, with the `IsArray`\nvalidator. Once we know we have an array, we can check if the two required\nfields exist. We can do these two operations independently, and, if they both\nfail, we want both the error messages. We use the `All` validator exactly for\nthis, to say that all the listed conditions need to be verified and that we\nwant all the error messages.\n\nAt this point we have the validations for `name` and the validations for `age`.\nFor the former, we first check that the `name` key is present, with the\n`HasKey` validator. Then we want to validate the value of the `name` key; to do\nthis we need to focus our attention not on the whole data structure, but just\non the single value. We use the `Focus` validator to specify a callable which\nallows to inspect the specific value. At this point we use `Sequence` again to\ncheck that the value is a string, using the `IsString` validator, and to assert\nthat it is not empty, with the `NonEmpty` validator.\n\nFor the `age` field, we do something similar. The only difference is that we\ncheck if it is an integer with the `IsInteger` validator and we use the\n`IsAsAsserted` validator, built with a user defined callable, to check that the\nvalue is a positive integer.\n\nThis example explains how to use several combinators to create complex\nvalidators. Actually, the specific validator we are considering here could be\nwritten more simply as\n\n```php\nAssociative::validations([\n    'name' =\u003e Sequence::validations([\n        new IsString(),\n        new NonEmpty()\n    ]),\n    'age' =\u003e Sequence::validations([\n        new IsInteger(),\n        IsGreaterThan::withBound(0)\n    ])\n]);\n```\n\nremoving a lot of boilerplate form the client code and allowing to concentrate\non what is specific for the validation at hand.\n\n## Applicative and monadic validation\n\nApplicative and monadic validations are common techniques in functional\nprogramming. Both consists in having several validators, applying them, and\ncombining their results. The main difference between them is how failure is\nhandled.\n\nWith the applicative style, all validators are computed independently and,\nin case of failure, all errors are returned. This is useful when you need\nto validate independent data and retrieve all the errors encountered.\n\nWith the monadic style, validators are applied sequentially, and the result of\na validator is provided as input for the next one. This is needed whenever\na validation depends on the result of a previous validation.\n\nProbably in this case some code says more that a lot of vague words. You can\ncheck yourself how applicative and monadic validation work looking at the\n[`ApplicativeMonadicSpec` tests](spec/Example/ApplicativeMonadicSpec.php).\n\n## How to use this library\n\nThis library is not build to be a ready-made artifact that you can install and\nstart using immediately. On the contrary it provides just some basic elements\nwhich you can use to easily build your own validators.\n\nThe idea is that everyone could create his own library of validators, specific\nfor his own domain and use case, composing the basic validators and custom ones\nwith the help of the provided combinators.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcosh%2Fphp-validation-dsl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcosh%2Fphp-validation-dsl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcosh%2Fphp-validation-dsl/lists"}