├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── composer.json
└── src
├── Constraints
├── OptionalLength6.php
├── OptionalLength6Validator.php
├── OptionalLength7.php
└── OptionalLength7Validator.php
├── Datalist.php
├── Factory.php
├── Form.php
├── Groups
├── Group.php
├── GroupCollection.php
├── InputGroup.php
├── MultipleGroupCollection.php
├── RadioGroup.php
└── SubmitGroup.php
├── InputInterface.php
├── Inputs
├── Checkbox.php
├── Color.php
├── Date.php
├── DatetimeLocal.php
├── Email.php
├── File.php
├── Hidden.php
├── Input.php
├── Month.php
├── Number.php
├── Password.php
├── Radio.php
├── Range.php
├── Search.php
├── Select.php
├── Submit.php
├── Tel.php
├── Text.php
├── Textarea.php
├── Time.php
├── Url.php
└── Week.php
├── Node.php
├── NodeInterface.php
├── Traits
└── HasOptionsTrait.php
├── ValidationError.php
├── ValidatorFactory.php
└── Validators
├── AcceptFile.php
├── Step.php
└── UploadedFile.php
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](http://keepachangelog.com/)
6 | and this project adheres to [Semantic Versioning](http://semver.org/).
7 |
8 | ## [7.0.0] - 2025-04-26
9 | ### Added
10 | - Support for PHP 8.4.
11 | - Support for `symfony/validator` v6 (LTS) and v7.
12 | - Support for custom validator factory `Factory::setValidator()`.
13 | - This allows to use `symfony/translations` for example to have translations.
14 | - See (examples/translations.php)[examples/translations.php].
15 | - Support for custom global error messages:
16 | ```php
17 | F::setErrorMessages([
18 | 'required' => 'The field is required',
19 | 'email' => 'The email is invalid',
20 | ]);
21 | ```
22 | - Support for required radio group:
23 | ```php
24 | $colors = F::radioGroup([
25 | 'red' => F::radio('Red', ['required' => true]),
26 | 'blue' => 'Blue',
27 | 'green' => 'Green',
28 | ]);
29 | ```
30 |
31 | ## Changed
32 | - Minimum requirement is `php >= 7.2`
33 | - Set minimum requirement of `symfony/validator` to v5.4
34 | - no active support anymore
35 | - [security fixes until 2028 though!](https://endoflife.date/symfony)
36 | - the pinned version to 5.4 is to avoid outdated and buggy versions that we cannot rely on.
37 |
38 |
39 | ## [6.1.2] - 2021-07-19
40 | ### Added
41 | - Added support for PHP 8
42 |
43 | ## [6.1.1] - 2021-01-13
44 | ### Fixed
45 | - Inputs with `multiple` attribute needs to force an array by appending a `[]` to the name [#89]
46 |
47 | ## [6.1.0] - 2020-11-14
48 | ### Added
49 | - Some phpdoc annotations to help IDEs [#81]
50 | - Elements that can contain options (`Select`, `Datalist`) have the `setOptgroups` method to define the optgroups.
51 | - The method `setOptions` can assign other attributes to the options [#83]
52 | ```php
53 | $select->setOptions([
54 | 'value1' => [
55 | 'label' => 'Value label',
56 | 'disabled' => true
57 | ]
58 | ])
59 | ```
60 |
61 | ### Removed
62 | - BREAKING: Ability to add optgroups with the `setOptions` method of select and datalist elements. Use `setOptgroups` instead
63 |
64 | ### Fixed
65 | - Validate step attribute with decimal values [#87]
66 |
67 | ## [6.0.1] - 2019-03-24
68 | ### Fixed
69 | - Error on throw `InvalidArgumentException` inside other namespace [#79]
70 | - Added php7.3 to travis
71 |
72 | ## 6.0.0 - 2018-12-24
73 | This library was rewritten and a lot of breaking changes were included.
74 |
75 | ### Added
76 | - This changelog
77 |
78 | ### Changed
79 | - Minimum requirement is `php >= 7.1`
80 | - Use of `symfony/validator` to validate the values
81 | - Removed a lot of logic and html features. Focus only in inputs and data structure.
82 | - Better error messages and easy to customize and translate
83 | - Replaced jQuery inspired API for a DOM inspired API. For example, use `$input->getAttribute()` and `$input->setAttribute()` instead `$input->attr()`.
84 | - Removed magic methods to add attributes in benefit of magic properties. For example, instead `$input->required()`, use `$input->required = true` or `$input->setAttribute('required', true)`.
85 | - Added the ability of define label and properties in the input constructors. For example: `F::text('Write your name', ['required'])` instead `F::text()->label('Write your name')->required()`
86 |
87 | [#79]: https://github.com/oscarotero/form-manager/issues/79
88 | [#81]: https://github.com/oscarotero/form-manager/issues/81
89 | [#83]: https://github.com/oscarotero/form-manager/issues/83
90 | [#87]: https://github.com/oscarotero/form-manager/issues/87
91 | [#89]: https://github.com/oscarotero/form-manager/issues/89
92 |
93 | [7.0.0]: https://github.com/oscarotero/form-manager/compare/v6.1.2...v7.0.0
94 | [6.1.2]: https://github.com/oscarotero/form-manager/compare/v6.1.1...v6.1.2
95 | [6.1.1]: https://github.com/oscarotero/form-manager/compare/v6.1.0...v6.1.1
96 | [6.1.0]: https://github.com/oscarotero/form-manager/compare/v6.0.1...v6.1.0
97 | [6.0.1]: https://github.com/oscarotero/form-manager/compare/v6.0.0...v6.0.1
98 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guide
2 |
3 | This project adheres to [The Code Manifesto](http://codemanifesto.com) as its guidelines for contributor interactions.
4 |
5 | ## The Code Manifesto
6 |
7 | We want to work in an ecosystem that empowers developers to reach their potential--one that encourages growth and effective collaboration. A space that is safe for all.
8 |
9 | A space such as this benefits everyone that participates in it. It encourages new developers to enter our field. It is through discussion and collaboration that we grow, and through growth that we improve.
10 |
11 | In the effort to create such a place, we hold to these values:
12 |
13 | 1. **Discrimination limits us.** This includes discrimination on the basis of race, gender, sexual orientation, gender identity, age, nationality, technology and any other arbitrary exclusion of a group of people.
14 | 2. **Boundaries honor us.** Your comfort levels are not everyone’s comfort levels. Remember that, and if brought to your attention, heed it.
15 | 3. **We are our biggest assets.** None of us were born masters of our trade. Each of us has been helped along the way. Return that favor, when and where you can.
16 | 4. **We are resources for the future.** As an extension of #3, share what you know. Make yourself a resource to help those that come after you.
17 | 5. **Respect defines us.** Treat others as you wish to be treated. Make your discussions, criticisms and debates from a position of respectfulness. Ask yourself, is it true? Is it necessary? Is it constructive? Anything less is unacceptable.
18 | 6. **Reactions require grace.** Angry responses are valid, but abusive language and vindictive actions are toxic. When something happens that offends you, handle it assertively, but be respectful. Escalate reasonably, and try to allow the offender an opportunity to explain themselves, and possibly correct the issue.
19 | 7. **Opinions are just that: opinions.** Each and every one of us, due to our background and upbringing, have varying opinions. That is perfectly acceptable. Remember this: if you respect your own opinions, you should respect the opinions of others.
20 | 8. **To err is human.** You might not intend it, but mistakes do happen and contribute to build experience. Tolerate honest mistakes, and don't hesitate to apologize if you make one yourself.
21 |
22 | ## How to contribute
23 |
24 | This is a collaborative effort. We welcome all contributions submitted as pull requests.
25 |
26 | (Contributions on wording & style are also welcome.)
27 |
28 | ### Bugs
29 |
30 | A bug is a demonstrable problem that is caused by the code in the repository. Good bug reports are extremely helpful – thank you!
31 |
32 | Please try to be as detailed as possible in your report. Include specific information about the environment – version of PHP, etc, and steps required to reproduce the issue.
33 |
34 | ### Pull Requests
35 |
36 | Good pull requests – patches, improvements, new features – are a fantastic help. Before create a pull request, please follow these instructions:
37 |
38 | * The code must follow the [PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md). Run `composer cs-fix` to fix your code before commit.
39 | * Write tests
40 | * Document any change in `README.md` and `CHANGELOG.md`
41 | * One pull request per feature. If you want to do more than one thing, send multiple pull request
42 |
43 | ### Runing tests
44 |
45 | ```sh
46 | composer test
47 | ```
48 |
49 | To get code coverage information execute the following comand:
50 |
51 | ```sh
52 | composer coverage
53 | ```
54 |
55 | Then, open the `./coverage/index.html` file in your browser.
56 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-2025 Oscar Otero Marzoa, Filis Futsarov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Form Manager
2 |
3 | [ico-version]: https://img.shields.io/packagist/v/form-manager/form-manager.svg?style=flat-square
4 | [ico-ga]: https://github.com/oscarotero/form-manager/workflows/testing/badge.svg
5 | [ico-downloads]: https://img.shields.io/packagist/dt/form-manager/form-manager.svg?style=flat-square
6 |
7 | [link-packagist]: https://packagist.org/packages/form-manager/form-manager
8 | [link-downloads]: https://packagist.org/packages/form-manager/form-manager
9 |
10 | [![Latest Version on Packagist][ico-version]][link-packagist]
11 | [![Software License][ico-license]](LICENSE)
12 | ![Testing][ico-ga]
13 | [![Total Downloads][ico-downloads]][link-downloads]
14 |
15 | > ### Note: this is the documentation of FormManager 7.x
16 | > For v6.x version [Click here](https://github.com/oscarotero/form-manager/tree/v6)
17 |
18 | ## Installation:
19 |
20 | This package requires `PHP>=7.2` and is available on [Packagist](https://packagist.org/packages/form-manager/form-manager):
21 |
22 | Supports `symfony/validator` v5, v6 and v7.
23 |
24 | ```
25 | composer require form-manager/form-manager
26 | ```
27 |
28 | ## Create a field
29 |
30 | FormManager is namespaced, but you only need to import a single class into your context:
31 |
32 | ```php
33 | use FormManager\Factory as F;
34 | ```
35 |
36 | Use the imported factory to create all form elements:
37 |
38 | ```php
39 | // Create an input type="text" element
40 | $name = F::text();
41 |
42 | // Create the input with a label
43 | $name = F::text('Please, introduce your name');
44 |
45 | // Or with extra attributes
46 | $name = F::text('Please, introduce your name', ['class' => 'name-field']);
47 |
48 | // Add or remove attributes
49 | $name->setAttribute('title', 'This is the name input');
50 | $name->removeAttribute('class');
51 | $name->setAttributes([
52 | 'required',
53 | 'readonly',
54 | 'tabindex' => 2,
55 | 'maxlength' => 50
56 | ]);
57 |
58 | // Set the value
59 | $name->setValue('MyName');
60 |
61 | // Use magic properties to get/set/remove attributes
62 | $name->class = 'name-field';
63 | $name->required = false;
64 | unset($name->readonly);
65 | ```
66 |
67 | ### List of all available inputs:
68 |
69 | All HTML5 field types are supported:
70 |
71 | * `F::checkbox($label, $attributes)`
72 | * `F::color($label, $attributes)`
73 | * `F::date($label, $attributes)`
74 | * `F::datetimeLocal($label, $attributes)`
75 | * `F::email($label, $attributes)`
76 | * `F::file($label, $attributes)`
77 | * `F::hidden($value, $attributes)`
78 | * `F::month($label, $attributes)`
79 | * `F::number($label, $attributes)`
80 | * `F::password($label, $attributes)`
81 | * `F::radio($label, $attributes)`
82 | * `F::range($label, $attributes)`
83 | * `F::search($label, $attributes)`
84 | * `F::select($label, $options, $attributes)`
85 | * `F::submit($label, $attributes)`
86 | * `F::tel($label, $attributes)`
87 | * `F::text($label, $attributes)`
88 | * `F::textarea($label, $attributes)`
89 | * `F::time($label, $attributes)`
90 | * `F::url($label, $attributes)`
91 | * `F::week($label, $attributes)`
92 |
93 | > Note that all inputs accepts the same arguments except `hidden` and `select`.
94 |
95 | ## Validation
96 |
97 | This library uses internally [symfony/validation](https://symfony.com/doc/current/validation.html) to perform basic html5 validations and error reporting. HTML5 validation attributes like `required`, `maxlength`, `minlength`, `pattern`, etc are supported, in addition to intrinsic validations assigned to each input like email, url, date, etc.
98 |
99 | ```php
100 | // Set global default error messages
101 | F::setErrorMessages([
102 | 'required' => 'The field is required'
103 | 'maxlength' => 'The field is too long, it must have {{ limit }} characters or less',
104 | ]);
105 |
106 | $email = F::email();
107 |
108 | // Set per-fied error messages
109 | $email->setErrorMessages([
110 | 'email' => 'The email is not valid',
111 | 'required' => 'The email is required',
112 | 'maxlength' => 'The email is too long, it must have {{ limit }} characters or less',
113 | ]);
114 |
115 | $email->setValue('invalid-email');
116 |
117 | // Validate the value
118 | if ($email->isValid()) {
119 | return true;
120 | }
121 |
122 | // Get the errors
123 | $error = $email->getError();
124 |
125 | // Print the first error message
126 | echo $error;
127 |
128 | // Iterate through all error messages
129 | foreach ($error as $err) {
130 | echo $err->getMessage();
131 | }
132 |
133 | // And add more symfony constraints
134 | $ip = F::text();
135 | $ip->addConstraint(new Constraints\Ip());
136 | ```
137 |
138 | See [all supported constraints by symfony/validation](https://symfony.com/doc/current/validation.html#supported-constraints).
139 |
140 | ## Translations
141 |
142 | This package allows you to set your custom Validation instance with `Factory::setValidator()`.
143 |
144 | This allows you to use [symfony/translations](https://symfony.com/doc/current/translation.html) in order to have translations in place.
145 |
146 | ```shell
147 | composer require symfony/translations
148 | ```
149 |
150 | ```php
151 | $validator = Validation::createValidatorBuilder()
152 | ->setTranslator($translator)
153 | ->setTranslationDomain('validators')
154 | ->getValidator();
155 |
156 | // Set validator
157 | F::setValidator($validator);
158 | ```
159 |
160 | See [examples/translations.php](examples/translations.php) to see the full example.
161 |
162 | ## Render html
163 |
164 | ```php
165 | $name = F::text('What is your name?', ['name' => 'name']);
166 |
167 | echo $name;
168 | ```
169 | ```html
170 |
171 | ```
172 |
173 | Set a custom template using `{{ label }}` and `{{ input }}` placeholders:
174 |
175 | ```php
176 | $name->setTemplate('{{ label }}
{{ input }}
');
177 | echo $name;
178 | ```
179 | ```html
180 |
181 | ```
182 |
183 | If you want to wrap the previous template in a custom html, use the `{{ template }}` placeholder:
184 |
185 | ```php
186 | $name->setTemplate('');
187 | echo $name;
188 | ```
189 | ```html
190 |
191 | ```
192 |
193 | ## Grouping fields
194 |
195 | Group the fields to follow a specific data structure:
196 |
197 | ### Group
198 |
199 | Groups allow to place a set of inputs under an specific name:
200 |
201 | ```php
202 | $group = F::group([
203 | 'name' => F::text('Username'),
204 | 'email' => F::email('Email'),
205 | 'password' => F::password('Password'),
206 | ]);
207 |
208 | $group->setValue([
209 | 'name' => 'oscar',
210 | 'email' => 'oom@oscarotero.com',
211 | 'password' => 'supersecret',
212 | ]);
213 | ```
214 |
215 | ### Radio group
216 |
217 | Special case for radios where all inputs share the same name with different values:
218 |
219 | ```php
220 | $radios = F::radioGroup([
221 | 'red' => 'Red',
222 | 'blue' => 'Blue',
223 | 'green' => 'Green',
224 | ]);
225 |
226 | $radios->setValue('blue');
227 | ```
228 |
229 | If you need the radio group to be required, you should add the `required` attribute to at least one radio button:
230 |
231 | ```php
232 | $radios = F::radioGroup([
233 | 'red' => F::radio('Red', ['required' => true]),
234 | 'blue' => 'Blue',
235 | 'green' => 'Green',
236 | ]);
237 | ```
238 |
239 | ### Submit group
240 |
241 | Special case to group several submit buttons under the same name but different values:
242 |
243 | ```php
244 | $buttons = F::submitGroup([
245 | 'save' => 'Save the row',
246 | 'duplicate' => 'Save as new row',
247 | ]);
248 |
249 | $buttons->setName('action');
250 | ```
251 |
252 | ### Group collection
253 |
254 | Is a collection of values using the same group:
255 |
256 | ```php
257 | $groupCollection = F::groupCollection(
258 | f::group([
259 | 'name' => F::text('Name'),
260 | 'genre' => F::radioGroup([
261 | 'm' => 'Male',
262 | 'f' => 'Female',
263 | 'o' => 'Other',
264 | ]),
265 | ])
266 | ]);
267 |
268 | $groupCollection->setValue([
269 | [
270 | 'name' => 'Oscar',
271 | 'genre' => 'm'
272 | ],[
273 | 'name' => 'Laura',
274 | 'genre' => 'f'
275 | ],
276 | ])
277 | ```
278 |
279 | ### Multiple group collection
280 |
281 | Is a collection of values using various groups, using the field `type` to identify which group is used by each row:
282 |
283 | ```php
284 | $multipleGroupCollection = F::multipleGroupCollection(
285 | 'text' => f::group([
286 | 'type' => F::hidden(),
287 | 'title' => F::text('Title'),
288 | 'text' => F::textarea('Body'),
289 | ]),
290 | 'image' => f::group([
291 | 'type' => F::hidden(),
292 | 'file' => F::file('Image file'),
293 | 'alt' => F::text('Alt text'),
294 | 'text' => F::textarea('Caption'),
295 | ]),
296 | 'link' => f::group([
297 | 'type' => F::hidden(),
298 | 'text' => F::text('Link text'),
299 | 'href' => F::url('Url'),
300 | 'target' => F::select([
301 | '_blank' => 'New window',
302 | '_self' => 'The same window',
303 | ]),
304 | ]),
305 | ]);
306 |
307 | $multipleGroupCollection->setValue([
308 | [
309 | 'type' => 'text',
310 | 'title' => 'Welcome to my page',
311 | 'text' => 'I hope you like it',
312 | ],[
313 | 'type' => 'image',
314 | 'file' => 'avatar.jpg',
315 | 'alt' => 'Image of mine',
316 | 'text' => 'This is my photo',
317 | ],[
318 | 'type' => 'link',
319 | 'text' => 'Go to my webpage',
320 | 'href' => 'https://oscarotero.com',
321 | 'target' => '_self',
322 | ],
323 | ]);
324 | ```
325 |
326 | ## Datalist
327 |
328 | [Datalists](http://www.w3.org/TR/html5/forms.html#the-datalist-element) are also allowed, just use the `createDatalist()` method:
329 |
330 | ```php
331 | $input = F::search();
332 |
333 | $datalist = $input->createDatalist([
334 | 'female' => 'Female',
335 | 'male' => 'Male'
336 | ]);
337 |
338 | echo $input;
339 | echo $datalist;
340 | ```
341 |
342 | ## Forms
343 |
344 | We need a form to put all this things together.
345 |
346 | ```php
347 | $loginForm = F::form([
348 | 'username' => F::text('User name'),
349 | 'password' => F::password('Password'),
350 | '' => F::submit('Login'),
351 | ]);
352 |
353 | $loginForm->setAttributes([
354 | 'action' => 'login.php',
355 | 'method' => 'post',
356 | ]);
357 |
358 | // mLoad data from globals $_GET, $_POST, $_FILES
359 | $loginForm->loadFromGlobals();
360 |
361 | // Load data passing the arrays
362 | $loginForm->loadFromArrays($_GET, $_POST, $_FILES);
363 |
364 | // Or load from PSR-7 server request
365 | $loginForm->loadFromServerRequest($serverRequest);
366 |
367 | // Get loaded data
368 | $data = $loginForm->getValue();
369 |
370 | // Print the form
371 | echo $loginForm;
372 |
373 | // Access to specific inputs:
374 | echo $loginForm->getOpeningTag();
375 | echo '