├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── doc └── src │ ├── built-in-helpers.md │ ├── constraints.md │ ├── getting-started.md │ ├── index.md │ ├── mappings.md │ └── rendering-forms.md ├── mkdocs.yml ├── phpcs.xml ├── phpunit.xml.dist ├── src ├── Data.php ├── Exception │ ├── ExceptionInterface.php │ ├── InvalidDataException.php │ ├── InvalidKeyException.php │ ├── InvalidValueException.php │ ├── NonExistentKeyException.php │ └── UnboundDataException.php ├── Field.php ├── Form.php ├── FormError │ ├── FormError.php │ └── FormErrorSequence.php ├── FormInterface.php ├── Helper │ ├── AttributeTrait.php │ ├── ErrorFormatter.php │ ├── ErrorList.php │ ├── Exception │ │ ├── ExceptionInterface.php │ │ ├── InvalidHtmlAttributeKeyException.php │ │ ├── InvalidHtmlAttributeValueException.php │ │ ├── InvalidSelectLabelException.php │ │ ├── MissingIntlExtensionException.php │ │ └── NonExistentMessageException.php │ ├── InputCheckbox.php │ ├── InputPassword.php │ ├── InputText.php │ ├── Select.php │ └── Textarea.php ├── Mapping │ ├── BindResult.php │ ├── Constraint │ │ ├── ConstraintInterface.php │ │ ├── EmailAddressConstraint.php │ │ ├── Exception │ │ │ ├── ExceptionInterface.php │ │ │ ├── InvalidLengthException.php │ │ │ ├── InvalidLimitException.php │ │ │ ├── InvalidStepException.php │ │ │ ├── InvalidTypeException.php │ │ │ └── MissingDecimalDependencyException.php │ │ ├── MaxLengthConstraint.php │ │ ├── MaxNumberConstraint.php │ │ ├── MinLengthConstraint.php │ │ ├── MinNumberConstraint.php │ │ ├── NotEmptyConstraint.php │ │ ├── StepNumberConstraint.php │ │ ├── UrlConstraint.php │ │ ├── ValidationError.php │ │ └── ValidationResult.php │ ├── Exception │ │ ├── BindFailureException.php │ │ ├── ExceptionInterface.php │ │ ├── InvalidBindResultException.php │ │ ├── InvalidMappingException.php │ │ ├── InvalidMappingKeyException.php │ │ ├── InvalidTypeException.php │ │ ├── InvalidUnapplyResultException.php │ │ ├── MappedClassMismatchException.php │ │ ├── NestedMappingExceptionTrait.php │ │ ├── NonExistentMappedClassException.php │ │ ├── NonExistentUnapplyKeyException.php │ │ ├── UnbindFailureException.php │ │ └── ValidBindResultException.php │ ├── FieldMapping.php │ ├── FieldMappingFactory.php │ ├── Formatter │ │ ├── BooleanFormatter.php │ │ ├── DateFormatter.php │ │ ├── DateTimeFormatter.php │ │ ├── DecimalFormatter.php │ │ ├── Exception │ │ │ ├── ExceptionInterface.php │ │ │ └── InvalidTypeException.php │ │ ├── FloatFormatter.php │ │ ├── FormatterInterface.php │ │ ├── IgnoredFormatter.php │ │ ├── IntegerFormatter.php │ │ ├── TextFormatter.php │ │ └── TimeFormatter.php │ ├── MappingInterface.php │ ├── MappingTrait.php │ ├── ObjectMapping.php │ ├── OptionalMapping.php │ └── RepeatedMapping.php └── Transformer │ ├── CallbackTransformer.php │ ├── TransformerInterface.php │ └── TrimTransformer.php └── test ├── DataTest.php ├── Exception ├── InvalidDataExceptionTest.php ├── InvalidKeyExceptionTest.php ├── InvalidValueExceptionTest.php ├── NonExistentKeyExceptionTest.php └── UnboundDataExceptionTest.php ├── FieldTest.php ├── FormError ├── FormErrorAssertion.php ├── FormErrorSequenceTest.php └── FormErrorTest.php ├── FormTest.php ├── Helper ├── ErrorFormatterTest.php ├── ErrorListTest.php ├── Exception │ ├── InvalidHtmlAttributeKeyExceptionTest.php │ ├── InvalidHtmlAttributeValueExceptionTest.php │ ├── InvalidSelectLabelExceptionTest.php │ ├── MissingIntlExtensionExceptionTest.php │ └── NonExistentMessageExceptionTest.php ├── InputCheckboxTest.php ├── InputPasswordTest.php ├── InputTextTest.php ├── SelectTest.php └── TextareaTest.php ├── Mapping ├── BindResultTest.php ├── Constraint │ ├── EmailAddressConstraintTest.php │ ├── Exception │ │ ├── InvalidLengthExceptionTest.php │ │ ├── InvalidLimitExceptionTest.php │ │ ├── InvalidStepExceptionTest.php │ │ ├── InvalidTypeExceptionTest.php │ │ └── MissingDecimalDependencyExceptionTest.php │ ├── MaxLengthConstraintTest.php │ ├── MaxNumberConstraintTest.php │ ├── MinLengthConstraintTest.php │ ├── MinNumberConstraintTest.php │ ├── NotEmptyConstraintTest.php │ ├── StepNumberConstraintTest.php │ ├── UrlConstraintTest.php │ ├── ValidationErrorAssertion.php │ ├── ValidationErrorTest.php │ └── ValidationResultTest.php ├── Exception │ ├── BindFailureExceptionTest.php │ ├── InvalidBindResultExceptionTest.php │ ├── InvalidMappingExceptionTest.php │ ├── InvalidMappingKeyExceptionTest.php │ ├── InvalidTypeExceptionTest.php │ ├── InvalidUnapplyResultExceptionTest.php │ ├── MappedClassMismatchExceptionTest.php │ ├── NonExistentMappedClassExceptionTest.php │ ├── NonExistentUnapplyKeyExceptionTest.php │ ├── UnbindFailureExceptionTest.php │ └── ValidBindResultExceptionTest.php ├── FieldMappingFactoryTest.php ├── FieldMappingTest.php ├── Formatter │ ├── BooleanFormatterTest.php │ ├── DateFormatterTest.php │ ├── DateTimeFormatterTest.php │ ├── DecimalFormatterTest.php │ ├── Exception │ │ └── InvalidTypeExceptionTest.php │ ├── FloatFormatterTest.php │ ├── IgnoredFormatterTest.php │ ├── IntegerFormatterTest.php │ ├── TextFormatterTest.php │ └── TimeFormatterTest.php ├── MappingTraitTestTrait.php ├── ObjectMappingTest.php ├── OptionalMappingTest.php ├── RepeatedMappingTest.php └── TestAsset │ └── SimpleObject.php └── Transformer ├── CallbackTransformerTest.php └── TrimTransformerTest.php /.coveralls.yml: -------------------------------------------------------------------------------- 1 | coverage_clover: clover.xml 2 | json_path: coveralls-upload.json 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /composer.lock 2 | /vendor/ 3 | /doc/html/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: php 4 | 5 | cache: 6 | directories: 7 | - $HOME/.composer/cache 8 | - $HOME/.local 9 | - vendor 10 | 11 | matrix: 12 | fast_finish: true 13 | include: 14 | - php: 7 15 | env: 16 | - EXECUTE_CS_CHECK=true 17 | - EXECUTE_TEST_COVERALLS=true 18 | - PATH="$HOME/.local/bin:$PATH" 19 | - php: nightly 20 | - php: hhvm 21 | allow_failures: 22 | - php: nightly 23 | - php: hhvm 24 | 25 | before_install: 26 | - if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then phpenv config-rm xdebug.ini || return 0 ; fi 27 | - composer self-update 28 | - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then composer require --dev --no-update satooshi/php-coveralls:dev-master ; fi 29 | 30 | install: 31 | - travis_retry composer install --no-interaction 32 | - composer info -i 33 | 34 | script: 35 | - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then composer test-coverage ; fi 36 | - if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then composer test ; fi 37 | - if [[ $EXECUTE_CS_CHECK == 'true' ]]; then composer cs ; fi 38 | 39 | after_script: 40 | - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then composer coveralls ; fi 41 | 42 | notifications: 43 | email: true 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Ben Scholzen (DASPRiD) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Formidable 2 | 3 | [![Build Status](https://travis-ci.org/DASPRiD/Formidable.svg?branch=master)](https://travis-ci.org/DASPRiD/Formidable) 4 | [![Coverage Status](https://coveralls.io/repos/github/DASPRiD/Formidable/badge.svg?branch=master)](https://coveralls.io/github/DASPRiD/Formidable?branch=master) 5 | [![Dependency Status](https://www.versioneye.com/user/projects/575b3fff7757a0003bd4bfe6/badge.svg?style=flat)](https://www.versioneye.com/user/projects/575b3fff7757a0003bd4bfe6) 6 | [![Reference Status](https://www.versioneye.com/php/dasprid:formidable/reference_badge.svg?style=flat)](https://www.versioneye.com/php/dasprid:formidable/references) 7 | [![Latest Stable Version](https://poser.pugx.org/dasprid/formidable/v/stable)](https://packagist.org/packages/dasprid/formidable) 8 | [![Total Downloads](https://poser.pugx.org/dasprid/formidable/downloads)](https://packagist.org/packages/dasprid/formidable) 9 | [![License](https://poser.pugx.org/dasprid/formidable/license)](https://packagist.org/packages/dasprid/formidable) 10 | 11 | Formidable is an **almost completely** strictly typed form library for PHP 7. Why only almost? Because we are missing 12 | generics in PHP still, which would be the requirement to make it completely strictly typed. As soon as those are 13 | available, Formidable will be updated accordingly. 14 | 15 | ## Installation 16 | 17 | Install via composer: 18 | 19 | ```bash 20 | $ composer require dasprid/formidable 21 | ``` 22 | 23 | ## Documentation 24 | 25 | Documentation builds are available at: 26 | 27 | - https://formidable.readthedocs.org 28 | 29 | You can also build the documentation locally via [MkDocs](http://www.mkdocs.org): 30 | 31 | ```bash 32 | $ mkdocs serve 33 | ``` 34 | 35 | ## Acknowledgment 36 | A huge thanks goes out to [Soliant](http://soliantconsulting.com/) for sponsoring the development of this library! 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dasprid/formidable", 3 | "description": "PHP 7 form library for handling user input", 4 | "type": "library", 5 | "license": "BSD-2-Clause", 6 | "authors": [ 7 | { 8 | "name": "Ben Scholzen 'DASPRiD'", 9 | "homepage": "https://dasprids.de/" 10 | } 11 | ], 12 | "keywords": [ 13 | "form", 14 | "http", 15 | "filter", 16 | "validator", 17 | "psr", 18 | "psr-7" 19 | ], 20 | "require": { 21 | "php": "^7.0", 22 | "psr/http-message": "^1.0" 23 | }, 24 | "require-dev": { 25 | "ext-iconv": "*", 26 | "ext-intl": "*", 27 | "phpunit/phpunit": "^5.3", 28 | "squizlabs/php_codesniffer": "^2.6", 29 | "litipk/php-bignumbers": "^0.8" 30 | }, 31 | "suggest": { 32 | "ext-iconv": "Allows using the built-in string length constraints", 33 | "ext-intl": "Allows using the built-in error formatter helper", 34 | "litipk/php-bignumbers": "Allows using the built-in number range constraints" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "DASPRiD\\Formidable\\": "src/" 39 | } 40 | }, 41 | "autoload-dev": { 42 | "psr-4": { 43 | "DASPRiD\\FormidableTest\\": "test/" 44 | } 45 | }, 46 | "scripts": { 47 | "check": [ 48 | "@cs", 49 | "@test" 50 | ], 51 | "coveralls": "coveralls", 52 | "cs": "phpcs", 53 | "cs-fix": "phpcbf", 54 | "test": "phpunit", 55 | "test-coverage": "phpunit --coverage-clover clover.xml" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /doc/src/built-in-helpers.md: -------------------------------------------------------------------------------- 1 | Formidable ships with a few helpers, located in the `DASPRiD\Formidable\Helper` namespace. 2 | 3 | The guiding principles here are as follows: 4 | 5 | - Support the [HTML Form Input Types](http://www.w3schools.com/html/html_form_input_types.asp) 6 | - Limit the markup output to the tags required for the input type 7 | 8 | # Input Helpers 9 | 10 | Formidable input helpers all take a `Field` object as the first argument, and optionally an array of HTML attributes as 11 | the last argument. They will automatically set the `name` attribute to the field's key and the `id` attribute to the 12 | field's key prepended by the string `input.`. 13 | 14 | ## `InputText` 15 | 16 | Renders a simple text input, with the type defaulting to "text". You can override the type by supplying a different type 17 | like "date", "color" or similar as HTML attribute. 18 | 19 | ## `InputPassword` 20 | 21 | Works exactly like [`InputText`](#inputtext), but will not render a `value` attribute. 22 | 23 | ## `Textarea` 24 | 25 | Renders a `', 23 | $helper(new Field('foo', 'bar&', new FormErrorSequence(), Data::none())) 24 | ); 25 | } 26 | 27 | public function testEmptyTextarea() 28 | { 29 | $helper = new Textarea(); 30 | $this->assertSame( 31 | '', 32 | $helper(new Field('foo', '', new FormErrorSequence(), Data::none())) 33 | ); 34 | } 35 | 36 | public function testCustomAttribute() 37 | { 38 | $helper = new Textarea(); 39 | $this->assertSame( 40 | '', 41 | $helper(new Field('foo', 'bar&', new FormErrorSequence(), Data::none()), ['data-foo' => 'bar']) 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/Mapping/BindResultTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($bindResult->isSuccess()); 23 | $this->assertSame('foo', $bindResult->getValue()); 24 | $this->expectException(ValidBindResultException::class); 25 | $bindResult->getFormErrorSequence(); 26 | } 27 | 28 | public function testBindResultFromFormErrors() 29 | { 30 | $bindResult = BindResult::fromFormErrors(new FormError('foo', 'bar')); 31 | $this->assertFalse($bindResult->isSuccess()); 32 | FormErrorAssertion::assertErrorMessages( 33 | $this, 34 | $bindResult->getFormErrorSequence(), 35 | [ 36 | 'foo' => 'bar', 37 | ] 38 | ); 39 | $this->expectException(InvalidBindResultException::class); 40 | $bindResult->getValue(); 41 | } 42 | 43 | public function testBindResultFromFormErrorSequence() 44 | { 45 | $bindResult = BindResult::fromFormErrorSequence(new FormErrorSequence(new FormError('foo', 'bar'))); 46 | $this->assertFalse($bindResult->isSuccess()); 47 | FormErrorAssertion::assertErrorMessages( 48 | $this, 49 | $bindResult->getFormErrorSequence(), 50 | [ 51 | 'foo' => 'bar', 52 | ] 53 | ); 54 | $this->expectException(InvalidBindResultException::class); 55 | $bindResult->getValue(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/Mapping/Constraint/EmailAddressConstraintTest.php: -------------------------------------------------------------------------------- 1 | expectException(InvalidTypeException::class); 19 | $constraint(1); 20 | } 21 | 22 | public function testFailureWithEmptyString() 23 | { 24 | $constraint = new EmailAddressConstraint(); 25 | $validationResult = $constraint(''); 26 | $this->assertFalse($validationResult->isSuccess()); 27 | ValidationErrorAssertion::assertErrorMessages($this, $validationResult, ['error.email-address' => []]); 28 | } 29 | 30 | public function testFailureWithInvalidEmailAddress() 31 | { 32 | $constraint = new EmailAddressConstraint(); 33 | $validationResult = $constraint('foobar'); 34 | $this->assertFalse($validationResult->isSuccess()); 35 | ValidationErrorAssertion::assertErrorMessages($this, $validationResult, ['error.email-address' => []]); 36 | } 37 | 38 | public function testSuccessWithValidEmailAddress() 39 | { 40 | $constraint = new EmailAddressConstraint(); 41 | $validationResult = $constraint('foo@bar.com'); 42 | $this->assertTrue($validationResult->isSuccess()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/Mapping/Constraint/Exception/InvalidLengthExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 17 | 'Length must be greater than or equal to zero, but got -1', 18 | InvalidLengthException::fromNegativeLength(-1)->getMessage() 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/Mapping/Constraint/Exception/InvalidLimitExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 18 | 'Limit was expected to be numeric, but got "test"', 19 | InvalidLimitException::fromNonNumericValue('test')->getMessage() 20 | ); 21 | } 22 | 23 | public function testFromNonNumericValueWithObject() 24 | { 25 | $this->assertSame( 26 | 'Limit was expected to be numeric, but got object', 27 | InvalidLimitException::fromNonNumericValue(new stdClass())->getMessage() 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/Mapping/Constraint/Exception/InvalidStepExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 19 | 'Step was expected to be numeric, but got "test"', 20 | InvalidStepException::fromNonNumericStep('test')->getMessage() 21 | ); 22 | } 23 | 24 | public function testFromNonNumericStepWithObject() 25 | { 26 | $this->assertSame( 27 | 'Step was expected to be numeric, but got object', 28 | InvalidStepException::fromNonNumericStep(new stdClass())->getMessage() 29 | ); 30 | } 31 | 32 | public function testFromNonNumericBaseWithString() 33 | { 34 | $this->assertSame( 35 | 'Base was expected to be numeric, but got "test"', 36 | InvalidStepException::fromNonNumericBase('test')->getMessage() 37 | ); 38 | } 39 | 40 | public function testFromNonNumericBaseWithObject() 41 | { 42 | $this->assertSame( 43 | 'Base was expected to be numeric, but got object', 44 | InvalidStepException::fromNonNumericBase(new stdClass())->getMessage() 45 | ); 46 | } 47 | 48 | public function testFromZeroOrNegativeStep() 49 | { 50 | $this->assertSame( 51 | 'Step must be greater than zero, but got 0', 52 | InvalidStepException::fromZeroOrNegativeStep(Decimal::fromInteger(0))->getMessage() 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/Mapping/Constraint/Exception/InvalidTypeExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 18 | 'Value was expected to be of type foo, but got stdClass', 19 | InvalidTypeException::fromInvalidType(new stdClass(), 'foo')->getMessage() 20 | ); 21 | } 22 | 23 | public function testFromInvalidTypeWithScalar() 24 | { 25 | $this->assertSame( 26 | 'Value was expected to be of type foo, but got boolean', 27 | InvalidTypeException::fromInvalidType(true, 'foo')->getMessage() 28 | ); 29 | } 30 | 31 | public function testFromNonNumericValueWithString() 32 | { 33 | $this->assertSame( 34 | 'Value was expected to be numeric, but got "test"', 35 | InvalidTypeException::fromNonNumericValue('test')->getMessage() 36 | ); 37 | } 38 | 39 | public function testFromNonNumericValueWithObject() 40 | { 41 | $this->assertSame( 42 | 'Value was expected to be numeric, but got object', 43 | InvalidTypeException::fromNonNumericValue(new stdClass())->getMessage() 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/Mapping/Constraint/Exception/MissingDecimalDependencyExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 17 | 'You must composer require litipk/php-bignumbers for this constraint to work', 18 | MissingDecimalDependencyException::fromMissingDependency()->getMessage() 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/Mapping/Constraint/MaxLengthConstraintTest.php: -------------------------------------------------------------------------------- 1 | expectException(InvalidLengthException::class); 19 | new MaxLengthConstraint(-1); 20 | } 21 | 22 | public function testAssertionWithInvalidValueType() 23 | { 24 | $constraint = new MaxLengthConstraint(0); 25 | $this->expectException(InvalidTypeException::class); 26 | $constraint(1); 27 | } 28 | 29 | public function testFailureWithEmptyString() 30 | { 31 | $constraint = new MaxLengthConstraint(1); 32 | $validationResult = $constraint('ab'); 33 | $this->assertFalse($validationResult->isSuccess()); 34 | ValidationErrorAssertion::assertErrorMessages( 35 | $this, 36 | $validationResult, 37 | ['error.max-length' => ['lengthLimit' => 1]] 38 | ); 39 | } 40 | 41 | public function testSuccessWithMultiByte() 42 | { 43 | $constraint = new MaxLengthConstraint(1); 44 | $validationResult = $constraint('ü'); 45 | $this->assertTrue($validationResult->isSuccess()); 46 | } 47 | 48 | public function testSuccessWithValidString() 49 | { 50 | $constraint = new MaxLengthConstraint(2); 51 | $validationResult = $constraint('ab'); 52 | $this->assertTrue($validationResult->isSuccess()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/Mapping/Constraint/MaxNumberConstraintTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($validationResult->isSuccess()); 38 | } 39 | 40 | public function invalidValueProvider() : array 41 | { 42 | return [ 43 | [0, 1], 44 | [0., 1.], 45 | ['0', '1'], 46 | ]; 47 | } 48 | 49 | /** 50 | * @dataProvider invalidValueProvider 51 | * @param int|float|string $limit 52 | * @param int|float|string $value 53 | */ 54 | public function testInvalidValues($limit, $value) 55 | { 56 | $constraint = new MaxNumberConstraint($limit); 57 | $validationResult = $constraint($value); 58 | $this->assertFalse($validationResult->isSuccess()); 59 | ValidationErrorAssertion::assertErrorMessages( 60 | $this, 61 | $validationResult, 62 | ['error.max-number' => ['limit' => (string) $limit]] 63 | ); 64 | } 65 | 66 | public function testAssertionWithInvalidLimitType() 67 | { 68 | $this->expectException(InvalidLimitException::class); 69 | new MaxNumberConstraint('test'); 70 | } 71 | 72 | public function testAssertionWithNonNumericValueType() 73 | { 74 | $constraint = new MaxNumberConstraint(0); 75 | $this->expectException(InvalidTypeException::class); 76 | $constraint('test'); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /test/Mapping/Constraint/MinLengthConstraintTest.php: -------------------------------------------------------------------------------- 1 | expectException(InvalidLengthException::class); 19 | new MinLengthConstraint(-1); 20 | } 21 | 22 | public function testAssertionWithInvalidValueType() 23 | { 24 | $constraint = new MinLengthConstraint(0); 25 | $this->expectException(InvalidTypeException::class); 26 | $constraint(1); 27 | } 28 | 29 | public function testFailureWithEmptyString() 30 | { 31 | $constraint = new MinLengthConstraint(1); 32 | $validationResult = $constraint(''); 33 | $this->assertFalse($validationResult->isSuccess()); 34 | ValidationErrorAssertion::assertErrorMessages( 35 | $this, 36 | $validationResult, 37 | ['error.min-length' => ['lengthLimit' => 1]] 38 | ); 39 | } 40 | 41 | public function testFailureWithMultiByte() 42 | { 43 | $constraint = new MinLengthConstraint(2); 44 | $validationResult = $constraint('ü'); 45 | $this->assertFalse($validationResult->isSuccess()); 46 | ValidationErrorAssertion::assertErrorMessages( 47 | $this, 48 | $validationResult, 49 | ['error.min-length' => ['lengthLimit' => 2]] 50 | ); 51 | } 52 | 53 | public function testSuccessWithValidString() 54 | { 55 | $constraint = new MinLengthConstraint(2); 56 | $validationResult = $constraint('ab'); 57 | $this->assertTrue($validationResult->isSuccess()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/Mapping/Constraint/MinNumberConstraintTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($validationResult->isSuccess()); 38 | } 39 | 40 | public function invalidValueProvider() : array 41 | { 42 | return [ 43 | [0, -1], 44 | [0., -1.], 45 | ['0', '-1'], 46 | ]; 47 | } 48 | 49 | /** 50 | * @dataProvider invalidValueProvider 51 | * @param int|float|string $limit 52 | * @param int|float|string $value 53 | */ 54 | public function testInvalidValues($limit, $value) 55 | { 56 | $constraint = new MinNumberConstraint($limit); 57 | $validationResult = $constraint($value); 58 | $this->assertFalse($validationResult->isSuccess()); 59 | ValidationErrorAssertion::assertErrorMessages( 60 | $this, 61 | $validationResult, 62 | ['error.min-number' => ['limit' => (string) $limit]] 63 | ); 64 | } 65 | 66 | public function testAssertionWithInvalidLimitType() 67 | { 68 | $this->expectException(InvalidLimitException::class); 69 | new MinNumberConstraint('test'); 70 | } 71 | 72 | public function testAssertionWithNonNumericValueType() 73 | { 74 | $constraint = new MinNumberConstraint(0); 75 | $this->expectException(InvalidTypeException::class); 76 | $constraint('test'); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /test/Mapping/Constraint/NotEmptyConstraintTest.php: -------------------------------------------------------------------------------- 1 | expectException(InvalidTypeException::class); 19 | $constraint(1); 20 | } 21 | 22 | public function testFailureWithEmptyString() 23 | { 24 | $constraint = new NotEmptyConstraint(); 25 | $validationResult = $constraint(''); 26 | $this->assertFalse($validationResult->isSuccess()); 27 | ValidationErrorAssertion::assertErrorMessages( 28 | $this, 29 | $validationResult, 30 | ['error.empty' => []] 31 | ); 32 | } 33 | 34 | public function testSuccessWithValidString() 35 | { 36 | $constraint = new NotEmptyConstraint(); 37 | $validationResult = $constraint('a'); 38 | $this->assertTrue($validationResult->isSuccess()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/Mapping/Constraint/UrlConstraintTest.php: -------------------------------------------------------------------------------- 1 | expectException(InvalidTypeException::class); 19 | $constraint(1); 20 | } 21 | 22 | public function testFailureWithEmptyString() 23 | { 24 | $constraint = new UrlConstraint(); 25 | $validationResult = $constraint(''); 26 | $this->assertFalse($validationResult->isSuccess()); 27 | ValidationErrorAssertion::assertErrorMessages($this, $validationResult, ['error.url' => []]); 28 | } 29 | 30 | public function testFailureWithInvalidUrl() 31 | { 32 | $constraint = new UrlConstraint(); 33 | $validationResult = $constraint('foobar'); 34 | $this->assertFalse($validationResult->isSuccess()); 35 | ValidationErrorAssertion::assertErrorMessages($this, $validationResult, ['error.url' => []]); 36 | } 37 | 38 | public function testSuccessWithValidHttpUrl() 39 | { 40 | $constraint = new UrlConstraint(); 41 | $validationResult = $constraint('http://example.com'); 42 | $this->assertTrue($validationResult->isSuccess()); 43 | } 44 | 45 | public function testSuccessWithValidHttpUrlWithLocalhost() 46 | { 47 | $constraint = new UrlConstraint(); 48 | $validationResult = $constraint('http://localhost'); 49 | $this->assertTrue($validationResult->isSuccess()); 50 | } 51 | 52 | public function testSuccessWithValidIrcUrl() 53 | { 54 | $constraint = new UrlConstraint(); 55 | $validationResult = $constraint('irc://example.com'); 56 | $this->assertTrue($validationResult->isSuccess()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/Mapping/Constraint/ValidationErrorAssertion.php: -------------------------------------------------------------------------------- 1 | getValidationErrors() as $validationError) { 19 | $actualMessages[$validationError->getMessage()] = $validationError->getArguments(); 20 | } 21 | 22 | $testCase->assertSame($expectedMessages, $actualMessages); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/Mapping/Constraint/ValidationErrorTest.php: -------------------------------------------------------------------------------- 1 | assertSame('foo', (new ValidationError('foo'))->getMessage()); 17 | } 18 | 19 | public function testArgumentsRetrieval() 20 | { 21 | $this->assertSame(['foo'], (new ValidationError('', ['foo']))->getArguments()); 22 | } 23 | 24 | public function testKeySuffixRetrieval() 25 | { 26 | $this->assertSame('foo', (new ValidationError('', [], 'foo'))->getKeySuffix()); 27 | } 28 | 29 | public function testDefaults() 30 | { 31 | $validationError = new ValidationError(''); 32 | $this->assertSame([], $validationError->getArguments()); 33 | $this->assertSame('', $validationError->getKeySuffix()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/Mapping/Constraint/ValidationResultTest.php: -------------------------------------------------------------------------------- 1 | assertTrue((new ValidationResult())->isSuccess()); 18 | } 19 | 20 | public function testFailureWithErrors() 21 | { 22 | $this->assertFalse((new ValidationResult(new ValidationError('')))->isSuccess()); 23 | } 24 | 25 | public function testValidationErrorsRetrieval() 26 | { 27 | $validationResult = new ValidationResult(new ValidationError('foo'), new ValidationError('bar')); 28 | ValidationErrorAssertion::assertErrorMessages($this, $validationResult, ['foo' => [], 'bar' => []]); 29 | } 30 | 31 | public function testMerge() 32 | { 33 | $validationResultA = new ValidationResult(new ValidationError('foo')); 34 | $validationResultB = new ValidationResult(); 35 | $validationResultC = new ValidationResult(new ValidationError('bar'), new ValidationError('baz')); 36 | 37 | $validationResult = $validationResultA->merge($validationResultB)->merge($validationResultC); 38 | $this->assertFalse($validationResult->isSuccess()); 39 | ValidationErrorAssertion::assertErrorMessages( 40 | $this, 41 | $validationResult, 42 | ['foo' => [], 'bar' => [], 'baz' => []] 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/Mapping/Exception/BindFailureExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 22 | 'Failed to bind foo: test', 23 | $exception->getMessage() 24 | ); 25 | $this->assertSame($previous, $exception->getPrevious()); 26 | } 27 | 28 | public function testFromBindExceptionWithNestedBindFailureException() 29 | { 30 | $previous = BindFailureException::fromBindException( 31 | 'bar', 32 | BindFailureException::fromBindException('baz', new Exception('test')) 33 | ); 34 | $exception = BindFailureException::fromBindException('foo', $previous); 35 | 36 | $this->assertSame( 37 | 'Failed to bind foo.bar.baz: test', 38 | $exception->getMessage() 39 | ); 40 | $this->assertSame($previous, $exception->getPrevious()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/Mapping/Exception/InvalidBindResultExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 17 | 'Value can only be retrieved when bind result was successful', 18 | InvalidBindResultException::fromGetValueAttempt()->getMessage() 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/Mapping/Exception/InvalidMappingExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 19 | sprintf('Mapping was expected to implement %s, but got stdClass', MappingInterface::class), 20 | InvalidMappingException::fromInvalidMapping(new stdClass())->getMessage() 21 | ); 22 | } 23 | 24 | public function testFromInvalidMappingWithScalar() 25 | { 26 | $this->assertSame( 27 | sprintf('Mapping was expected to implement %s, but got boolean', MappingInterface::class), 28 | InvalidMappingException::fromInvalidMapping(true)->getMessage() 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/Mapping/Exception/InvalidMappingKeyExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 18 | 'Mapping key must be of type string, but got object', 19 | InvalidMappingKeyException::fromInvalidMappingKey(new stdClass())->getMessage() 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/Mapping/Exception/InvalidTypeExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 18 | 'Value was expected to be of type foo, but got stdClass', 19 | InvalidTypeException::fromInvalidType(new stdClass(), 'foo')->getMessage() 20 | ); 21 | } 22 | 23 | public function testFromInvalidTypeWithScalar() 24 | { 25 | $this->assertSame( 26 | 'Value was expected to be of type foo, but got boolean', 27 | InvalidTypeException::fromInvalidType(true, 'foo')->getMessage() 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/Mapping/Exception/InvalidUnapplyResultExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 18 | 'Unapply was expected to return an array, but returned object', 19 | InvalidUnapplyResultException::fromInvalidUnapplyResult(new stdClass())->getMessage() 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/Mapping/Exception/MappedClassMismatchExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 18 | 'Value to bind or unbind must be an instance of foo, but got stdClass', 19 | MappedClassMismatchException::fromMismatchedClass('foo', new stdClass())->getMessage() 20 | ); 21 | } 22 | 23 | public function testFromMismatchedClassWithScalar() 24 | { 25 | $this->assertSame( 26 | 'Value to bind or unbind must be an instance of foo, but got boolean', 27 | MappedClassMismatchException::fromMismatchedClass('foo', true)->getMessage() 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/Mapping/Exception/NonExistentMappedClassExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 18 | sprintf('Class with name foo does not exist', MappingInterface::class), 19 | NonExistentMappedClassException::fromNonExistentClass('foo')->getMessage() 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/Mapping/Exception/NonExistentUnapplyKeyExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 17 | 'Key "foo" not found in array returned by unapply function', 18 | NonExistentUnapplyKeyException::fromNonExistentUnapplyKey('foo')->getMessage() 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/Mapping/Exception/UnbindFailureExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 22 | 'Failed to unbind foo: test', 23 | $exception->getMessage() 24 | ); 25 | $this->assertSame($previous, $exception->getPrevious()); 26 | } 27 | 28 | public function testFromUnbindExceptionWithNestedBindFailureException() 29 | { 30 | $previous = UnbindFailureException::fromUnbindException( 31 | 'bar', 32 | UnbindFailureException::fromUnbindException('baz', new Exception('test')) 33 | ); 34 | $exception = UnbindFailureException::fromUnbindException('foo', $previous); 35 | 36 | $this->assertSame( 37 | 'Failed to unbind foo.bar.baz: test', 38 | $exception->getMessage() 39 | ); 40 | $this->assertSame($previous, $exception->getPrevious()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/Mapping/Exception/ValidBindResultExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 17 | 'Form errors can only be retrieved when bind result was not successful', 18 | ValidBindResultException::fromGetFormErrorsAttempt()->getMessage() 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/Mapping/FieldMappingTest.php: -------------------------------------------------------------------------------- 1 | 'bar']); 27 | $bindResult = BindResult::fromFormErrors(); 28 | 29 | $binder = $this->prophesize(FormatterInterface::class); 30 | $binder->bind('foo', $data)->willReturn($bindResult); 31 | 32 | $mapping = (new FieldMapping($binder->reveal()))->withPrefixAndRelativeKey('', 'foo'); 33 | $this->assertSame($bindResult, $mapping->bind($data)); 34 | } 35 | 36 | public function testBindReturnsSuccessResult() 37 | { 38 | $data = Data::fromFlatArray(['foo' => 'bar']); 39 | 40 | $binder = $this->prophesize(FormatterInterface::class); 41 | $binder->bind('foo', $data)->willReturn(BindResult::fromValue('bar')); 42 | 43 | $mapping = (new FieldMapping($binder->reveal()))->withPrefixAndRelativeKey('', 'foo'); 44 | $bindResult = $mapping->bind($data); 45 | $this->assertTrue($bindResult->isSuccess()); 46 | $this->assertSame('bar', $bindResult->getValue()); 47 | } 48 | 49 | public function testBindAppliesConstraints() 50 | { 51 | $data = Data::fromFlatArray(['foo' => 'bar']); 52 | 53 | $binder = $this->prophesize(FormatterInterface::class); 54 | $binder->bind('foo', $data)->willReturn(BindResult::fromValue('bar')); 55 | 56 | $constraint = $this->prophesize(ConstraintInterface::class); 57 | $constraint->__invoke('bar')->willReturn(new ValidationResult(new ValidationError('bar'))); 58 | 59 | $mapping = (new FieldMapping($binder->reveal()))->withPrefixAndRelativeKey('', 'foo')->verifying( 60 | $constraint->reveal() 61 | ); 62 | $bindResult = $mapping->bind($data); 63 | $this->assertFalse($bindResult->isSuccess()); 64 | $this->assertSame('bar', $bindResult->getFormErrorSequence()->getIterator()->current()->getMessage()); 65 | $this->assertSame('foo', $bindResult->getFormErrorSequence()->getIterator()->current()->getKey()); 66 | } 67 | 68 | public function testUnbind() 69 | { 70 | $data = Data::fromFlatArray(['foo' => 'bar']); 71 | 72 | $binder = $this->prophesize(FormatterInterface::class); 73 | $binder->unbind('foo', 'bar')->willReturn($data); 74 | 75 | $mapping = (new FieldMapping($binder->reveal()))->withPrefixAndRelativeKey('', 'foo'); 76 | $this->assertSame($data, $mapping->unbind('bar')); 77 | } 78 | 79 | public function testCreatePrefixedKey() 80 | { 81 | $binder = $this->prophesize(FormatterInterface::class); 82 | 83 | $mapping = (new FieldMapping($binder->reveal()))->withPrefixAndRelativeKey('foo', 'bar'); 84 | $this->assertAttributeSame('foo[bar]', 'key', $mapping); 85 | } 86 | 87 | /** 88 | * {@inheritdoc} 89 | */ 90 | protected function getInstanceForTraitTests() : MappingInterface 91 | { 92 | return new FieldMapping($this->prophesize(FormatterInterface::class)->reveal()); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /test/Mapping/Formatter/BooleanFormatterTest.php: -------------------------------------------------------------------------------- 1 | assertTrue((new BooleanFormatter())->bind('foo', Data::fromFlatArray(['foo' => 'true']))->getValue()); 19 | } 20 | 21 | public function testBindValidFalseValue() 22 | { 23 | $this->assertFalse((new BooleanFormatter())->bind('foo', Data::fromFlatArray(['foo' => 'false']))->getValue()); 24 | } 25 | 26 | public function testFallbackToFalseOnBindNonExistentKey() 27 | { 28 | $this->assertFalse((new BooleanFormatter())->bind('foo', Data::fromFlatArray([]))->getValue()); 29 | } 30 | 31 | public function testBindEmptyStringValue() 32 | { 33 | $bindResult = (new BooleanFormatter())->bind('foo', Data::fromFlatArray(['foo' => ''])); 34 | $this->assertFalse($bindResult->isSuccess()); 35 | $this->assertCount(1, $bindResult->getFormErrorSequence()); 36 | 37 | $error = iterator_to_array($bindResult->getFormErrorSequence())[0]; 38 | $this->assertSame('foo', $error->getKey()); 39 | $this->assertSame('error.boolean', $error->getMessage()); 40 | } 41 | 42 | public function testUnbindValidTrueValue() 43 | { 44 | $data = (new BooleanFormatter())->unbind('foo', true); 45 | $this->assertSame('true', $data->getValue('foo')); 46 | } 47 | 48 | public function testUnbindValidFalseValue() 49 | { 50 | $data = (new BooleanFormatter())->unbind('foo', false); 51 | $this->assertSame('false', $data->getValue('foo')); 52 | } 53 | 54 | public function testUnbindInvalidStringValue() 55 | { 56 | $this->expectException(InvalidTypeException::class); 57 | (new BooleanFormatter())->unbind('foo', 'false'); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/Mapping/Formatter/DateFormatterTest.php: -------------------------------------------------------------------------------- 1 | assertSame('2000-03-04', (new DateFormatter(new DateTimeZone('UTC')))->bind( 21 | 'foo', 22 | Data::fromFlatArray(['foo' => '2000-03-04']) 23 | )->getValue()->format('Y-m-d')); 24 | } 25 | 26 | public function testBindToSpecificTimeZone() 27 | { 28 | $this->assertSame('Europe/Berlin', (new DateFormatter(new DateTimeZone('Europe/Berlin')))->bind( 29 | 'foo', 30 | Data::fromFlatArray(['foo' => '2000-03-04']) 31 | )->getValue()->getTimezone()->getName()); 32 | } 33 | 34 | public function testBindEmptyStringValue() 35 | { 36 | $bindResult = (new DateFormatter(new DateTimeZone('UTC')))->bind('foo', Data::fromFlatArray(['foo' => ''])); 37 | $this->assertFalse($bindResult->isSuccess()); 38 | $this->assertCount(1, $bindResult->getFormErrorSequence()); 39 | 40 | $error = iterator_to_array($bindResult->getFormErrorSequence())[0]; 41 | $this->assertSame('foo', $error->getKey()); 42 | $this->assertSame('error.date', $error->getMessage()); 43 | } 44 | 45 | public function testThrowErrorOnBindNonExistentKey() 46 | { 47 | $bindResult = (new DateFormatter(new DateTimeZone('UTC')))->bind('foo', Data::fromFlatArray([])); 48 | $this->assertFalse($bindResult->isSuccess()); 49 | $this->assertCount(1, $bindResult->getFormErrorSequence()); 50 | 51 | $error = iterator_to_array($bindResult->getFormErrorSequence())[0]; 52 | $this->assertSame('foo', $error->getKey()); 53 | $this->assertSame('error.required', $error->getMessage()); 54 | } 55 | 56 | public function testUnbindDateTime() 57 | { 58 | $data = (new DateFormatter(new DateTimeZone('UTC')))->unbind('foo', new DateTimeImmutable('2000-03-04')); 59 | $this->assertSame('2000-03-04', $data->getValue('foo')); 60 | } 61 | 62 | public function testUnbindDateTimeWithDifferentTimeZone() 63 | { 64 | $data = (new DateFormatter(new DateTimeZone('UTC')))->unbind('foo', new DateTimeImmutable( 65 | '2000-03-04 00:00:00', 66 | new DateTimeZone('Europe/Berlin') 67 | )); 68 | $this->assertSame('2000-03-03', $data->getValue('foo')); 69 | } 70 | 71 | public function testUnbindInvalidStringValue() 72 | { 73 | $this->expectException(InvalidTypeException::class); 74 | (new DateFormatter(new DateTimeZone('UTC')))->unbind('foo', '2000-03-04'); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/Mapping/Formatter/DateTimeFormatterTest.php: -------------------------------------------------------------------------------- 1 | assertSame('01:02:00.000000', (new DateTimeFormatter(new DateTimeZone('UTC')))->bind( 21 | 'foo', 22 | Data::fromFlatArray(['foo' => '1970-01-01T01:02+00:00']) 23 | )->getValue()->format('H:i:s.u')); 24 | } 25 | 26 | public function testBindTimeStringWithSeconds() 27 | { 28 | $this->assertSame('01:02:03.000000', (new DateTimeFormatter(new DateTimeZone('UTC')))->bind( 29 | 'foo', 30 | Data::fromFlatArray(['foo' => '1970-01-01T01:02:03+00:00']) 31 | )->getValue()->format('H:i:s.u')); 32 | } 33 | 34 | public function testBindTimeStringWithSecondsAndMicroseconds() 35 | { 36 | $this->assertSame('01:02:03.456789', (new DateTimeFormatter(new DateTimeZone('UTC')))->bind( 37 | 'foo', 38 | Data::fromFlatArray(['foo' => '1970-01-01T01:02:03.456789+00:00']) 39 | )->getValue()->format('H:i:s.u')); 40 | } 41 | 42 | public function testBindToSpecificTimeZone() 43 | { 44 | $dateTime = (new DateTimeFormatter(new DateTimeZone('Europe/Berlin')))->bind( 45 | 'foo', 46 | Data::fromFlatArray(['foo' => '1970-01-01T01:02:03+00:00']) 47 | )->getValue(); 48 | 49 | $this->assertSame('Europe/Berlin', $dateTime->getTimezone()->getName()); 50 | $this->assertSame('1970-01-01T02:02:03', $dateTime->format('Y-m-d\TH:i:s')); 51 | } 52 | 53 | public function testBindEmptyStringValue() 54 | { 55 | $bindResult = (new DateTimeFormatter(new DateTimeZone('UTC')))->bind('foo', Data::fromFlatArray(['foo' => ''])); 56 | $this->assertFalse($bindResult->isSuccess()); 57 | $this->assertCount(1, $bindResult->getFormErrorSequence()); 58 | 59 | $error = iterator_to_array($bindResult->getFormErrorSequence())[0]; 60 | $this->assertSame('foo', $error->getKey()); 61 | $this->assertSame('error.date-time', $error->getMessage()); 62 | } 63 | 64 | public function testThrowErrorOnBindNonExistentKey() 65 | { 66 | $bindResult = (new DateTimeFormatter(new DateTimeZone('UTC')))->bind('foo', Data::fromFlatArray([])); 67 | $this->assertFalse($bindResult->isSuccess()); 68 | $this->assertCount(1, $bindResult->getFormErrorSequence()); 69 | 70 | $error = iterator_to_array($bindResult->getFormErrorSequence())[0]; 71 | $this->assertSame('foo', $error->getKey()); 72 | $this->assertSame('error.required', $error->getMessage()); 73 | } 74 | 75 | public function testUnbindDateTimeWithSeconds() 76 | { 77 | $data = ( 78 | new DateTimeFormatter(new DateTimeZone('UTC')) 79 | )->unbind('foo', new DateTimeImmutable('1970-01-01 01:02:03 UTC')); 80 | $this->assertSame('1970-01-01T01:02:03+00:00', $data->getValue('foo')); 81 | } 82 | 83 | public function testUnbindDateTimeWithSecondsAndMicroseconds() 84 | { 85 | $data = ( 86 | new DateTimeFormatter(new DateTimeZone('UTC')) 87 | )->unbind('foo', new DateTimeImmutable('1970-01-01 01:02:03.456789 UTC')); 88 | $this->assertSame('1970-01-01T01:02:03.456789+00:00', $data->getValue('foo')); 89 | } 90 | 91 | public function testUnbindDateTimeWithDifferentTimeZone() 92 | { 93 | $data = (new DateTimeFormatter(new DateTimeZone('UTC')))->unbind('foo', new DateTimeImmutable( 94 | '1970-01-01 01:02:03.456789', 95 | new DateTimeZone('Europe/Berlin') 96 | )); 97 | $this->assertSame('1970-01-01T00:02:03.456789+00:00', $data->getValue('foo')); 98 | } 99 | 100 | public function testUnbindInvalidStringValue() 101 | { 102 | $this->expectException(InvalidTypeException::class); 103 | (new DateTimeFormatter(new DateTimeZone('UTC')))->unbind('foo', '00:00:00'); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /test/Mapping/Formatter/DecimalFormatterTest.php: -------------------------------------------------------------------------------- 1 | assertSame('42.12', (new DecimalFormatter())->bind( 19 | 'foo', 20 | Data::fromFlatArray(['foo' => '42.12']) 21 | )->getValue()); 22 | } 23 | 24 | public function testBindValidNegativeValue() 25 | { 26 | $this->assertSame('-42.12', (new DecimalFormatter())->bind( 27 | 'foo', 28 | Data::fromFlatArray(['foo' => '-42.12']) 29 | )->getValue()); 30 | } 31 | 32 | public function testBindEmptyStringValue() 33 | { 34 | $bindResult = (new DecimalFormatter())->bind('foo', Data::fromFlatArray(['foo' => ''])); 35 | $this->assertFalse($bindResult->isSuccess()); 36 | $this->assertCount(1, $bindResult->getFormErrorSequence()); 37 | 38 | $error = iterator_to_array($bindResult->getFormErrorSequence())[0]; 39 | $this->assertSame('foo', $error->getKey()); 40 | $this->assertSame('error.float', $error->getMessage()); 41 | } 42 | 43 | public function testThrowErrorOnBindNonExistentKey() 44 | { 45 | $bindResult = (new DecimalFormatter())->bind('foo', Data::fromFlatArray([])); 46 | $this->assertFalse($bindResult->isSuccess()); 47 | $this->assertCount(1, $bindResult->getFormErrorSequence()); 48 | 49 | $error = iterator_to_array($bindResult->getFormErrorSequence())[0]; 50 | $this->assertSame('foo', $error->getKey()); 51 | $this->assertSame('error.required', $error->getMessage()); 52 | } 53 | 54 | public function testUnbindValidPositiveValue() 55 | { 56 | $data = (new DecimalFormatter())->unbind('foo', '42.12'); 57 | $this->assertSame('42.12', $data->getValue('foo')); 58 | } 59 | 60 | public function testUnbindValidNegativeValue() 61 | { 62 | $data = (new DecimalFormatter())->unbind('foo', '-42.12'); 63 | $this->assertSame('-42.12', $data->getValue('foo')); 64 | } 65 | 66 | public function testUnbindInvalidFloatValue() 67 | { 68 | $this->expectException(InvalidTypeException::class); 69 | (new DecimalFormatter())->unbind('foo', 1.1); 70 | } 71 | 72 | public function testUnbindInvalidStringValue() 73 | { 74 | $this->expectException(InvalidTypeException::class); 75 | (new DecimalFormatter())->unbind('foo', 'test'); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/Mapping/Formatter/Exception/InvalidTypeExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 18 | 'Value was expected to be of type foo, but got stdClass', 19 | InvalidTypeException::fromInvalidType(new stdClass(), 'foo')->getMessage() 20 | ); 21 | } 22 | 23 | public function testFromInvalidTypeWithScalar() 24 | { 25 | $this->assertSame( 26 | 'Value was expected to be of type foo, but got boolean', 27 | InvalidTypeException::fromInvalidType(true, 'foo')->getMessage() 28 | ); 29 | } 30 | 31 | public function testFromNonNumericString() 32 | { 33 | $this->assertSame( 34 | 'String was expected to be numeric, but got "test"', 35 | InvalidTypeException::fromNonNumericString('test')->getMessage() 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/Mapping/Formatter/FloatFormatterTest.php: -------------------------------------------------------------------------------- 1 | assertSame(42.12, (new FloatFormatter())->bind( 19 | 'foo', 20 | Data::fromFlatArray(['foo' => '42.12']) 21 | )->getValue()); 22 | } 23 | 24 | public function testBindValidNegativeValue() 25 | { 26 | $this->assertSame(-42.12, (new FloatFormatter())->bind( 27 | 'foo', 28 | Data::fromFlatArray(['foo' => '-42.12']) 29 | )->getValue()); 30 | } 31 | 32 | public function testBindEmptyStringValue() 33 | { 34 | $bindResult = (new FloatFormatter())->bind('foo', Data::fromFlatArray(['foo' => ''])); 35 | $this->assertFalse($bindResult->isSuccess()); 36 | $this->assertCount(1, $bindResult->getFormErrorSequence()); 37 | 38 | $error = iterator_to_array($bindResult->getFormErrorSequence())[0]; 39 | $this->assertSame('foo', $error->getKey()); 40 | $this->assertSame('error.float', $error->getMessage()); 41 | } 42 | 43 | public function testThrowErrorOnBindNonExistentKey() 44 | { 45 | $bindResult = (new FloatFormatter())->bind('foo', Data::fromFlatArray([])); 46 | $this->assertFalse($bindResult->isSuccess()); 47 | $this->assertCount(1, $bindResult->getFormErrorSequence()); 48 | 49 | $error = iterator_to_array($bindResult->getFormErrorSequence())[0]; 50 | $this->assertSame('foo', $error->getKey()); 51 | $this->assertSame('error.required', $error->getMessage()); 52 | } 53 | 54 | public function testUnbindValidPositiveValue() 55 | { 56 | $data = (new FloatFormatter())->unbind('foo', 42.12); 57 | $this->assertSame('42.12', $data->getValue('foo')); 58 | } 59 | 60 | public function testUnbindValidNegativeValue() 61 | { 62 | $data = (new FloatFormatter())->unbind('foo', -42.12); 63 | $this->assertSame('-42.12', $data->getValue('foo')); 64 | } 65 | 66 | public function testUnbindInvalidStringValue() 67 | { 68 | $this->expectException(InvalidTypeException::class); 69 | (new FloatFormatter())->unbind('foo', '1.1'); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/Mapping/Formatter/IgnoredFormatterTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 18 | 'foo', 19 | (new IgnoredFormatter('foo'))->bind('foo', Data::fromFlatArray(['foo' => 'baz']))->getValue() 20 | ); 21 | } 22 | 23 | public function testUnbindValue() 24 | { 25 | $data = (new IgnoredFormatter('foo'))->unbind('foo', 'bar'); 26 | $this->assertTrue($data->isEmpty()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/Mapping/Formatter/IntegerFormatterTest.php: -------------------------------------------------------------------------------- 1 | assertSame(42, (new IntegerFormatter())->bind( 19 | 'foo', 20 | Data::fromFlatArray(['foo' => '42']) 21 | )->getValue()); 22 | } 23 | 24 | public function testBindValidNegativeValue() 25 | { 26 | $this->assertSame(-42, (new IntegerFormatter())->bind( 27 | 'foo', 28 | Data::fromFlatArray(['foo' => '-42']) 29 | )->getValue()); 30 | } 31 | 32 | public function testBindInvalidFloatValue() 33 | { 34 | $bindResult = (new IntegerFormatter())->bind('foo', Data::fromFlatArray(['foo' => '1.1'])); 35 | $this->assertFalse($bindResult->isSuccess()); 36 | $this->assertCount(1, $bindResult->getFormErrorSequence()); 37 | 38 | $error = iterator_to_array($bindResult->getFormErrorSequence())[0]; 39 | $this->assertSame('foo', $error->getKey()); 40 | $this->assertSame('error.integer', $error->getMessage()); 41 | } 42 | 43 | public function testBindEmptyStringValue() 44 | { 45 | $bindResult = (new IntegerFormatter())->bind('foo', Data::fromFlatArray(['foo' => ''])); 46 | $this->assertFalse($bindResult->isSuccess()); 47 | $this->assertCount(1, $bindResult->getFormErrorSequence()); 48 | 49 | $error = iterator_to_array($bindResult->getFormErrorSequence())[0]; 50 | $this->assertSame('foo', $error->getKey()); 51 | $this->assertSame('error.integer', $error->getMessage()); 52 | } 53 | 54 | public function testThrowErrorOnBindNonExistentKey() 55 | { 56 | $bindResult = (new IntegerFormatter())->bind('foo', Data::fromFlatArray([])); 57 | $this->assertFalse($bindResult->isSuccess()); 58 | $this->assertCount(1, $bindResult->getFormErrorSequence()); 59 | 60 | $error = iterator_to_array($bindResult->getFormErrorSequence())[0]; 61 | $this->assertSame('foo', $error->getKey()); 62 | $this->assertSame('error.required', $error->getMessage()); 63 | } 64 | 65 | public function testUnbindValidPositiveValue() 66 | { 67 | $data = (new IntegerFormatter())->unbind('foo', 42); 68 | $this->assertSame('42', $data->getValue('foo')); 69 | } 70 | 71 | public function testUnbindValidNegativeValue() 72 | { 73 | $data = (new IntegerFormatter())->unbind('foo', -42); 74 | $this->assertSame('-42', $data->getValue('foo')); 75 | } 76 | 77 | public function testUnbindInvalidFloatValue() 78 | { 79 | $this->expectException(InvalidTypeException::class); 80 | (new IntegerFormatter())->unbind('foo', 1.1); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /test/Mapping/Formatter/TextFormatterTest.php: -------------------------------------------------------------------------------- 1 | assertSame('bar', (new TextFormatter())->bind( 19 | 'foo', 20 | Data::fromFlatArray(['foo' => 'bar']) 21 | )->getValue()); 22 | } 23 | 24 | public function testBindEmptyStringValue() 25 | { 26 | $bindResult = (new TextFormatter())->bind('foo', Data::fromFlatArray(['foo' => ''])); 27 | $this->assertTrue($bindResult->isSuccess()); 28 | } 29 | 30 | public function testThrowErrorOnBindNonExistentKey() 31 | { 32 | $bindResult = (new TextFormatter())->bind('foo', Data::fromFlatArray([])); 33 | $this->assertFalse($bindResult->isSuccess()); 34 | $this->assertCount(1, $bindResult->getFormErrorSequence()); 35 | 36 | $error = iterator_to_array($bindResult->getFormErrorSequence())[0]; 37 | $this->assertSame('foo', $error->getKey()); 38 | $this->assertSame('error.required', $error->getMessage()); 39 | } 40 | 41 | public function testUnbindValidValue() 42 | { 43 | $data = (new TextFormatter())->unbind('foo', 'bar'); 44 | $this->assertSame('bar', $data->getValue('foo')); 45 | } 46 | 47 | public function testUnbindInvalidValue() 48 | { 49 | $this->expectException(InvalidTypeException::class); 50 | (new TextFormatter())->unbind('foo', 1); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/Mapping/Formatter/TimeFormatterTest.php: -------------------------------------------------------------------------------- 1 | assertSame('01:02:00.000000', (new TimeFormatter(new DateTimeZone('UTC')))->bind( 21 | 'foo', 22 | Data::fromFlatArray(['foo' => '01:02']) 23 | )->getValue()->format('H:i:s.u')); 24 | } 25 | 26 | public function testBindTimeStringWithSeconds() 27 | { 28 | $this->assertSame('01:02:03.000000', (new TimeFormatter(new DateTimeZone('UTC')))->bind( 29 | 'foo', 30 | Data::fromFlatArray(['foo' => '01:02:03']) 31 | )->getValue()->format('H:i:s.u')); 32 | } 33 | 34 | public function testBindTimeStringWithSecondsAndMicroseconds() 35 | { 36 | $this->assertSame('01:02:03.456789', (new TimeFormatter(new DateTimeZone('UTC')))->bind( 37 | 'foo', 38 | Data::fromFlatArray(['foo' => '01:02:03.456789']) 39 | )->getValue()->format('H:i:s.u')); 40 | } 41 | 42 | public function testBindToSpecificTimeZone() 43 | { 44 | $this->assertSame('Europe/Berlin', (new TimeFormatter(new DateTimeZone('Europe/Berlin')))->bind( 45 | 'foo', 46 | Data::fromFlatArray(['foo' => '01:02:03']) 47 | )->getValue()->getTimezone()->getName()); 48 | } 49 | 50 | public function testBindEmptyStringValue() 51 | { 52 | $bindResult = (new TimeFormatter(new DateTimeZone('UTC')))->bind('foo', Data::fromFlatArray(['foo' => ''])); 53 | $this->assertFalse($bindResult->isSuccess()); 54 | $this->assertCount(1, $bindResult->getFormErrorSequence()); 55 | 56 | $error = iterator_to_array($bindResult->getFormErrorSequence())[0]; 57 | $this->assertSame('foo', $error->getKey()); 58 | $this->assertSame('error.time', $error->getMessage()); 59 | } 60 | 61 | public function testThrowErrorOnBindNonExistentKey() 62 | { 63 | $bindResult = (new TimeFormatter(new DateTimeZone('UTC')))->bind('foo', Data::fromFlatArray([])); 64 | $this->assertFalse($bindResult->isSuccess()); 65 | $this->assertCount(1, $bindResult->getFormErrorSequence()); 66 | 67 | $error = iterator_to_array($bindResult->getFormErrorSequence())[0]; 68 | $this->assertSame('foo', $error->getKey()); 69 | $this->assertSame('error.required', $error->getMessage()); 70 | } 71 | 72 | public function testUnbindDateTimeWithSeconds() 73 | { 74 | $data = ( 75 | new TimeFormatter(new DateTimeZone('UTC')) 76 | )->unbind('foo', new DateTimeImmutable('1970-01-01 01:02:03 UTC')); 77 | $this->assertSame('01:02:03', $data->getValue('foo')); 78 | } 79 | 80 | public function testUnbindDateTimeWithSecondsAndMicroseconds() 81 | { 82 | $data = ( 83 | new TimeFormatter(new DateTimeZone('UTC')) 84 | )->unbind('foo', new DateTimeImmutable('1970-01-01 01:02:03.456789 UTC')); 85 | $this->assertSame('01:02:03.456789', $data->getValue('foo')); 86 | } 87 | 88 | public function testUnbindDateTimeWithDifferentTimeZone() 89 | { 90 | $data = (new TimeFormatter(new DateTimeZone('UTC')))->unbind('foo', new DateTimeImmutable( 91 | '1970-01-01 01:02:03.456789', 92 | new DateTimeZone('Europe/Berlin') 93 | )); 94 | $this->assertSame('00:02:03.456789', $data->getValue('foo')); 95 | } 96 | 97 | public function testUnbindInvalidStringValue() 98 | { 99 | $this->expectException(InvalidTypeException::class); 100 | (new TimeFormatter(new DateTimeZone('UTC')))->unbind('foo', '00:00:00'); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /test/Mapping/MappingTraitTestTrait.php: -------------------------------------------------------------------------------- 1 | getInstanceForTraitTests(); 14 | $mappingB = $mappingA->verifying($this->prophesize(ConstraintInterface::class)->reveal()); 15 | $mappingC = $mappingB->verifying($this->prophesize(ConstraintInterface::class)->reveal()); 16 | 17 | $this->assertNotSame($mappingA, $mappingB); 18 | $this->assertNotSame($mappingB, $mappingC); 19 | $this->assertAttributeCount(0, 'constraints', $mappingA); 20 | $this->assertAttributeCount(1, 'constraints', $mappingB); 21 | $this->assertAttributeCount(2, 'constraints', $mappingC); 22 | } 23 | 24 | abstract protected function getInstanceForTraitTests() : MappingInterface; 25 | } 26 | -------------------------------------------------------------------------------- /test/Mapping/TestAsset/SimpleObject.php: -------------------------------------------------------------------------------- 1 | foo = $foo; 21 | $this->bar = $bar; 22 | } 23 | 24 | public function getFoo() : string 25 | { 26 | return $this->foo; 27 | } 28 | 29 | public function getBar() : string 30 | { 31 | return $this->bar; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/Transformer/CallbackTransformerTest.php: -------------------------------------------------------------------------------- 1 | assertSame('foobar', $transformer('bar', 'foo')); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/Transformer/TrimTransformerTest.php: -------------------------------------------------------------------------------- 1 | assertSame('foo', $transformer("\0\r\n foo\0\r\n ", '')); 18 | } 19 | } 20 | --------------------------------------------------------------------------------