{"id":23219118,"url":"https://github.com/b2pweb/bdf-form","last_synced_at":"2025-08-19T08:32:45.048Z","repository":{"id":44660900,"uuid":"329572858","full_name":"b2pweb/bdf-form","owner":"b2pweb","description":"Simple and flexible PHP form library","archived":false,"fork":false,"pushed_at":"2025-04-04T09:13:19.000Z","size":518,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"1.6","last_synced_at":"2025-07-12T06:48:52.619Z","etag":null,"topics":["form","form-builder"],"latest_commit_sha":null,"homepage":"","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/b2pweb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-01-14T09:52:54.000Z","updated_at":"2025-04-04T08:11:13.000Z","dependencies_parsed_at":"2024-03-04T10:52:56.104Z","dependency_job_id":"b2b70518-c95a-4e02-ae36-588e898d811a","html_url":"https://github.com/b2pweb/bdf-form","commit_stats":{"total_commits":91,"total_committers":2,"mean_commits":45.5,"dds":0.01098901098901095,"last_synced_commit":"09e338811c94eb3e25028e817e71fbfb68c76ce3"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/b2pweb/bdf-form","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b2pweb%2Fbdf-form","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b2pweb%2Fbdf-form/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b2pweb%2Fbdf-form/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b2pweb%2Fbdf-form/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/b2pweb","download_url":"https://codeload.github.com/b2pweb/bdf-form/tar.gz/refs/heads/1.6","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b2pweb%2Fbdf-form/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271121770,"owners_count":24702871,"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","status":"online","status_checked_at":"2025-08-19T02:00:09.176Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["form","form-builder"],"created_at":"2024-12-18T21:19:20.362Z","updated_at":"2025-08-19T08:32:45.035Z","avatar_url":"https://github.com/b2pweb.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# BDF Form\n\nLibrary for handle form, and request validation.\n\n[![build](https://github.com/b2pweb/bdf-form/actions/workflows/php.yml/badge.svg)](https://github.com/b2pweb/bdf-form/actions/workflows/php.yml)\n[![codecov](https://codecov.io/github/b2pweb/bdf-form/branch/1.6/graph/badge.svg?token=VOFSPEWYKX)](https://app.codecov.io/github/b2pweb/bdf-form)\n[![Packagist Version](https://img.shields.io/packagist/v/b2pweb/bdf-form.svg)](https://packagist.org/packages/b2pweb/bdf-form)\n[![Total Downloads](https://img.shields.io/packagist/dt/b2pweb/bdf-form.svg)](https://packagist.org/packages/b2pweb/bdf-form)\n[![Type Coverage](https://shepherd.dev/github/b2pweb/bdf-form/coverage.svg)](https://shepherd.dev/github/b2pweb/bdf-form)\n\n## Table of content\n\n- [Installation using composer](#installation-using-composer)\n- [Basic usage](#basic-usage)\n- [Handle entities](#handle-entities)\n- [Transformation process](#transformation-process)\n- [Embedded and array](#embedded-and-array)\n- [Field path and dependencies](#field-path-and-dependencies)\n- [Choices](#choices)\n- [Buttons](#buttons)\n- [Elements](#elements)\n    - [StringElement](#stringelement)\n        - [Email](#email)\n        - [Url](#url)\n    - [IntegerElement](#integerelement)\n    - [FloatElement](#floatelement)\n    - [BooleanElement](#booleanelement)\n    - [DateTimeElement](#datetimeelement)\n    - [PhoneElement](#phoneelement)\n    - [CsrfElement](#csrfelement)\n    - [AnyElement](#anyelement)\n- [Create a custom element](#create-a-custom-element)\n    - [Using custom form](#using-custom-form)\n    - [Using LeafElement](#using-leafelement)\n    - [Usage](#usage)\n    - [Custom child builder](#custom-child-builder)\n- [Error Handling](#error-handling)\n    - [Simple usage](#simple-usage)\n    - [Printer](#printer)\n\n## Installation using composer\n\n```\ncomposer require b2pweb/bdf-form\n```\n\n## Basic usage\n\nTo create a form, simply extends the class [`CustomForm`](src/Custom/CustomForm.php) and implements method `CustomForm::configure()` :\n\n```php\n\u003c?php\n\nnamespace App\\Form;\n\nuse Bdf\\Form\\Aggregate\\FormBuilderInterface;\nuse Bdf\\Form\\Custom\\CustomForm;\n\nclass LoginForm extends CustomForm\n{\n    protected function configure(FormBuilderInterface $builder) : void\n    {\n        // Register inputs using builder\n        // required() specify than the input value cannot be empty\n        // setter() specify that the value will be exported when calling $form-\u003evalue()\n        $builder-\u003estring('username')-\u003erequired()-\u003esetter();\n        $builder-\u003estring('password')-\u003erequired()-\u003esetter();\n\n        // A button can also be declared (useful for handle multiple actions in one form)\n        $builder-\u003esubmit('login');\n    }\n}\n```\n\nTo display the form, call the `ElementInterface::view()` method on the form object, and use the view object :\n\n```php\n\u003c?php\n// Instantiate the form (a container can be use for handle dependency injection)\n$form = new LoginForm();\n$view = $form-\u003eview(); // Get the form view\n\n?\u003e\n\n\u003cform method=\"post\" action=\"login.php\"\u003e\n    \u003c!-- Use array access for get form elements --\u003e\n    \u003c!-- The onError() method will return the parameter only if the element is on error. This method also supports a callback as parameter --\u003e\n    \u003cdiv class=\"input-group\u003c?php echo $view['username']-\u003eonError(' has-error'); ?\u003e\"\u003e\n        \u003clabel for=\"login-username\"\u003eUsername\u003c/label\u003e\n        \u003c!-- You can configure attributes using magic method call : here it will add class=\"form-control\" id=\"login-username\" --\u003e\n        \u003c!-- The view element can be transformed to string. The input html element, the value and the name will be renderer --\u003e\n        \u003c?php echo $view['username']-\u003eclass('form-control')-\u003eid('login-username'); ?\u003e\n        \u003c!-- Render the error message --\u003e\n        \u003cdiv class=\"form-control-feedback\"\u003e\u003c?php echo $view['username']-\u003eerror(); ?\u003e\u003c/div\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"input-group\u003c?php echo $view['password']-\u003eonError(' has-error'); ?\u003e\"\u003e\n        \u003clabel for=\"login-password\"\u003ePassword\u003c/label\u003e\n        \u003c!-- If there is a conflict with a method name for add an attribute, you can use the method set() --\u003e\n        \u003c?php echo $view['password']-\u003eclass('form-control')-\u003eid('login-password')-\u003eset('type', 'password'); ?\u003e\n        \u003cdiv class=\"form-control-feedback\"\u003e\u003c?php echo $view['password']-\u003eerror(); ?\u003e\u003c/div\u003e\n    \u003c/div\u003e\n    \n    \u003c!-- Render the button --\u003e\n    \u003c?php echo $view['login']-\u003eclass('btn btn-primary')-\u003einner('Login'); ?\u003e\n\u003c/form\u003e\n```\n\nNow, you can submit data to the form, and perform validation :\n\n```php\n\u003c?php\n\n// Instantiate the form (a container can be use for handle dependency injection)\n$form = new LoginForm();\n\n// Submit and check if the form is valid\nif (!$form-\u003esubmit($_POST)-\u003evalid()) {\n    // The form has an error : use `ElementInterface::error()` to get the error and render it\n    echo 'Error : ', $form-\u003eerror();\n    return;\n}\n\n// The form is valid : get the value\n$credentials = $form-\u003evalue();\n\n// $credentials is an array with elements values \nperformLogin($credentials['username'], $credentials['password']);\n```\n\n## Handle entities\n\nThe form system can be use to import, create or fill an entity using accessors :\n- For `FormInterface::import()` the entity, use `ChildInterface::getter()` on the corresponding field. This method will use [`Getter`](src/PropertyAccess/Getter.php) as extractor.\n- For fill an entity, using `FormInterface::attach()` followed by `FormInterface::value()`, use `ChildInterface::setter()`. This method will use [`Setter`](src/PropertyAccess/Setter.php) as hydrator.\n- For create a new instance of the entity, using `FormInterface::value()`, without `attach()`, use `FormBuilderInterface::generates()`. This method will use [`ValueGenerator`](src/Aggregate/Value/ValueGenerator.php).\n\nDeclaration :\n\n```php\n\u003c?php\n\n// Declare the entity\n// The properties should be public, or has public accessors to be handled by the form\nclass Person\n{\n    /** @var string */\n    public $firstName;\n    /** @var string */\n    public $lastName;\n    /** @var DateTimeInterface|null */\n    public $birthDate;\n    /** @var Country|null */\n    public $country;\n}\n\nclass PersonForm extends \\Bdf\\Form\\Custom\\CustomForm\n{\n    protected function configure(\\Bdf\\Form\\Aggregate\\FormBuilderInterface $builder) : void\n    {\n        // Define that PersonForm::value() should return a Person instance \n        $builder-\u003egenerates(Person::class);\n        \n        // Declare fields with getter and setter\n        $builder-\u003estring('firstName')-\u003erequired()-\u003egetter()-\u003esetter();\n        $builder-\u003estring('lastName')-\u003erequired()-\u003egetter()-\u003esetter();\n        $builder-\u003edateTime('birthDate')-\u003eimmutable()-\u003egetter()-\u003esetter();\n        \n        // Custom transformer can be declared with a callback as first parameter on getter() and setter() methods\n        $builder-\u003estring('country')\n            -\u003egetter(function (Country $value) { return $value-\u003ecode; })\n            -\u003esetter(function (string $value) { return Country::findByCode($value); })\n        ;\n    }\n}\n```\n\nUsage :\n\n```php\n\u003c?php\n\nclass PersonController extends Controller\n{\n    private $repository;\n    \n    // Get a form view with entity values\n    public function editForm($request)\n    {\n        // Get the entity\n        $person = $this-\u003erepository-\u003efind($request-\u003equery-\u003eget('id'));\n        \n        // Create the form, import the entity data, and create the view object\n        $form = new PersonForm();\n        $view = $form-\u003eimport($person)-\u003eview();\n        \n        // The form view can be used: fields values are set\n        return $this-\u003erender('person/form', ['form' =\u003e $view]);\n    }\n    \n    // Use the form to create the entity\n    public function create($request)\n    {\n        // Get the form instance\n        $form = new PersonForm();\n        \n        // Submit form data\n        if (!$form-\u003esubmit($request-\u003epost())-\u003evalid()) {\n            throw new FormError($form-\u003eerror());\n        }\n        \n        // $form-\u003evalue() will return the filled entity\n        $this-\u003erepository-\u003einsert($form-\u003evalue());\n    }\n    \n    // Update an existent entity: simply attach the entity to fill\n    public function update($request)\n    {\n        // Get the entity\n        $person = $this-\u003erepository-\u003efind($request-\u003equery-\u003eget('id'));\n        \n        // Get the form instance and attach the entity to update\n        $form = new PersonForm();\n        $form-\u003eattach($person);\n\n        // Submit form data\n        if (!$form-\u003esubmit($request-\u003epost())-\u003evalid()) {\n            throw new FormError($form-\u003eerror());\n        }\n        \n        // $form-\u003evalue() will return the filled entity\n        $this-\u003erepository-\u003einsert($form-\u003evalue());\n    }\n    \n    // Works like update, but apply only provided fields (HTTP PATCH method)\n    // The entity must be import()'ed instead of attach()'ed\n    public function patch($request)\n    {\n        // Get the entity\n        $person = $this-\u003erepository-\u003efind($request-\u003equery-\u003eget('id'));\n        \n        // Get the form instance and import the entity to patch\n        $form = new PersonForm();\n        $form-\u003eimport($person);\n\n        // Submit form data\n        if (!$form-\u003epatch($request-\u003epost())-\u003evalid()) {\n            throw new FormError($form-\u003eerror());\n        }\n        \n        // $form-\u003evalue() will return the filled entity\n        $this-\u003erepository-\u003einsert($form-\u003evalue());\n    }\n}\n```\n\n## Transformation process\n\nHere a description, step by step, of the transformation process, from HTTP value to model value.\nThis process is reversible to generate HTTP value from model.\n\n\u003e Note: This example is for leaf element contained into a form. \n\u003e For an embedded for or array, simply replace the leaf element process by the form process.\n\n### 1 - Submit to buttons (scope: RootForm)\n\nThe first step perform by the `RootForm` is to check the submit buttons. \nIf the HTTP data contains the button name, and the value match with the configured one, the button is marked as clicked.\n\n\u003e Note: in reverse process, the clicked button value will be added to HTTP value\n\nSee :\n- `ButtonInterface::submit()` \n- `ButtonInterface::clicked()`\n- `RootFormInterface::submitButton()`\n\n### 2 - Call transformers of the form (scope: Form)\n\nTransformers of the container form are called. There are used to normalize input HTTP data to usable array value.\n\n\u003e Note: the transformers are called in reverse order (i.e. last registered is first executed) when transform from HTTP to PHP\n\u003e and called in order for PHP to HTTP transformation\n\n```php\n// Declare a transformer\nclass JsonTransformer implements \\Bdf\\Form\\Transformer\\TransformerInterface\n{\n    public function transformToHttp($value,\\Bdf\\Form\\ElementInterface $input)\n    {\n        return json_encode($value);\n    }\n\n    public function transformFromHttp($value,\\Bdf\\Form\\ElementInterface $input)\n    {\n        return json_decode($value, true);\n    }\n}\n\nclass MyForm extends CustomForm\n{\n    protected function configure(FormBuilderInterface $builder): void\n    {\n        // Transform JSON input to associative array\n        $builder-\u003etransformer(new JsonTransformer());\n    }\n}\n```\n\nSee:\n- `TransformerInterface`\n- `FormBuilderInterface::transformer()`\n\n### 3 - Extract the HTTP field value (scope: Child)\n\nAt this step, the normalized HTTP value is passed to the child, and the current field value is extracted.\nIf the value is not available, null is returned.\n\nThere is two extraction strategy :\n- Array offset: This is the default strategy. Extract the field value using a simple array access like `$httpValue[$name]`. By default the HTTP field name is same as the child name.\n- Array prefix: This strategy can only be used for aggregate elements like for or array. Filter the HTTP value, and keep only fields which starts with the given prefix.\n\nSee:\n- `HttpFieldsInterface::extract()`\n- `ChildBuilder::httpFields()`\n- `ChildBuilder::prefix()`\n- `ChildInterface::submit()`\n \n### 4 - Apply filters (scope: Child)\n\nOnce the field value is extracted, filters are applied. There are used for normalize and remove illegal values. \nIt's a destructive operation by definition (cannot be reversed), unlike transformers. There can be used for perform `trim` or `array_filter`.\n\n\u003e Note: Unlike transformers, filters are only applied during transformation from HTTP to PHP.\n\u003e Do not use if it's a \"view\" operation, like decoding a string.\n\n```php\n// Filter for keep only alpha numeric characters\nclass AlphaNumFilter implements \\Bdf\\Form\\Filter\\FilterInterface\n{\n    public function filter($value,\\Bdf\\Form\\Child\\ChildInterface $input,$default)\n    {\n        if (!is_string($value)) {\n            return null;\n        }\n\n        return preg_replace('/[^a-z0-9]/i', '', $value);\n    }\n}\n\nclass MyForm extends CustomForm\n{\n    protected function configure(FormBuilderInterface $builder): void\n    {\n        $builder-\u003estring('foo')-\u003efilter(new AlphaNumFilter())-\u003esetter();\n    }\n}\n\n$form = new MyForm();\n$form-\u003esubmit(['foo' =\u003e '$$$abc123___']);\n$form-\u003evalue()['foo'] === 'abc123'; // The string is filtered\n```\n\nSee:\n- `FilterInterface`\n- `ChildBuilderInterface::filter()`\n\n### 5 - Set default value (scope: Child)\n\nSet the default value is the filtered field value is considered as empty and a default value is provided.\nA value is empty is it's an empty string `''` or array `[]`, or it's  `null`. `0`, `0.0` or `false` are not considered as empty.\nIf not default value is given, the filtered value will be used.\n\n\u003e Note: To set a default value, you should call `ChildBuilderInterface::default()` with PHP value\n\nSee:\n- `HttpValue::orDefault()`\n- `ChildBuilderInterface::default()`\n\n### 6 - Call element transformers (scope: Element)\n\nWorks like Form transformers (step 2), but on the element value.\nIf a transformer throws an exception, the submit process will be stopped, the raw HTTP value will be kept,\nand the element will be marked as invalid with the exception message as error.\n\nThe transformer exception behavior can be changed on the `ElementBuilder`. \nIf the exception is ignored by calling `ignoreTransformerException()` on the builder, the validation process will be performed on the raw HTTP value.\n\n```php\n// Declare a transformer\nclass Base64Transformer implements \\Bdf\\Form\\Transformer\\TransformerInterface\n{\n    public function transformToHttp($value,\\Bdf\\Form\\ElementInterface $input)\n    {\n        return base64_encode($value);\n    }\n\n    public function transformFromHttp($value,\\Bdf\\Form\\ElementInterface $input)\n    {\n        $value = base64_decode($value);\n        \n        // Throw exception on invalid value\n        if ($value === false) {\n            throw new InvalidArgumentException('Invalid base64 data');\n        }\n\n        return $value;\n    }\n}\n\nclass MyForm extends CustomForm\n{\n    protected function configure(FormBuilderInterface $builder): void\n    {\n        // \"foo\" is a base64 string input\n        $builder\n            -\u003estring('foo')\n            -\u003etransformer(new Base64Transformer())\n            -\u003etransformerErrorMessage('Expecting base 64 data') // Define custom transformer error code and message\n            -\u003etransformerErrorCode('INVALID_BASE64_ERROR')\n        ;\n    }\n}\n```\n\nSee:\n- `ElementBuilderInterface::transformer()`\n- `ValidatorBuilderTrait::ignoreTransformerException()`\n- `ValidatorBuilderTrait::transformerErrorMessage()`\n- `ValidatorBuilderTrait::transformerErrorCode()`\n\n### 7 - Cast to PHP value (scope: Element)\n\nThe value is converted from HTTP value to usable PHP value, like a cast to int on `IntegerElement`.\n\n\u003e Note: this step is only performed on `LeafElement` implementations\n\nSee:\n- `LeafElement::toPhp()`\n- `LeafElement::fromPhp()`\n\n### 8 - Validation (scope: Element)\n\nValidate the PHP value of the element, using constraints.\n\nSee:\n- `ElementInterface::error()`\n- `ElementInterface::valid()`\n- `ElementBuilderInterface::satisfy()`\n\n### 9 - Generate the form value (scope: Form)\n\nCreate the entity to fill by form values.\n\nSee:\n- `ValueGeneratorInterface`\n- `FormInterface::value()`\n- `FormBuilderInterface::generator()`\n- `FormBuilderInterface::generates()`\n\n### 10 - Apply model transformer (scope: Child)\n\nCall the model transformer, for transform input data to model data.\n\n```php\nclass MyForm extends CustomForm\n{\n    protected function configure(FormBuilderInterface $builder): void\n    {\n        // The date should be saved as timestamp on the entity\n        $builder\n            -\u003edateTime('date')\n            -\u003esaveAsTimestamp()\n            -\u003esetter()\n        ;\n\n        // Save data as mongodb Binary        \n        $builder\n            -\u003estring('data')\n            -\u003emodelTransformer(function ($value, $input, $toModel) {\n                return $toModel ? new Binary($value, Binary::TYPE_GENERIC) : $value-\u003egetData();\n            })\n            -\u003esetter()\n        ;\n    }\n}\n```\n\nSee:\n- `ChildBuilderInterface::modelTransformer()`\n\n### 11 - Call accessor (scope: Child)\n\nThe accessor is used to fill the entity (in case of HTTP to PHP), or form import from entity (in case of PHP to form).\n\nSee:\n- `ChildBuilder::setter()`\n- `ChildBuilder::getter()`\n- `ChildBuilder::hydrator()`\n- `ChildBuilder::extractor()`\n\n### 12 - Validate the form value (scope: Form)\n\nOnce the value is hydrated, it will be validated by the form constraints.\n\n```php\nclass MyForm extends CustomForm\n{\n    protected function configure(FormBuilderInterface $builder): void\n    {\n        $builder-\u003egenerates(MyEntity::class);\n\n        $builder-\u003estring('foo')-\u003esetter();\n        $builder-\u003estring('bar')-\u003esetter();\n        \n        $builder-\u003esatisfy(function (MyEntity $entity) {\n            // Validate the hydrated entity\n            if (!$entity-\u003eisValid()) {\n                return 'Invalid entity';\n            }\n        });\n    }\n}\n```\n\nSee:\n- `FormInterface::valid()`\n- `FormInterface::error()`\n- `FormBuilderInterface::satisfy()`\n\n## Embedded and array\n\nComplex form structure can be created using embedded form and generic array element.\nEmbedded form is useful for reuse a form into another.\n\n```php\n\u003c?php \n\nclass UserForm extends \\Bdf\\Form\\Custom\\CustomForm\n{\n    protected function configure(\\Bdf\\Form\\Aggregate\\FormBuilderInterface $builder): void\n    {\n        // Define a sub-form \"credentials\", which generates a Credentials object\n        $builder-\u003eembedded('credentials', function (\\Bdf\\Form\\Child\\ChildBuilderInterface $builder) {\n            // $builder is type of ChildBuilderInterface, but forward call to FormBuilderInterface\n            // So it can be used like a simple form builder\n            \n            $builder-\u003egenerates(Credentials::class);\n            $builder-\u003estring('username')-\u003erequired()-\u003elength(['min' =\u003e 3])-\u003egetter()-\u003esetter();\n            $builder-\u003estring('password')-\u003erequired()-\u003elength(['min' =\u003e 6])-\u003egetter()-\u003esetter();\n        });\n        \n        // Define an array of Address instances\n        $builder-\u003earray('addresses')-\u003eform(function (FormBuilderInterface $builder) {\n            $builder-\u003egenerates(Address::class);\n            $builder-\u003estring('address')-\u003erequired()-\u003egetter()-\u003esetter();\n            $builder-\u003estring('city')-\u003erequired()-\u003egetter()-\u003esetter();\n            $builder-\u003estring('zipcode')-\u003erequired()-\u003egetter()-\u003esetter();\n            $builder-\u003estring('country')-\u003erequired()-\u003egetter()-\u003esetter();\n        });\n\n        // embedded and leaf fields can be mixed on the same form \n        $builder-\u003estring('email')-\u003erequired()-\u003egetter()-\u003esetter();\n    }\n}\n```\n\nThis form will handle data like :\n\n```\n[\n    'credentials' =\u003e [\n        'username' =\u003e 'jdoe',\n        'password' =\u003e 'p@ssw04d'\n    ],\n    'addresses' =\u003e [\n        ['address' =\u003e '147 Avenue du Parc', 'city' =\u003e 'Villes-sur-Auzon', 'zipcode' =\u003e '84148', 'country' =\u003e 'FR'],\n        ['address' =\u003e '20 Rue de la paix', 'city' =\u003e 'Gordes', 'zipcode' =\u003e '84220', 'country' =\u003e 'FR'],\n    ],\n    'email' =\u003e 'jdoe@example.com',\n]\n```\n\nOr in HTTP format :\n\n```\ncredentials[username]=jdoe\n\u0026credentials[password]=p@ssw04d\n\u0026addresses[0][address]=147 Avenue du Parc\n\u0026addresses[0][city]=Villes-sur-Auzon\n\u0026addresses[0][zipcode]=84148\n\u0026addresses[0][country]=FR\n\u0026addresses[1][address]=20 Rue de la paix \n\u0026addresses[1][city]=Gordes\n\u0026addresses[1][zipcode]=84220 \n\u0026addresses[1][country]=FR\n\u0026email=jdoe@example.com\n```\n\nTo improve readability and reusability, each embedded form can be declared in its own class :\n\n\n```php\n\u003c?php \n\nclass CredentialsForm extends \\Bdf\\Form\\Custom\\CustomForm\n{\n    protected function configure(\\Bdf\\Form\\Aggregate\\FormBuilderInterface $builder) : void\n    {\n        $builder-\u003egenerates(Credentials::class);\n        $builder-\u003estring('username')-\u003erequired()-\u003elength(['min' =\u003e 3])-\u003egetter()-\u003esetter();\n        $builder-\u003estring('password')-\u003erequired()-\u003elength(['min' =\u003e 6])-\u003egetter()-\u003esetter();\n    }\n}\n\nclass AddressForm extends \\Bdf\\Form\\Custom\\CustomForm\n{\n    protected function configure(\\Bdf\\Form\\Aggregate\\FormBuilderInterface $builder) : void\n    {\n        $builder-\u003egenerates(Address::class);\n        $builder-\u003estring('address')-\u003erequired()-\u003egetter()-\u003esetter();\n        $builder-\u003estring('city')-\u003erequired()-\u003egetter()-\u003esetter();\n        $builder-\u003estring('zipcode')-\u003erequired()-\u003egetter()-\u003esetter();\n        $builder-\u003estring('country')-\u003erequired()-\u003egetter()-\u003esetter();\n    }\n}\n\nclass UserForm extends \\Bdf\\Form\\Custom\\CustomForm\n{\n    protected function configure(\\Bdf\\Form\\Aggregate\\FormBuilderInterface $builder) : void\n    {\n        // Simply define element with the embedded form class name\n        $builder-\u003eadd('credentials', CredentialsForm::class);\n        $builder-\u003earray('addresses', AddressForm::class);\n        $builder-\u003estring('email')-\u003erequired()-\u003egetter()-\u003esetter();\n    }\n}\n```\n\nYou can also \"flatten\" the HTTP fields by using `ChildBuilderInterface::prefix()`. \nThe embedded form will use a prefix instead of a sub-array.\n\n```php\n\u003c?php\n\nclass UserForm extends \\Bdf\\Form\\Custom\\CustomForm\n{\n    protected function configure(\\Bdf\\Form\\Aggregate\\FormBuilderInterface $builder) : void\n    {\n        // Simply define element with the embedded form class name\n        $builder-\u003eadd('credentials', CredentialsForm::class)-\u003eprefix();\n        $builder-\u003earray('addresses', AddressForm::class);\n        $builder-\u003estring('email')-\u003erequired()-\u003egetter()-\u003esetter();\n    }\n}\n```\n\nUsing prefix, the new data format is : \n\n```\n[\n    'credentials_username' =\u003e 'jdoe',\n    'credentials_password' =\u003e 'p@ssw04d'\n    'addresses' =\u003e [\n        ['address' =\u003e '147 Avenue du Parc', 'city' =\u003e 'Villes-sur-Auzon', 'zipcode' =\u003e '84148', 'country' =\u003e 'FR'],\n        ['address' =\u003e '20 Rue de la paix', 'city' =\u003e 'Gordes', 'zipcode' =\u003e '84220', 'country' =\u003e 'FR'],\n    ],\n    'email' =\u003e 'jdoe@example.com',\n]\n```\n\nOr in HTTP format :\n\n```\ncredentials_username=jdoe\n\u0026credentials_password=p@ssw04d\n\u0026addresses[0][address]=147 Avenue du Parc\n\u0026addresses[0][city]=Villes-sur-Auzon\n\u0026addresses[0][zipcode]=84148\n\u0026addresses[0][country]=FR\n\u0026addresses[1][address]=20 Rue de la paix \n\u0026addresses[1][city]=Gordes\n\u0026addresses[1][zipcode]=84220 \n\u0026addresses[1][country]=FR\n\u0026email=jdoe@example.com\n```\n\n## Field path and dependencies\n\nIn some cases, a field value should be validated or transformed using another field value.\nIt's for this goal that field dependencies are added : when a field depends on other, you can declare it using `ChildBuilderInterface::depends()`.\nField path are used to access to the specific field.\n\n\u003e Note: dependencies add complexity to the form, it's advisable to use a constraint on the parent form if possible.\n\nTo create a field path (and access to the desired field), you should use `FieldPath::parse()`, or [`FieldFinderTrait`](src/Util/FieldFinderTrait.php).\n\nThe format works like unix file system path, with `/` as field separator, `.` for the current field, and `..` for the parent.\nUse `/` at the start will define path as absolute.\nUnlike unix path, by default, the path starts from the parent of the field (i.e. equivalent to `../`).\n\nFormat: \n```\n[.|..|/] [fieldName] [/fieldName]...\n```\n\nWith :\n- `.` to start the path from the current element (and not from it's parent). The current element must be an aggregate element like a form to works\n- `..` to start the path from the parent of the current element. This is the default behavior, so it's not necessary to start with \"../\" the path\n- `/` is the fields separator. When used at the beginning of the path it means that the path is absolute (i.e. start from the root element)\n- `fieldName` is a field name. The name is the declared one, not the HTTP field name\n\nUsage:\n\n```php\n\u003c?php \n// Using \"low level\" FieldPath helper\nclass CredentialsForm extends \\Bdf\\Form\\Custom\\CustomForm\n{\n    protected function configure(\\Bdf\\Form\\Aggregate\\FormBuilderInterface $builder) : void\n    {\n        $builder-\u003estring('username');\n        $builder-\u003estring('password');\n        $builder-\u003estring('confirm')\n            -\u003edepends('password') // Password must be submitted before confirm\n            -\u003esatisfy(function ($value, \\Bdf\\Form\\ElementInterface $input) {\n                // Get sibling field value using FieldPath\n                // Note: with FieldPath, the path is relative to the parent of the current field\n                if ($value !== \\Bdf\\Form\\Util\\FieldPath::parse('password')-\u003evalue($input)) {\n                    return 'Confirm must be same as password';\n                }\n            })\n        ;\n    }\n}\n\n// Using FieldFinderTrait on custom form\nclass CredentialsForm extends \\Bdf\\Form\\Custom\\CustomForm\n{\n    use \\Bdf\\Form\\Util\\FieldFinderTrait;\n\n    protected function configure(\\Bdf\\Form\\Aggregate\\FormBuilderInterface $builder) : void\n    {\n        $builder-\u003estring('username');\n        $builder-\u003estring('password');\n        $builder-\u003estring('confirm')\n            -\u003edepends('password') // Password must be submitted before confirm\n            -\u003esatisfy(function ($value, \\Bdf\\Form\\ElementInterface $input) {\n                // Get sibling field value using findFieldValue\n                // Note: with FieldFinderTrait, the path is relative to the custom form\n                if ($value !== $this-\u003efindFieldValue('password')) {\n                    return 'Confirm must be same as password';\n                }\n            })\n        ;\n    }\n}\n```\n\nThe `FieldPath` can also be used outside the form, and with embedded forms :\n\n```php\nuse Bdf\\Form\\Util\\FieldPath;\n\n$form = new UserForm();\n\n// Find the username field, starting from the root\n// Start the expression with \".\" to not start the path from the parent of UserForm (which do not exists)\n$username = FieldPath::parse('./embedded/username')-\u003eresolve($form);\n\n// Also works from a \"leaf\" field\n$password = FieldPath::parse('password')-\u003eresolve($username);\n// Same as above\n$password = FieldPath::parse('../password')-\u003eresolve($username);\n\n// Absolute path : get \"email\" field of the root form\n$email = FieldPath::parse('/email')-\u003eresolve($username);\n```\n\n## Choices\n\nChoice system is use to allow only a set of values, like with HTML `\u003cselecte\u003e` element.\nTo define a choice, simply call `choice()` on the element builder, if supported. \nA label can be defined using the key of associative array for the list of available values.\n\n```php\n$builder-\u003estring('country')\n    -\u003echoices([\n        'France' =\u003e 'FR',\n        'United-Kingdom' =\u003e 'UK',\n        'Spain' =\u003e 'ES',\n    ])\n;\n```\n\nOnce defined, the view system will automatically transform simple input elements to `\u003cselect\u003e`.\nTo render manually the choice, you can also call `FieldViewInterface::choices()` to get choices array :\n\n```php\n\u003cselect name=\"\u003c?php echo $view-\u003ename(); ?\u003e\"\u003e\n \u003c?php foreach ($view-\u003echoices() as $choice): ?\u003e\n     \u003coption value=\"\u003c?php echo $choice-\u003evalue(); ?\u003e\"\u003c?php echo $choice-\u003eselected() ? ' selected' : ''; ?\u003e\u003e\u003c?php echo $choice-\u003elabel(); ?\u003e\u003c/option\u003e\n \u003c?php endforeach; ?\u003e\n\u003c/select\u003e\n```\n\n## Buttons\n\nSubmit button can be defined to handle multiple action on the same form.\n\nThe form :\n\n```php\n\u003c?php\n\nclass MyForm extends \\Bdf\\Form\\Custom\\CustomForm\n{\n    const SAVE_BTN = 'save';\n    const DELETE_BTN = 'delete';\n\n    protected function configure(\\Bdf\\Form\\Aggregate\\FormBuilderInterface $builder) : void\n    {\n        $builder-\u003estring('foo');\n        \n        // Define buttons\n        $builder-\u003esubmit(self::SAVE_BTN);\n        $builder-\u003esubmit(self::DELETE_BTN);\n    }\n}\n```\n\nThe view :\n\n```php\n\u003c?php \n$view = (new MyForm())-\u003eview();\n?\u003e\n\n\u003cform action=\"action.php\" method=\"post\"\u003e\n    \u003c?php echo $view['foo']; ?\u003e\n    \n    \u003c!-- Render the buttons. Use inner() to define the button text --\u003e\n    \u003c?php echo $view[MyForm::SAVE_BTN]-\u003einner('Save'); ?\u003e\n    \u003c?php echo $view[MyForm::DELETE_BTN]-\u003einner('Delete'); ?\u003e\n\u003c/form\u003e\n```\n\nThe controller :\n\n```php\n\u003c?php\n\n$form = new MyForm();\n\n// Submit the form \n$form-\u003esubmit($_POST);\n\n// Get the submitted button name\n// The submit button is handled by the root element\nswitch ($btn = $form-\u003eroot()-\u003esubmitButton() ? $btn-\u003ename() : null) {\n    case MyForm::SAVE_BTN:\n        doSave($form-\u003evalue());\n        break;\n\n    case MyForm::DELETE_BTN:\n        doDelete($form-\u003evalue());\n        break;\n        \n    default:\n        throw new Exception();\n}\n```\n\n## Elements\n\n### StringElement\n\n```php\n$builder-\u003estring('username')\n    -\u003elength(['min' =\u003e 3, 'max' =\u003e 32]) // Define length options\n    -\u003eregex('/[a-z0-9_-]+/i') // Define a validation regex\n;\n```\n\n#### Email\n\nString element with Email constraint\n\n```php\n$builder-\u003eemail('username')-\u003emode(Email::VALIDATION_MODE_HTML5);\n```\n\n#### Url\n\nString element with Url constraint\n\n```php\n$builder-\u003eurl('server')-\u003eprotocols('ftp', 'sftp');\n```\n\n### IntegerElement\n\n```php\n$builder-\u003einteger('number')\n    -\u003eposivite() // Same as -\u003emin(0)\n    -\u003emin(5) // The number must be \u003e= 5\n    -\u003emax(9999) // The element must be \u003c= 9999\n    -\u003egrouping(true) // The HTTP value will group values by thousands (i.e. 145 000 instead of 145000)\n    -\u003eraw(false) // If set to true, the input will be simply caster to int, and not transformed following the locale\n;\n```\n\n\n### FloatElement\n\n```php\n$builder-\u003efloat('number')\n    -\u003eposivite() // Same as -\u003emin(0)\n    -\u003emin(1.1) // The number must be \u003e= 1.1\n    -\u003emax(99.99) // The element must be \u003c= 99.99\n    -\u003egrouping(true) // The HTTP value will group values by thousands (i.e. 145 000 instead of 145000)\n    -\u003escale(2) // Only consider 2 digit on the decimal part\n    -\u003eraw(false) // If set to true, the input will be simply caster to int, and not transformed following the locale\n;\n```\n\n### BooleanElement\n\nHandle boolean value, like a checkbox.\nThe value will be considered as true if it's present, and is equals to the defined one (by default `1`).\n\n\u003e Note: in HTTP a falsy value is an absent value, so a default value cannot be defined on a boolean element.\n\u003e To define a \"view default\" value, use `ElementBuilderInterface::value()` instead of `ChildElementBuilder::default()`\n\nThe default renderer will render a `\u003cinput type=\"checkbox\" /\u003e` with the defined http value and checked state.\n\n```php\n$builder-\u003eboolean('enabled')\n    -\u003ehttpValue('enabled') // Define the requireds HTTP field value\n;\n```\n\n### DateTimeElement\n\n```php\n$builder-\u003edateTime('eventDate')\n    -\u003eclassName(Carbon::class) // Define a custom date class\n    -\u003eimmutable() // Same as -\u003eclassName(DateTimeImmutable::class)\n    -\u003eformat('d/m/Y H:i') // Define the parsed date format\n    -\u003etimezone('Europe/Paris') // Define the parse timezone. The element PHP value will be on this timezone\n    -\u003ebefore(new DateTime('+1 year')) // eventDate must be before next year\n    -\u003ebeforeField('eventDateEnd') // Compare the field value to a sibling field (eventDateEnd)\n    -\u003eafter(new DateTime()) // eventDate must be after now\n    -\u003eafterField('accessDate') // Compare the field value to a sibling field (accessDate)\n```\n\n### PhoneElement\n\nHandle phone number. The package `giggsey/libphonenumber-for-php` is required to use this element.\nThis element will not return a `string` but a `PhoneNumber` instance. \n\n```php\n$builder-\u003ephone('contact')\n    -\u003eregionResolver(function () {\n        return $this-\u003euser()-\u003ecountry(); // Resolve the phone region using a custom resolver\n    })\n    -\u003eregion('FR') // Force the region value for parse the phone number\n    -\u003eregionInput('address/country') // Use a sibling input for parse the number (do not forget to call `depends()`)\n    -\u003eallowInvalidNumber(true) // Do not check the phone number\n    -\u003evalidateNumber('My error message') // Enable validation, and define the validator options (here the error message)\n    -\u003esetter()-\u003esaveAsString() // Save the phone number as string on the entity\n;\n```\n\n### CsrfElement\n\nThis element allows to mitigate CSRF. The package `symfony/security-csrf` is required for this element.\nSome element methods are disallowed, like `import()`, any constraints, transformer or default.\nThe view will be rendered as `\u003cinput type=\"hidden\" /\u003e`.\n\n```php\n$builder-\u003ecsrf() // No need to set a name. by default \"_token\"\n    -\u003etokenId('my_token') // Define the token id. By default is use CsrfElement::class, but define a custom tokenId permit to not share CSRF token between forms\n    -\u003emessage('The token is invalid') // Define a custom error message\n    -\u003einvalidate(true) // The token is always invalidated after check, and should be regenerated\n;\n```\n\n### AnyElement\n\nAn element for handle any value types. This is useful for create an inline custom element.\nBut it's strongly discouraged : prefer use one of the native element, or create a custom one.\n\n```php\n$builder-\u003eadd('foo', AnyElement::class) // No helper method are present\n    -\u003esatisfy(function ($value) { ... }) // Configure the element\n    -\u003etransform(function ($value) { ... })\n;\n```\n\n## Create a custom element\n\nYou can declare custom elements to handle complex types, and reuse into any forms.\nThe examples bellow are for declare an element to handle [`UriInterface`](https://www.php-fig.org/psr/psr-7/#35-psrhttpmessageuriinterface) objects, created using PSR-17 [`UriFactoryInterface`](https://www.php-fig.org/psr/psr-17/#26-urifactoryinterface)\n\n### Using custom form\n\nYou can use a `CustomForm` to declare an element. \nThe advantage of this method is that it don't need to implements low level interfaces, and require only to extends the `CustomForm` class.\nBut it has the consequence of using more resources, has a lower flexibility, and cannot define a custom builder.\n\n```php\nuse Bdf\\Form\\Aggregate\\FormBuilderInterface;\nuse Bdf\\Form\\Custom\\CustomForm;\nuse Bdf\\Form\\ElementInterface;\nuse Bdf\\Form\\Transformer\\TransformerInterface;\n\n// Declare the element class\nclass UriElement extends CustomForm\n{\n    const INNER_ELEMENT = 'uri';\n\n    /**\n    * @var UriFactoryInterface \n    */\n    private $uriFactory;\n    \n    // Set the UriFactoryInterface at the constructor\n    public function __construct(?FormBuilderInterface $builder = null, ?UriFactoryInterface $uriFactory = null) \n    {\n        parent::__construct($builder);\n        \n        $this-\u003euriFactory = $uriFactory ?? new DefaultUriFactory(); // Default instance for the factory\n    }\n\n    protected function configure(FormBuilderInterface $builder) : void\n    {\n        // Define inner element to store value\n        // The URI is basically a string\n        $builder-\u003estring(self::INNER_ELEMENT);\n\n        // The UriElement is a form, and supports only array values\n        // Define a transformer to remap value to the inner element\n        $builder-\u003etransformer(new class implements TransformerInterface {\n            public function transformToHttp($value, ElementInterface $input)\n            {\n                // $value is an array of children value\n                // Return only the inner element value\n                return $value[UriElement::INNER_ELEMENT];\n            }\n            \n            public function transformFromHttp($value, ElementInterface $input)\n            {\n                // $value is the URI string\n                // Map it as array to the inner element\n                return [UriElement::INNER_ELEMENT =\u003e $value];\n            }           \n        });\n\n        // Define the value generator : parse the inner element value to UriInterface using the factory        \n        $builder-\u003egenerates(function () {\n            return $this-\u003euriFactory-\u003ecreateUri($this[self::INNER_ELEMENT]-\u003eelement()-\u003evalue());\n        });\n    }\n}\n```\n\n### Using LeafElement\n\nIf declaring an element using the `CustomForm` is not enough, or if you want to optimise this element, you can use the low level class [`LeafElement`](src/Leaf/LeafElement.php) to declare a custom element.\n\n```php\nuse Bdf\\Form\\Choice\\ChoiceInterface;\nuse Bdf\\Form\\Leaf\\LeafElement;\nuse Bdf\\Form\\Transformer\\TransformerInterface;\nuse Bdf\\Form\\Validator\\ValueValidatorInterface;\n\n// Declare the element using LeafElement class\nclass UriElement extends LeafElement\n{\n    /**\n     * @var UriFactory \n     */\n    private $uriFactory;\n\n    // Overrides constructor to add the factory\n    public function __construct(?ValueValidatorInterface $validator = null, ?TransformerInterface $transformer = null, ?ChoiceInterface $choices = null, ?UriFactory $uriFactory = null) \n    {\n        parent::__construct($validator, $transformer, $choices);\n        \n        $this-\u003euriFactory = $uriFactory ?? new DefaultUriFactory(); // Use default instance\n    }\n\n    // Parse the HTTP string value using the factory\n    protected function toPhp($httpValue): ?UriInterface\n    {\n        return $httpValue ? $this-\u003euriFactory-\u003ecreateUri($httpValue) : null;\n    }\n    \n    // Stringify the UriInterface instance\n    protected function toHttp($phpValue): ?string\n    {\n        return $phpValue === null ? null : (string) $phpValue;\n    }\n}\n\nuse Bdf\\Form\\AbstractElementBuilder;\nuse Bdf\\Form\\Choice\\ChoiceBuilderTrait;\nuse Bdf\\Form\\ElementInterface;\n\n// Declare the builder\nclass UriElementBuilder extends AbstractElementBuilder\n{\n    use ChoiceBuilderTrait; // Enable choices building\n    \n    /**\n     * @var UriFactory\n     */\n    private $uriFactory;\n    \n    public function __construct(?RegistryInterface $registry = null, ?UriFactory $uriFactory = null) \n    {\n        parent::__construct($registry);\n\n        $this-\u003euriFactory = $uriFactory ?? new DefaultUriFactory();\n    }\n    \n    // You can define custom builder methods for constrains or \n    public function host(string $hostName): self\n    {\n        return $this-\u003esatisfy(function (?UriInterface $uri) use($hostName) {\n            if ($uri-\u003egetHost() !== $hostName) {\n                return 'Invalid host name';\n            }\n        });\n    }\n\n    // Create the element\n    protected function createElement(ValueValidatorInterface $validator, TransformerInterface $transformer) : ElementInterface\n    {\n        return new UriElement($validator, $transformer, $this-\u003egetChoices(), $this-\u003euriFactory);\n    }\n}\n\n// Register the element builder on the registry\n// Use container to inject dependencies (here the UriFactoryInterface)\n$registry-\u003eregister(UriElement::class, function(Registry $registry) use($container) {\n    return new UriElementBuilder($registry, $container-\u003eget(UriFactoryInterface::class));\n});\n```\n\n### Usage\n\nTo use the custom element, simply call `FormBuilderInterface::add()` with the element class name as second parameter :\n\n```php\n$builder-\u003eadd('uri', UriElement::class);\n```\n\n### Custom child builder\n\nIn some case, defining a custom child builder can be relevant, like for register model transformers.\nTo declare the child, simply extends `ChildBuilder` class, and register to the `Registry` :\n\n```php\nclass MyCustomChildBuilder extends ChildBuilder\n{\n    public function __construct(string $name, ElementBuilderInterface $elementBuilder, RegistryInterface $registry = null)\n    {\n        parent::__construct($name, $elementBuilder, $registry);\n\n        // Add a filter provider\n        $this-\u003eaddFilterProvider([$this, 'provideFilter']);\n    }\n    \n    // Model transformer helper method\n    public function saveAsCustom()\n    {\n        return $this-\u003emodelTransformer(new MyCustomTransformer());\n    }\n    \n    // Provide default filter\n    protected function provideFilter()\n    {\n        return [new MyFilter()];\n    }\n}\n\n// Now you can register the child builder with the element builder\n$registry-\u003eregister(CustomElement::class, CustomElementBuilder::class, MyCustomChildBuilder::class);\n```\n\n## Error Handling\n\nWhen an error occurs on the form, a [`FormError`](src/Error/FormError.php) object is created with all errors.\n\n### Simple usage \n\nIf an error is on a child, use `FormError::children()` to get the error.\nIf there is no error on children, but on the parent element, use `FormError::global()` instead.\nTo get a simple string representation of errors, cast the errors to string.\nTo get an array representation in form [fieldName =\u003e error], use `FormError::toArray()`.\n\n```php\nif (!$form-\u003eerror()-\u003eempty()) {\n    // Simple render errors as string\n    return new Response('\u003cdiv class=\"alert alert-danger\"\u003e'.$form-\u003eerror().'\u003c/div\u003e');\n    \n    // For simple API error system, use toArray()\n    return new JsonReponse(['error' =\u003e $form-\u003eerror()-\u003etoArray()]);\n    \n    // Or use accessors\n    foreach ($form-\u003eerror()-\u003echildren() as $name =\u003e $error) {\n        echo $error-\u003efield().' : '.$error-\u003eglobal().' ('.$error-\u003ecode().')'.PHP_EOL;\n    }\n}\n```\n\n### Printer\n\nTo format errors with reusable way, a [`FormErrorPrinterInterface`](src/Error/FormErrorPrinterInterface.php) can be implemented.\n\n```php\n/**\n * Print errors in format [httpField =\u003e ['code' =\u003e errorCode, 'message' =\u003e errorMessage]]\n */\nclass ApiPrinter implements \\Bdf\\Form\\Error\\FormErrorPrinterInterface\n{\n    private $errors = [];\n    private $current;\n\n    // Define the error message for the current element\n    public function global(string $error) : void\n    {\n        $this-\u003eerrors[$this-\u003ecurrent]['message'] = $error;\n    }\n    \n    // Define the error code for the current element\n    public function code(string $code) : void\n    {\n        $this-\u003eerrors[$this-\u003ecurrent]['code'] = $code;\n    }\n    \n    // Define the error element http field\n    public function field(\\Bdf\\Form\\Child\\Http\\HttpFieldPath $field) : void\n    {\n        $this-\u003ecurrent = $field-\u003eget();\n    }\n    \n    // Iterate on element's children\n    public function child(string $name,\\Bdf\\Form\\Error\\FormError $error) : void\n    {\n        $error-\u003eprint($this);\n    }\n    \n    // Get all errors\n    public function print()\n    {\n        return $this-\u003eerrors;\n    }\n}\n\n// Usage\nreturn new JsonReponse(['errors' =\u003e $form-\u003eerror()-\u003eprint(new ApiPrinter())]);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fb2pweb%2Fbdf-form","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fb2pweb%2Fbdf-form","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fb2pweb%2Fbdf-form/lists"}