diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c451dda --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.yml] +indent_size = 2 diff --git a/.github/ISSUE_TEMPLATE/Bug.md b/.github/ISSUE_TEMPLATE/Bug.md new file mode 100644 index 0000000..f563f05 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug.md @@ -0,0 +1,24 @@ +--- +name: 🐛 Bug +about: Did you encounter a bug? +--- + +### Bug Report + + + +| Q | A +|------------ | ------ +| BC Break | yes/no +| Version | x.y.z + +#### Summary + + + +#### How to reproduce + + diff --git a/.github/ISSUE_TEMPLATE/Feature_Request.md b/.github/ISSUE_TEMPLATE/Feature_Request.md new file mode 100644 index 0000000..de0f669 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_Request.md @@ -0,0 +1,21 @@ +--- +name: 🎉 Feature Request +about: Do you have a new feature in mind? +--- + +### Feature Request + + + +| Q | A +|------------ | ------ +| New Feature | yes/no +| BC Break | yes/no + +#### Scenario / Use-case + + + +#### Summary + + diff --git a/.github/ISSUE_TEMPLATE/Question.md b/.github/ISSUE_TEMPLATE/Question.md new file mode 100644 index 0000000..fa8e763 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Question.md @@ -0,0 +1,8 @@ +--- +name: ❓ Question +about: Are you unsure about something? +--- + +### Question + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3fa51b5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,51 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + strategy: + matrix: + php: ['8.1', '8.2', '8.3'] + include: + - php: '8.1' + send-to-scrutinizer: 'yes' + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup PHP with fail-fast + uses: shivammathur/setup-php@v2 + with: + send-to-scrutinizer: 'no' + phpunit-flags: '--no-coverage' + php-version: ${{ matrix.php }} + coverage: xdebug + env: + fail-fast: true + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Run test suite + run: | + mkdir -p build/logs + composer run stan + composer run test diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..dfb2eff --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,28 @@ +name: Publish docs + +on: + push: + tags: + - '*.*.*' +jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + + - name: Build docs + run: | + curl -OS https://couscous.io/couscous.phar + php couscous.phar generate --target=build/docs/ ./docs + + - name: FTP Deployer + uses: sand4rt/ftp-deployer@v1.1 + with: + host: ${{ secrets.DOCS_FTP_HOST }} + username: ${{ secrets.DOCS_FTP_USER }} + password: ${{ secrets.DOCS_FTP_PASSWORD }} + remote_folder: validation + # The local folder location + local_folder: build/docs/ + # Remove existing files inside FTP remote folder + cleanup: false # optional diff --git a/.gitignore b/.gitignore index c5048e4..8c1a9f2 100755 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,9 @@ composer.lock website/ docs/.couscous docs/couscous.phar +php-cs-fixer.phar +phpcbf.phar +phpcs.phar +phpmd.phar +.phpunit.result.cache +.php_cs.cache diff --git a/.travis.yml b/.travis.yml deleted file mode 100755 index 37fecbf..0000000 --- a/.travis.yml +++ /dev/null @@ -1,26 +0,0 @@ -language: php - -php: - - 5.3 - - 5.4 - - 5.5 - - 5.6 - - hhvm - - '7' - -matrix: - allow_failures: - - php: 5.3 - -before_script: - - composer self-update - - composer install --prefer-source - -script: - - mkdir -p build/logs - - cd tests - - phpunit - -after_script: - - wget https://scrutinizer-ci.com/ocular.phar - - php ocular.phar code-coverage:upload --format=php-clover ../build/logs/clover.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 40416ca..c1cc350 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,23 @@ +#### 3.0.0 + +- Simplified the API (ie: less options to create rules) +- Expanded the ability to set custom error messages per selector (eg: "address.city.required") + +#### 2.3.0 + +- Removed 5.* support + +#### 2.2.0 + +- added `Upload\Required` rule +- fixed upload validators when the upload was not successful + #### 2.0.0 -- added the `data_key:label` feature +- added the `data_selector:label` feature - implemented default rule messages in the `RuleFactory` class. This way, if you have a custom error message for the `required` fields, you don't have to provide it to all the required fields, just set it up once in the `RuleFactory` +- implemented a way to allow validation rule options to be passed as CSV (eg: '100,200' instead of 'min=200&max=200') + #### 1.2.4 diff --git a/composer.json b/composer.json index bd4d042..e902d5d 100755 --- a/composer.json +++ b/composer.json @@ -17,15 +17,32 @@ } ], "require": { - "php": ">=5.3" - }, - "require-dev": { - "phpunit/phpunit": "3.7", - "satooshi/php-coveralls": "dev-master" + "php": ">=8.0" }, "autoload": { - "files": [ - "autoload.php" + "psr-4": { + "Sirius\\Validation\\": "src/" + } + }, + "scripts": { + "stan": [ + "php vendor/bin/phpstan analyse" + ], + "csfix": [ + "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --standard=PSR-2 src" + ], + "test": [ + "php vendor/bin/pest" ] + }, + "require-dev": { + "pestphp/pest": "*", + "phpstan/phpstan": "^1.10", + "pestphp/pest-plugin-drift": "^2.5" + }, + "config": { + "allow-plugins": { + "pestphp/pest-plugin": true + } } } diff --git a/docs/complex_validators.md b/docs/complex_validators.md index 838f2fb..716f7b6 100644 --- a/docs/complex_validators.md +++ b/docs/complex_validators.md @@ -50,7 +50,7 @@ $UniqueUsername = $dependencyInjectionContainer->get('UniqueUsername'); $validator = new Validator($ruleFactory); // the second parameter for the add() can be the name of a rule or a callback -$validator->add('username', array($UniqueUsername, 'validate')); +$validator->add('username', [$UniqueUsername, 'validate'];); ``` ###2. Pass the dependencies as options @@ -76,9 +76,9 @@ and in your validator you do something like ``` $dbConn = $serviceLocator->get('dbconnection'); -$validator->add('username', 'MyApp\Validation\Rule\UniqueUsername', array( +$validator->add('username', 'MyApp\Validation\Rule\UniqueUsername', [ 'db_connection' => $dbConn -)); +];); ``` ###3. Extend the `RuleFactory` class @@ -100,7 +100,7 @@ class RuleFactory extends \Sirius\Validation\RuleFactory { protected function constructValidatorByNameAndOptions($name, $options) { $validatorClass = $this->validatorsMap[$name]; - $validator = $this->dic->createInstanceWithParams($validatorClass, array($options)); + $validator = $this->dic->createInstanceWithParams($validatorClass, [$options];); return $validator; } } @@ -136,4 +136,4 @@ use Sirius\Validation\Validator; $validator = new Validator($container->get('RuleFactory')); $validator->add('username', 'MyApp\Validation\Rule\UniqueUsername'); -``` \ No newline at end of file +``` diff --git a/docs/couscous.yml b/docs/couscous.yml index 3055226..38aa5c7 100644 --- a/docs/couscous.yml +++ b/docs/couscous.yml @@ -12,7 +12,9 @@ exclude: # Base URL of the published website (no "/" at the end!) # You are advised to set and use this variable to write your links in the HTML layouts -baseUrl: http://www.sirius.ro/php/validation +baseUrl: //www.sirius.ro/php/sirius/validation +paypal: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SGXDKNJCXFPJU +gacode: UA-535999-18 projectName: Sirius\Validation title: Sirius\Validation @@ -83,4 +85,4 @@ menu: relativeUrl: custom_rule.html translate_messages: text: Translating the error messages - relativeUrl: translate_messages.html \ No newline at end of file + relativeUrl: translate_messages.html diff --git a/docs/custom_rule.md b/docs/custom_rule.md index f10ef51..953707a 100644 --- a/docs/custom_rule.md +++ b/docs/custom_rule.md @@ -21,18 +21,18 @@ class ThisOrThat extends AbstractRule { const OPTION_THAT = 'that'; // specify default options if you want - protected $options = array( + protected $options = [ 'this' => 'a', 'that' => 'b' - ); + ];; // if you want to let the user pass the options as a CSV (eg: 'this,that') // you need to provide a `optionsIndexMap` property which will convert the options list // into an associative array of options - protected $optionsIndexMap = array( + protected $optionsIndexMap = [ 0 => self::OPTION_THIS, 1 => self::OPTION_THAT - ); + ];; function validate($value, $valueIdentifier = null) { return $value == $this->options[self::OPTION_THIS] || $value == $this->options[self::OPTION_THAT]; @@ -50,11 +50,12 @@ use Sirius\Validation\Validator; use MyApp\Validation\Rule\ThisOrThat; $validator = new Validator(); -$validator->add('key', 'MyApp\Validation\Rule\ThisOrThat', array( +$validator->add('key', 'MyApp\Validation\Rule\ThisOrThat', [ ThisOrThat::OPTION_THIS => 'c', ThisOrThat::OPTION_THAT => 'd' -)); +];); // or less verbose +$validator->add('key', 'MyApp\Validation\Rule\ThisOrThat(this=c&that=d)'); $validator->add('key', 'MyApp\Validation\Rule\ThisOrThat(c,d)'); -``` \ No newline at end of file +``` diff --git a/docs/index.md b/docs/index.md index a796e83..f15d166 100755 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@ Sirius Validation is a library for data validation. It offers: 1. [validator object](validator.md) 2. [45 build-in validation rules](validation_rules.md). There are validators for strings, array, numbers, emails, URLs, files and uploads -3. [validation helper](helper.md) to simplify the validation of single values +3. [validation helper](validation_helper.md) to simplify the validation of single values Out-of-the-box, the library can handle `array`s, `ArrayObject`s and objects that have implemented the `toArray` method. In order to validate other data containers you must create a [`DataWrapper`](https://github.com/siriusphp/validation/blob/master/src/Validation/DataWrapper/WrapperInterface.php) so that the validator be able to extract data from your object. @@ -24,20 +24,29 @@ $validation = new \Sirius\Validation\Validator; // let's validate an invoice form $validator->add(array( - 'date:Date' => 'required | date', - 'client_id:Client' => 'required | clientexists', // clientexists is an app-specific rule - 'notify_recipients[*]:Send invoice to' => 'email', // same rule for an array of items + // :Date specifies the label for the field. It will be used in the error messages + 'order_date:Date' => 'required | date', + // `clientexists` is an app-specific rule + 'client_id:Client' => 'required | clientexists', + // apply the same rule for an array of items + 'notify_recipients[*]:Send invoice to' => 'email', + // apply a rule to a specific item in the array 'shipping_address[line_1]:Address' => 'required' 'shipping_address[city]:City' => 'required' 'shipping_address[state]:State' => 'required' 'shipping_address[country]:Country' => 'required', - 'lines[*]price:Price' => array( - 'requiredWith(item=lines[*]product_id', // the price is required only if a product was selected - 'MyApp\Validator\Rule\InvoiceItemPrice' // another app-specific rule, specified as a class - ), - 'lines[*]quantity:Quantity' => array( - 'requiredWith(item=lines[*]product_id', - ('invoice_item_quantity', 'The quantity is not valid') // here we have a custom error message + 'lines[*]price:Price' => [ + // the price is required only if a product was selected + 'requiredWith(item=lines[*]product_id)', + // another app-specific rule applied to the price, specified as a class + 'MyApp\Validator\Rule\InvoiceItemPrice' + ];, + 'lines[*]quantity:Quantity' => [ + // the price is required only if a product was selected + 'requiredWith(item=lines[*]product_id)', + // here we have a custom validation rule with no parameters AND a custom error message + 'quantity_in_stock()(The quantity is not valid)' + ; ) )); ``` @@ -56,7 +65,7 @@ This may seem counter-productive but remember that your forms' input fields may ``` 2. Because, If I am to do server side validation I can receive a JSON -```javascript +```json { "errors": { "recipients[0]": "Field must be a valid email", @@ -82,4 +91,4 @@ $clientSideRules = $helperThatCompilesTheClientSideValidation->getValidationRule ?> $('#myForm').validate(); -``` \ No newline at end of file +``` diff --git a/docs/installation.md b/docs/installation.md index 86f9f0b..7dc2d9c 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -8,7 +8,7 @@ title: Installating Sirius\Validation Sirius\Validation is available on [Packagist](https://packagist.org/packages/siriusphp/validation) so you can use ``` -composer require sirius/validation +composer require siriusphp/validation ``` Make sure to include the Composer autoload file in your project @@ -18,4 +18,4 @@ require 'vendor/autoload.php'; ## Downloading a `.zip` file -This project is also available for download as a `.zip` file on GitHub. Visit the [releases page](https://github.com/siriusphp/validation/releases), select the version you want, and click the "Source code (zip)" download button. \ No newline at end of file +This project is also available for download as a `.zip` file on GitHub. Visit the [releases page](https://github.com/siriusphp/validation/releases), select the version you want, and click the "Source code (zip)" download button. diff --git a/docs/simple_example.md b/docs/simple_example.md index 1f45e6b..2464d6d 100644 --- a/docs/simple_example.md +++ b/docs/simple_example.md @@ -13,7 +13,7 @@ use Sirius\Validation\Validator; $validator = new Validator(); $validator->add( - array( + [ // the key is in the form [field]:[label] 'name:Name' => 'required', @@ -22,7 +22,7 @@ $validator->add( 'email:Your email' => 'required | email', // validators can have options - 'message:Your message' => 'required | minlength(10)' + 'message:Your message' => 'required | minlength(10];', // and you can overwrite the default error message 'phone:Phone' => 'regex(/your_regex_here/)(This field must be a valid US phone number)' @@ -47,4 +47,4 @@ if ($validator->validate($_POST)) { } ``` -Easy-peasy, right? \ No newline at end of file +Easy-peasy, right? diff --git a/docs/syntactic_sugar.md b/docs/syntactic_sugar.md index 55b36db..d487766 100644 --- a/docs/syntactic_sugar.md +++ b/docs/syntactic_sugar.md @@ -18,7 +18,7 @@ $validator->add('name', 'minlength({"min":2})({label} must have at least {min} c $validator->add('name', 'minlength(min=2)({label} must have at least {min} characters)(Name)'); // the above examples are similar to -$validator->add('name', 'minlength', array('min' => 2), '{label} must have at least {min} characters', 'Name'); +$validator->add('name', 'minlength', ['min' => 2];, '{label} must have at least {min} characters', 'Name'); ``` ##### 3. Mix and match 1 and 2 @@ -33,11 +33,11 @@ Of course this means the error message cannot contain the ` | ` sequence $validator->add( // add the label after the selector so you don't have to pass the label to every rule 'email:Email', - array( + [ // only using the name of the validation rule 'email', - // or with all parameters (here passed as CSV) - array('length', '2,100', '{label} must have between {min} and {max} characters'), + // or with all parameters (here passed as CSV]; + ['length', '2,100', '{label} must have between {min} and {max} characters'];, ) ); ``` diff --git a/docs/translate_messages.md b/docs/translate_messages.md index 687725b..062bd47 100644 --- a/docs/translate_messages.md +++ b/docs/translate_messages.md @@ -4,11 +4,10 @@ title: Translating the error messages # Translating the error messages -There are a couple of ways to translate the error messages, each having it's pros and cons: +There are a couple of ways to translate the error messages, each having its pros and cons: 1. Postpone the translation until display 2. Use a translatable error message class -3. Use translated string for the message templates ### 1.Postpone the translation until display @@ -34,15 +33,6 @@ class TranslatableErrorMessage extends Sirius\Validation\ErrorMessage { } // later when constructing your validators -$validator = new Sirius\Validation\Validator(null, new TranslatableErrorMessage); -``` - -### 3.Use translated string for the message templates - -```php -// in the validator -$validator->add('title:' . __('Title'), 'maxlength', 'max=100', __('{label} must have less than {max} characters')); - -// or the rule factory -$ruleFactoryInstance->setErrorMessages('maxlength', __('This field must have less than {max} characters'), __('{label} must have less than {max} characters')); +$translator = new MyTranslator(); +$validator = new Sirius\Validation\Validator(null, new TranslatableErrorMessage($translator)); ``` diff --git a/docs/validation_rules.md b/docs/validation_rules.md index bb0eaeb..e4e270d 100644 --- a/docs/validation_rules.md +++ b/docs/validation_rules.md @@ -22,14 +22,16 @@ title: Built-in validation rules ### Array validators 1. `ArrayLength`: values which are arrays must contain a specific number of items. Rule options: `min` and `max` 2. `ArrayMinlength`: array must contain at least a specific number of items: Rule options: `min` -3. `ArrayMinlength`: array must contain at most a specific number of items: Rule options: `max` -4. `InList: the value must be in a list of acceptable values. Rule options: `list` -5. `NotInList: the value must not be in a list of forbidden values: Rule options: `list` +3. `ArrayMaxlength`: array must contain at most a specific number of items: Rule options: `max` +4. `InList`: the value must be in a list of acceptable values. Rule options: `list` +5. `NotInList`: the value must not be in a list of forbidden values: Rule options: `list` ### Number validators -7. `Between`: value must be a number between 2 limits: Rule options: `min` and `max` -8. `LessThan`: value must be less than a number. Rule options: `max` and `inclusive` (to determine if the comparator is < or <=, defaults to TRUE) -9. `GreaterThan`: value must be greater than a number. Rule options: `min` and `inclusive` (to determine if the comparator is > or >=, defaults to TRUE) +1. `Number`: value must be a valid number +2. `Integer`: value must be a valid integer +3. `LessThan`: value must be less than a number. Rule options: `max` and `inclusive` (to determine if the comparator is < or <=, defaults to TRUE) +4. `GreaterThan`: value must be greater than a number. Rule options: `min` and `inclusive` (to determine if the comparator is > or >=, defaults to TRUE) +5. `Between`: value must be a number between 2 limits: Rule options: `min` and `max`. The same as `GreaterThan` and `LessThan`, both inclusive. ### Email/URLs validators 1. `Email`: value must be an email address. Uses a regular expression for validation @@ -47,10 +49,13 @@ title: Built-in validation rules 2. `NotRegex`: value must NOT match a regular expression pattern. Rule options: `pattern` 3. `Callback`: checks if a value is valid using a custom callback (a function, an object's method, a class' static method). Rule options: `callback` and `arguments` (additional paramters for the callback) 4. `Match`: the value must match the value of another item in the context. Rule options: `item` (eg: if `auth[password_confirm]` must match `auth[password]` the `item` is `auth[password]` -5. `Equal`: the value must be the same as predefined value. Rule options: `value` +5. `NotMatch': the value must not match the value of another item in the context. Rule options: `item` (eg: if `auth[password]` must not match `auth[username]` the `item` is `auth[username]` +6. `Equal`: the value must be the same as predefined value. Rule options: `value` +7. `NotEqual`: the value must not be the same as predefined value. Rule options: `value` ### File validators File validators work only with local files and they fail if the file does not exist + 1. `File\Extension`. Checks if the file has a certain extension. Rule options: `allowed` which can be an array or a comma separated string. 2. `File\Image`. Checks if the file is an image of a certain type. Rule options: `allowed` which can be an array or a comma separated string (default: `jpg,png,gif`) 3. `File\ImageRatio`. Checks if the image has a certain ratio. Rule options: `ratio` which can be a number or a string like `4:3`, `error_margin` - how much the file's ratio can deviate from the target (default: 0) @@ -60,11 +65,13 @@ File validators work only with local files and they fail if the file does not ex ### Upload validators Upload validators work only uploaded files (each file is an upload-like array) and they fail if the temporary file does not exist. -1. `Upload\Extension`. Checks if the uploaded file has a certain extension. Rule options: `allowed` which can be an array or a comma separated string. -2. `Upload\Image`. Checks if the uploaded file is an image of a certain type. Rule options: `allowed` which can be an array or a comma separated string (default: `jpg,png,gif`) -3. `Upload\ImageRatio`. Checks if the uploaded image has a certain ratio. Rule options: `ratio` which can be a number or a string like `4:3`, `error_margin` - how much the file's ratio can deviate from the target (default: 0) -4. `Upload\ImageWidth`. Checks if the uploaded image's width is between certain limits. Rule options: `min` (default: 0) and `max` (default: 1 million) -5. `Upload\ImageHeight`. Checks if the uploaded image's height is between certain limits. Rule options: `min` (default: 0) and `max` (default: 1 million) -6. `Upload\Size`. Checks if the uploaded file' size is bellow a certain limit. Rule options: `size` which can be a number or a string like '10K', '0.5M' or '1.3G` (default: 2M) -*Note!* The upload validators use only the `tmp_name` and `name` values to perform the validation \ No newline at end of file +1. `Upload\Required`. Checks if the uploaded file was uploaded and there are no errors. +2. `Upload\Extension`. Checks if the uploaded file has a certain extension. Rule options: `allowed` which can be an array or a comma separated string. +3. `Upload\Image`. Checks if the uploaded file is an image of a certain type. Rule options: `allowed` which can be an array or a comma separated string (default: `jpg,png,gif`) +4. `Upload\ImageRatio`. Checks if the uploaded image has a certain ratio. Rule options: `ratio` which can be a number or a string like `4:3`, `error_margin` - how much the file's ratio can deviate from the target (default: 0) +5. `Upload\ImageWidth`. Checks if the uploaded image's width is between certain limits. Rule options: `min` (default: 0) and `max` (default: 1 million) +6. `Upload\ImageHeight`. Checks if the uploaded image's height is between certain limits. Rule options: `min` (default: 0) and `max` (default: 1 million) +7. `Upload\Size`. Checks if the uploaded file' size is bellow a certain limit. Rule options: `size` which can be a number or a string like '10K', '0.5M' or '1.3G` (default: 2M) + +*Note!* The upload validators use only the `tmp_name` and `name` values to perform the validation diff --git a/docs/validator.md b/docs/validator.md index ab03f46..3ec881f 100755 --- a/docs/validator.md +++ b/docs/validator.md @@ -29,7 +29,7 @@ $validator->add($selector, $name = null, $options = null, $messageTemplate = nul // examples $validator->add('username', 'required'); -$validator->add('password', 'minLength', array('min' => 6), '{label} must have at least {min} characters', 'Password'); +$validator->add('password', 'minLength', ['min' => 6];, '{label} must have at least {min} characters', 'Password'); $validator->add('additional_emails[*]', 'email', array(), 'Email address is not valid'); ``` diff --git a/phpmd.xml b/phpmd.xml new file mode 100644 index 0000000..9294e77 --- /dev/null +++ b/phpmd.xml @@ -0,0 +1,28 @@ + + + Sirius PMD ruleset + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..6615f46 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 8 + checkGenericClassInNonGenericObjectType: false + paths: + - src diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..7d0904f --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,18 @@ + + + + + ./tests + + + + + ./app + ./src + + + diff --git a/readme.md b/readme.md index 5dabd7c..88c66e9 100755 --- a/readme.md +++ b/readme.md @@ -1,12 +1,12 @@ -#Sirius Validation +# Sirius Validation -[![Source Code](http://img.shields.io/badge/source-siriusphp/validation-blue.svg?style=flat-square)](https://github.com/siriusphp/validation) -[![Latest Version](https://img.shields.io/packagist/v/siriusphp/validation.svg?style=flat-square)](https://github.com/siriusphp/validation/releases) -[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/siriusphp/validation/blob/master/LICENSE) -[![Build Status](https://img.shields.io/travis/siriusphp/validation/master.svg?style=flat-square)](https://travis-ci.org/siriusphp/validation) -[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/siriusphp/validation.svg?style=flat-square)](https://scrutinizer-ci.com/g/siriusphp/validation/code-structure) -[![Quality Score](https://img.shields.io/scrutinizer/g/siriusphp/validation.svg?style=flat-square)](https://scrutinizer-ci.com/g/siriusphp/validation) -[![Total Downloads](https://img.shields.io/packagist/dt/siriusphp/validation.svg?style=flat-square)](https://packagist.org/packages/siriusphp/validation) +[![Source Code](http://img.shields.io/badge/source-siriusphp/validation-blue.svg)](https://github.com/siriusphp/validation) +[![Latest Version](https://img.shields.io/packagist/v/siriusphp/validation.svg)](https://github.com/siriusphp/validation/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/siriusphp/validation/blob/master/LICENSE) +[![Build Status](https://github.com/siriusphp/validation/workflows/CI/badge.svg)](https://github.com/siriusphp/validation/actions) +[![Coverage Status](https://scrutinizer-ci.com/g/siriusphp/validation/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/siriusphp/validation/code-structure) +[![Quality Score](https://scrutinizer-ci.com/g/siriusphp/validation/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/siriusphp/validation) +[![Total Downloads](https://img.shields.io/packagist/dt/siriusphp/validation.svg)](https://packagist.org/packages/siriusphp/validation) Sirius Validation is a library for data validation. It offers: @@ -17,10 +17,10 @@ Sirius Validation is a library for data validation. It offers: Out-of-the-box, the library can handle `array`s, `ArrayObject`s and objects that have implemented the `toArray` method. In order to validate other data containers you must create a [`DataWrapper`](https://github.com/siriusphp/validation/blob/master/src/Validation/DataWrapper/WrapperInterface.php) so that the validator be able to extract data from your object. -##Elevator pitch +## Elevator pitch ```php -$validation = new \Sirius\Validation\Validator; +$validator = new \Sirius\Validation\Validator; // add a validation rule $validator->add('title', 'required'); @@ -40,18 +40,17 @@ $validator->add('title', 'maxlength', 'max=100', 'Article title must have less t // add a rule with a custom message and a label (very handy with forms) $validator->add('title:Title', 'maxlength', 'max=100', '{label} must have less than {max} characters'); -// add all of rule's configuration in a string (you'll see later why it's handy') -$validator->add('title:Title', 'maxlength(max=255)({label} must have less than {max} characters)'); - // add multiple rules at once (separate using [space][pipe][space]) $validator->add('title:Title', 'required | maxlength(255) | minlength(min=10)'); // add all your rules at once -$validator->add(array( - 'title:Title' => 'required | maxlength(100)({label} must have less than {max} characters)', - 'content:Content' => 'required', - 'source:Source' => 'website' -)); +$validator->add([ + 'title:Title' => 'required | maxlength(100)', + 'content:Content' => 'required', + 'source:Source' => 'website' + ], [ + 'content.required' => 'The content field should have a velue' + ]); // add nested rules $validator->add('recipients[*]:Recipients', 'email'); //all recipients must be valid email addresses @@ -59,13 +58,8 @@ $validator->add('shipping_address[city]:City', 'MyApp\Validator\City'); // uses ``` -##Links +## Links -- [documentation](http://www.sirius.ro/php/sirius/validation) +- [documentation](http://sirius.ro/php/sirius/validation/) - [changelog](CHANGELOG.md) -##Known issues - -In PHP 5.3 there is some problem with the SplObject storage that prevents the library to remove validation rules. -This means that in PHP 5.3, you cannot remove a validation rule from a `Validator` or `ValueValidator` object - diff --git a/src/DataWrapper/ArrayWrapper.php b/src/DataWrapper/ArrayWrapper.php index e473fec..42985e6 100644 --- a/src/DataWrapper/ArrayWrapper.php +++ b/src/DataWrapper/ArrayWrapper.php @@ -1,23 +1,24 @@ */ - protected $data = array(); + protected array $data = []; /** - * @param array|\ArrayObject|object $data + * @param array|\ArrayObject|object $data + * * @throws \InvalidArgumentException */ - public function __construct($data = array()) + public function __construct(mixed $data) { if (is_object($data)) { if ($data instanceof \ArrayObject) { @@ -32,12 +33,15 @@ public function __construct($data = array()) $this->data = $data; } - public function getItemValue($item) + public function getItemValue(string $item): mixed { return Arr::getByPath($this->data, $item); } - public function getItemsBySelector($selector) + /** + * @return array + */ + public function getItemsBySelector(string $selector): array { return Arr::getBySelector($this->data, $selector); } diff --git a/src/DataWrapper/WrapperInterface.php b/src/DataWrapper/WrapperInterface.php index 331201b..1ccdb93 100644 --- a/src/DataWrapper/WrapperInterface.php +++ b/src/DataWrapper/WrapperInterface.php @@ -1,4 +1,5 @@ */ - public function getItemsBySelector($selector); - + public function getItemsBySelector(string $selector): array; } diff --git a/src/ErrorMessage.php b/src/ErrorMessage.php index 57da774..967cb1d 100755 --- a/src/ErrorMessage.php +++ b/src/ErrorMessage.php @@ -1,29 +1,45 @@ + */ + protected array $variables = []; + + /** + * @param array $variables + */ + public function __construct(string $template = '', array $variables = []) { $this->setTemplate($template) ->setVariables($variables); } - public function setTemplate($template) + public function setTemplate(string $template): self { - $template = trim((string)$template); + $template = trim($template); if ($template) { - $this->template = (string)$template; + $this->template = $template; } return $this; } - public function setVariables($variables = array()) + public function getTemplate(): string + { + return $this->template; + } + + /** + * @param array $variables + */ + public function setVariables(array $variables = []): self { foreach ($variables as $k => $v) { $this->variables[$k] = $v; @@ -32,12 +48,20 @@ public function setVariables($variables = array()) return $this; } + /** + * @return array + */ + public function getVariables(): array + { + return $this->variables; + } + public function __toString() { $result = $this->template; foreach ($this->variables as $k => $v) { if (strpos($result, "{{$k}}") !== false) { - $result = str_replace("{{$k}}", $v, $result); + $result = str_replace("{{$k}}", (string)$v, (string)$result); } } diff --git a/src/Helper.php b/src/Helper.php index 12c2cad..45eefe1 100755 --- a/src/Helper.php +++ b/src/Helper.php @@ -1,30 +1,39 @@ + */ + protected static array $methods = []; - protected static $methods = array(); - - public static function addMethod($ruleName, $callback) + /** + * @param callable|\Closure $callback + */ + public static function addMethod(string $ruleName, mixed $callback): void { if (is_callable($callback)) { self::$methods[$ruleName] = $callback; - - return true; + return; } - return false; + throw new \InvalidArgumentException(sprintf('Validation method "%s" is not callable', $ruleName)); // @phpstan-ignore-line } - public static function methodExists($name) + public static function methodExists(string $name): bool { return method_exists(__CLASS__, $name) || array_key_exists($name, self::$methods); } - public static function __callStatic($name, $arguments) + /** + * @param array $arguments + */ + public static function __callStatic(string $name, array $arguments = []): mixed { if (array_key_exists($name, self::$methods)) { return call_user_func_array(self::$methods[$name], $arguments); @@ -32,7 +41,10 @@ public static function __callStatic($name, $arguments) throw new \InvalidArgumentException(sprintf('Validation method "%s" does not exist', $name)); } - public static function callback($value, $callback, $context = array()) + /** + * @param array $context + */ + public static function callback(mixed $value, callable $callback, array $context = []): bool { $validator = new Rule\Callback(); $validator->setOption('callback', $callback); @@ -41,209 +53,181 @@ public static function callback($value, $callback, $context = array()) return $validator->validate($value); } - public static function required($value) + public static function required(mixed $value): bool { - return $value !== null && trim($value) !== ''; + return $value !== null && (!is_string($value) || trim($value) !== ''); } - public static function truthy($value) + public static function truthy(mixed $value): bool { return (bool)$value; } - public static function falsy($value) + public static function falsy(mixed $value): bool { return !static::truthy($value); } - public static function number($value) + public static function number(mixed $value): bool { return $value == '0' || is_numeric($value); } - public static function integer($value) + public static function integer(mixed $value): bool { return $value == '0' || (int)$value == $value; } - public static function lessThan($value, $max) + public static function lessThan(mixed $value, int|float $max): bool { - $validator = new Rule\LessThan( - array( - 'max' => $max - ) - ); + $validator = new Rule\LessThan(['max' => $max]); return $validator->validate($value); } - public static function greaterThan($value, $min) + public static function greaterThan(mixed $value, int|float $min): bool { - $validator = new Rule\GreaterThan( - array( - 'min' => $min - ) - ); + $validator = new Rule\GreaterThan(['min' => $min]); return $validator->validate($value); } - public static function between($value, $min, $max) + public static function between(mixed $value, int|float $min, int|float $max): bool { - $validator = new Rule\Between( - array( - 'min' => $min, - 'max' => $max - ) - ); + $validator = new Rule\Between([ + 'min' => $min, + 'max' => $max + ]); return $validator->validate($value); } - public static function exactly($value, $otherValue) + public static function exactly(mixed $value, mixed $otherValue): bool { return $value == $otherValue; } - public static function not($value, $otherValue) + public static function not(mixed $value, mixed $otherValue): bool { return !self::exactly($value, $otherValue); } - public static function alpha($value) + public static function alpha(mixed $value): bool { $validator = new Rule\Alpha(); return $validator->validate($value); } - public static function alphanumeric($value) + public static function alphanumeric(mixed $value): bool { $validator = new Rule\AlphaNumeric(); return $validator->validate($value); } - public static function alphanumhyphen($value) + public static function alphanumhyphen(mixed $value): bool { $validator = new Rule\AlphaNumHyphen(); return $validator->validate($value); } - public static function minLength($value, $min) + public static function minLength(string $value, int $min): bool { - $validator = new Rule\MinLength( - array( - 'min' => $min - ) - ); + $validator = new Rule\MinLength(['min' => $min]); return $validator->validate($value); } - public static function maxLength($value, $max) + public static function maxLength(string $value, int $max): bool { - $validator = new Rule\MaxLength( - array( - 'max' => $max - ) - ); + $validator = new Rule\MaxLength(['max' => $max]); return $validator->validate($value); } - public static function length($value, $min, $max) + public static function length(string $value, int $min, int $max): bool { - $validator = new Rule\Length( - array( - 'min' => $min, - 'max' => $max - ) - ); + $validator = new Rule\Length([ + 'min' => $min, + 'max' => $max + ]); return $validator->validate($value); } - public static function setMinSize($value, $min) + /** + * @param array $value + */ + public static function setMinSize(array $value, int $min): bool { - $validator = new Rule\ArrayMinLength( - array( - 'min' => $min - ) - ); + $validator = new Rule\ArrayMinLength(['min' => $min]); return $validator->validate($value); } - public static function setMaxSize($value, $max) + /** + * @param array $value + */ + public static function setMaxSize(array $value, int $max): bool { - $validator = new Rule\ArrayMaxLength( - array( - 'max' => $max - ) - ); + $validator = new Rule\ArrayMaxLength(['max' => $max]); return $validator->validate($value); } - public static function setSize($value, $min, $max) + /** + * @param array $value + */ + public static function setSize(array $value, int $min, int $max): bool { - $validator = new Rule\ArrayLength( - array( - 'min' => $min, - 'max' => $max - ) - ); + $validator = new Rule\ArrayLength([ + 'min' => $min, + 'max' => $max + ]); return $validator->validate($value); } - public static function in($value, $values) + /** + * @param list $values + */ + public static function inList(mixed $value, array $values): bool { - $validator = new Rule\InList( - array( - 'list' => $values - ) - ); + $validator = new Rule\InList(['list' => $values]); return $validator->validate($value); } - public static function notIn($value, $values) + /** + * @param list $values + */ + public static function notInList(mixed $value, array $values): bool { - $validator = new Rule\NotInList( - array( - 'list' => $values - ) - ); + $validator = new Rule\NotInList(['list' => $values]); return $validator->validate($value); } - public static function regex($value, $pattern) + public static function regex(mixed $value, string $pattern): bool { - $validator = new Rule\Regex( - array( - 'pattern' => $pattern - ) - ); + $validator = new Rule\Regex(['pattern' => $pattern]); return $validator->validate($value); } - public static function notRegex($value, $pattern) + public static function notRegex(mixed $value, string $pattern): bool { - $validator = new Rule\NotRegex( - array( - 'pattern' => $pattern - ) - ); + $validator = new Rule\NotRegex(['pattern' => $pattern]); return $validator->validate($value); } - public static function equalTo($value, $otherElementOrValue, $context = null) + /** + * @param array $context + */ + public static function equalTo(mixed $value, mixed $otherElementOrValue, array $context = []): bool { if (func_num_args() == 2) { return $value == $otherElementOrValue; @@ -252,67 +236,61 @@ public static function equalTo($value, $otherElementOrValue, $context = null) return $value == Arr::getByPath($context, $otherElementOrValue); } - public static function date($value, $format = 'Y-m-d') + /** + * @param array $context + */ + public static function notEqualTo(mixed $value, mixed $otherElementOrValue, array $context = []): bool + { + if (func_num_args() == 2) { + return $value != $otherElementOrValue; + } + + return $value != Arr::getByPath($context, $otherElementOrValue); + } + + public static function date(mixed $value, string $format = 'Y-m-d'): bool { - $validator = new Rule\Date( - array( - 'format' => $format - ) - ); + $validator = new Rule\Date(['format' => $format]); return $validator->validate($value); } - public static function dateTime($value, $format = 'Y-m-d H:i:s') + public static function dateTime(mixed $value, string $format = 'Y-m-d H:i:s'): bool { - $validator = new Rule\DateTime( - array( - 'format' => $format - ) - ); + $validator = new Rule\DateTime(['format' => $format]); return $validator->validate($value); } - public static function time($value, $format = 'H:i:s') + public static function time(mixed $value, string $format = 'H:i:s'): bool { - $validator = new Rule\Time( - array( - 'format' => $format - ) - ); + $validator = new Rule\Time(['format' => $format]); return $validator->validate($value); } - public static function website($value) + public static function website(mixed $value): bool { $validator = new Rule\Website(); return $validator->validate($value); } - public static function url($value) + public static function url(mixed $value): bool { $validator = new Rule\Url(); return $validator->validate($value); } - /** - * Test if a variable is a valid IP address - * - * @param string $value - * @return bool - */ - public static function ip($value) + public static function ipAddress(mixed $value): bool { $validator = new Rule\IpAddress(); return $validator->validate($value); } - public static function email($value) + public static function email(mixed $value): bool { $validator = new Rule\Email(); @@ -322,11 +300,8 @@ public static function email($value) /** * Test if a variable is a full name * Criterias: at least 6 characters, 2 words - * - * @param mixed $value - * @return bool */ - public static function fullName($value) + public static function fullName(mixed $value): bool { $validator = new Rule\FullName(); @@ -335,11 +310,8 @@ public static function fullName($value) /** * Test if the domain of an email address is available - * - * @param string $value - * @return bool */ - public static function emailDomain($value) + public static function emailDomain(mixed $value): bool { $validator = new Rule\EmailDomain(); diff --git a/src/Rule/AbstractRule.php b/src/Rule/AbstractRule.php index 8a67074..a61fd4d 100644 --- a/src/Rule/AbstractRule.php +++ b/src/Rule/AbstractRule.php @@ -1,9 +1,12 @@ */ - protected $options = array(); + protected array $options = []; /** * Custom error message template for the validator instance * If you don't agree with the default messages that were provided - * - * @var string */ - protected $messageTemplate; + protected string $messageTemplate; /** * Result of the last validation - * - * @var boolean */ - protected $success = false; + protected bool $success = false; /** * Last value validated with the validator. * Stored in order to be passed to the errorMessage so that you get error * messages like '"abc" is not a valid email' - * - * @var mixed */ - protected $value; + protected mixed $value = null; /** * The error message prototype that will be used to generate the error message - * - * @var ErrorMessage */ - protected $errorMessagePrototype; - - public function __construct($options = array()) - { - $options = $this->normalizeOptions($options); - if (is_array($options) && !empty($options)) { - foreach ($options as $k => $v) { - $this->setOption($k, $v); - } - } - } - - /** - * Method that parses the option variable and converts it into an array - * You can pass anything to a validator like: - * - a query string: 'min=3&max=5' - * - a JSON string: '{"min":3,"max":5}' - * - a CSV string: '5,true' (for this scenario the 'optionsIndexMap' property is required) - * - * @param mixed $options - * - * @return array - * @throws \InvalidArgumentException - */ - protected function normalizeOptions($options) - { - if (!$options) { - return array(); - } - - if (is_array($options) && $this->arrayIsAssoc($options)) { - return $options; - } - - $result = $options; - if ($options && is_string($options)) { - $startChar = substr($options, 0, 1); - if ($startChar == '{') { - $result = json_decode($options, true); - } elseif (strpos($options, '=') !== false) { - $result = $this->parseHttpQueryString($options); - } else { - $result = $this->parseCsvString($options); - } - } - - if (!is_array($result)) { - throw new \InvalidArgumentException('Validator options should be an array, JSON string or query string'); - } - - return $result; - } + protected ?ErrorMessage $errorMessagePrototype = null; /** - * Converts a HTTP query string to an array + * Options map in case the options are passed as list instead of associative array * - * @param $str - * @return array + * @var array */ - protected function parseHttpQueryString($str) { - parse_str($str, $arr); - return $this->convertBooleanStrings($arr); - } + protected array $optionsIndexMap = []; /** - * Converts 'true' and 'false' strings to TRUE and FALSE - * - * @param $v - * @return bool + * @param array|string|null $options */ - protected function convertBooleanStrings($v) { - if (is_array($v)) { - return array_map(array($this, 'convertBooleanStrings'), $v); - } - if ($v === 'true') { - return true; - } - if ($v === 'false') { - return false; - } - return $v; - } - - /** - * Parses a CSV string and converts the result into an "options" array - * (an associative array that contains the options for the validation rule) - * - * @param $str - * @return array - */ - protected function parseCsvString($str) { - if (!isset($this->optionsIndexMap) || !is_array($this->optionsIndexMap) || empty($this->optionsIndexMap)) { - throw new \InvalidArgumentException(sprintf('Class %s is missing the `optionsIndexMap` property', get_class($this))); - } - - $options = explode(',', $str); - $result = array(); - foreach ($options as $k => $v) { - if (!isset($this->optionsIndexMap[$k])) { - throw new \InvalidArgumentException(sprintf('Class %s does not have the index %d configured in the `optionsIndexMap` property', get_class($this), $k)); + public function __construct(mixed $options = null) + { + $options = RuleHelper::normalizeOptions($options, $this->optionsIndexMap); + if (is_array($options) && !empty($options)) { + foreach ($options as $k => $v) { + $this->setOption((string) $k, $v); } - $result[$this->optionsIndexMap[$k]] = $v; } - return $this->convertBooleanStrings($result); } - /** - * Checks if an array is associative (ie: the keys are not numbers in sequence) - * - * @param array $arr - * @return bool - */ - protected function arrayIsAssoc($arr) - { - return array_keys($arr) !== range(0, count($arr)); - } - - /** * Generates a unique string to identify the validator. * It is used to compare 2 validators so you don't add the same rule twice in a validator object - * - * @return string */ - public function getUniqueId() + public function getUniqueId(): string { return get_called_class() . '|' . json_encode(ksort($this->options)); } @@ -190,29 +87,39 @@ public function getUniqueId() * Set an option for the validator. * * The options are also be passed to the error message. - * - * @param string $name - * @param mixed $value - * @return \Sirius\Validation\Rule\AbstractRule */ - public function setOption($name, $value) + public function setOption(string $name, mixed $value): static { $this->options[$name] = $value; return $this; } + /** + * Get an option for the validator. + * + * @return mixed + */ + public function getOption(string $name) + { + if (isset($this->options[$name])) { + return $this->options[$name]; + } else { + return null; + } + } + /** * The context of the validator can be used when the validator depends on other values * that are not known at the moment the validator is constructed * For example, when you need to validate an email field matches another email field, * to confirm the email address * - * @param array|object $context + * @param array|WrapperInterface $context + * * @throws \InvalidArgumentException - * @return \Sirius\Validation\Rule\AbstractRule */ - public function setContext($context = null) + public function setContext(mixed $context = null): self { if ($context === null) { return $this; @@ -220,9 +127,10 @@ public function setContext($context = null) if (is_array($context)) { $context = new ArrayWrapper($context); } - if (!is_object($context) || !$context instanceof WrapperInterface) { + if (!$context instanceof WrapperInterface) { throw new \InvalidArgumentException( - 'Validator context must be either an array or an instance of Sirius\Validator\DataWrapper\WrapperInterface' + 'Validator context must be either an array or an instance + of ' . WrapperInterface::class ); } $this->context = $context; @@ -234,7 +142,8 @@ public function setContext($context = null) * Custom message for this validator to used instead of the the default one * * @param string $messageTemplate - * @return \Sirius\Validation\Rule\AbstractRule + * + * @return AbstractRule */ public function setMessageTemplate($messageTemplate) { @@ -248,9 +157,9 @@ public function setMessageTemplate($messageTemplate) * * @return string */ - public function getMessageTemplate() + public function getMessageTemplate(): string { - if ($this->messageTemplate) { + if (isset($this->messageTemplate)) { return $this->messageTemplate; } if (isset($this->options['label'])) { @@ -262,23 +171,15 @@ public function getMessageTemplate() /** * Validates a value - * - * @param mixed $value - * @param null|mixed $valueIdentifier - * @return mixed */ - abstract function validate($value, $valueIdentifier = null); + abstract public function validate(mixed $value, string $valueIdentifier = null): bool; /** - * Sets the error message prototype that will be used when returning the error message - * when validation fails. + * Sets the error message prototype that will be used when + * returning the error message if validation fails. * This option can be used when you need translation - * - * @param ErrorMessage $errorMessagePrototype - * @throws \InvalidArgumentException - * @return \Sirius\Validation\Rule\AbstractRule */ - public function setErrorMessagePrototype(ErrorMessage $errorMessagePrototype) + public function setErrorMessagePrototype(ErrorMessage $errorMessagePrototype): self { $this->errorMessagePrototype = $errorMessagePrototype; @@ -291,7 +192,7 @@ public function setErrorMessagePrototype(ErrorMessage $errorMessagePrototype) * * @return ErrorMessage */ - public function getErrorMessagePrototype() + public function getErrorMessagePrototype(): ErrorMessage { if (!$this->errorMessagePrototype) { $this->errorMessagePrototype = new ErrorMessage(); @@ -302,20 +203,16 @@ public function getErrorMessagePrototype() /** * Retrieve the error message if validation failed - * - * @return NULL|\Sirius\Validation\ErrorMessage */ - public function getMessage() + public function getMessage(): ?ErrorMessage { if ($this->success) { return null; } $message = $this->getPotentialMessage(); - $message->setVariables( - array( - 'value' => $this->value - ) - ); + $message->setVariables([ + 'value' => $this->value + ]); return $message; } @@ -326,9 +223,9 @@ public function getMessage() * * @return ErrorMessage */ - public function getPotentialMessage() + public function getPotentialMessage(): ErrorMessage { - $message = clone ($this->getErrorMessagePrototype()); + $message = clone $this->getErrorMessagePrototype(); $message->setTemplate($this->getMessageTemplate()); $message->setVariables($this->options); @@ -339,15 +236,11 @@ public function getPotentialMessage() * Method for determining the path to a related item. * Eg: for `lines[5][price]` the related item `lines[*][quantity]` * has the value identifier as `lines[5][quantity]` - * - * @param $valueIdentifier - * @param $relatedItem - * @return string|null */ - protected function getRelatedValueIdentifier($valueIdentifier, $relatedItem) + protected function getRelatedValueIdentifier(string $valueIdentifier, string $relatedItem): string|null { // in case we don't have a related path - if (strpos($relatedItem, '*') === false) { + if (!str_contains($relatedItem, '*')) { return $relatedItem; } @@ -361,7 +254,7 @@ protected function getRelatedValueIdentifier($valueIdentifier, $relatedItem) } // the result should be ['lines', '5', 'quantity'] - $relatedValueIdentifierParts = array(); + $relatedValueIdentifierParts = []; foreach ($relatedItemParts as $index => $part) { if ($part === '*' && isset($valueIdentifierParts[$index])) { $relatedValueIdentifierParts[] = $valueIdentifierParts[$index]; @@ -371,9 +264,10 @@ protected function getRelatedValueIdentifier($valueIdentifier, $relatedItem) } $relatedValueIdentifier = implode('][', $relatedValueIdentifierParts) . ']'; - $relatedValueIdentifier = str_replace($relatedValueIdentifierParts[0] . ']', $relatedValueIdentifierParts[0], - $relatedValueIdentifier); - - return $relatedValueIdentifier; + return str_replace( + $relatedValueIdentifierParts[0] . ']', + $relatedValueIdentifierParts[0], + $relatedValueIdentifier + ); } } diff --git a/src/Rule/AbstractStringRule.php b/src/Rule/AbstractStringRule.php new file mode 100644 index 0000000..67fca90 --- /dev/null +++ b/src/Rule/AbstractStringRule.php @@ -0,0 +1,19 @@ +options['encoding'] ?? mb_internal_encoding() + ); + } + + return strlen($str); + } +} diff --git a/src/Rule/Alpha.php b/src/Rule/Alpha.php index 7445581..76f216c 100755 --- a/src/Rule/Alpha.php +++ b/src/Rule/Alpha.php @@ -1,16 +1,17 @@ value = $value; - $this->success = (bool)ctype_alpha((string)str_replace(' ', '', $value)); + $this->success = (bool)ctype_alpha((string)str_replace(' ', '', (string)$value)); return $this->success; } diff --git a/src/Rule/AlphaNumHyphen.php b/src/Rule/AlphaNumHyphen.php index 4d94e3f..a9a8423 100755 --- a/src/Rule/AlphaNumHyphen.php +++ b/src/Rule/AlphaNumHyphen.php @@ -1,24 +1,25 @@ value = $value; $this->success = (bool)ctype_alnum( - (string)str_replace( - array( + str_replace( + [ ' ', '_', '-' - ), + ], '', - $value + (string) $value ) ); diff --git a/src/Rule/AlphaNumeric.php b/src/Rule/AlphaNumeric.php index 925c64b..a8ef000 100755 --- a/src/Rule/AlphaNumeric.php +++ b/src/Rule/AlphaNumeric.php @@ -1,14 +1,15 @@ value = $value; $this->success = (bool)ctype_alnum((string)str_replace(' ', '', $value)); diff --git a/src/Rule/ArrayLength.php b/src/Rule/ArrayLength.php index 373223d..5934a8f 100755 --- a/src/Rule/ArrayLength.php +++ b/src/Rule/ArrayLength.php @@ -1,23 +1,22 @@ self::OPTION_MIN, 1 => self::OPTION_MAX - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; $maxValidator = new ArrayMaxLength(); diff --git a/src/Rule/ArrayMaxLength.php b/src/Rule/ArrayMaxLength.php index 82cb763..d4f7df9 100755 --- a/src/Rule/ArrayMaxLength.php +++ b/src/Rule/ArrayMaxLength.php @@ -1,22 +1,21 @@ value = $value; if (!isset($this->options['max'])) { diff --git a/src/Rule/ArrayMinLength.php b/src/Rule/ArrayMinLength.php index 609ab68..efb8495 100755 --- a/src/Rule/ArrayMinLength.php +++ b/src/Rule/ArrayMinLength.php @@ -1,21 +1,20 @@ self::OPTION_MIN - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (!isset($this->options['min'])) { diff --git a/src/Rule/Between.php b/src/Rule/Between.php index 285f6ea..fd1b1cc 100755 --- a/src/Rule/Between.php +++ b/src/Rule/Between.php @@ -1,23 +1,22 @@ self::OPTION_MIN, 1 => self::OPTION_MAX - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; $minValidator = new LessThan(); diff --git a/src/Rule/Callback.php b/src/Rule/Callback.php index 3ec24e3..7b873f8 100755 --- a/src/Rule/Callback.php +++ b/src/Rule/Callback.php @@ -1,9 +1,10 @@ options['callback'])) { $uniqueId .= '|' . $this->options['callback']; } elseif (is_array($this->options['callback'])) { - // the callback is an array that points to a static class method (eg: array('MyClass', 'method')) + // the callback is an array that points to a static class method (eg: ['MyClass', 'method']) if (is_string($this->options['callback'][0])) { $uniqueId .= '|' . implode('::', $this->options['callback']); } elseif (is_object($this->options['callback'][0])) { @@ -39,13 +40,13 @@ public function getUniqueId() return $uniqueId; } - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (!isset($this->options['callback']) || !is_callable($this->options['callback'])) { $this->success = true; } else { - $args = (isset($this->options['arguments'])) ? (array)$this->options['arguments'] : array(); + $args = (isset($this->options['arguments'])) ? (array)$this->options['arguments'] : []; array_unshift($args, $value); array_push($args, $valueIdentifier, $this->context); $this->success = (bool)call_user_func_array($this->options['callback'], $args); diff --git a/src/Rule/Date.php b/src/Rule/Date.php index 0e4a9f1..303ff48 100644 --- a/src/Rule/Date.php +++ b/src/Rule/Date.php @@ -1,37 +1,46 @@ 'Y-m-d' - ); + ]; - protected $optionsIndexMap = array( + protected array $optionsIndexMap = [ 0 => self::OPTION_FORMAT - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; - $this->success = $value == date($this->options['format'], - $this->getTimestampFromFormatedString($value, $this->options['format'])); + $this->success = $value == date( + (string) $this->options['format'], + $this->getTimestampFromFormatedString($value, $this->options['format']) + ); return $this->success; } - protected function getTimestampFromFormatedString($string, $format) + protected function getTimestampFromFormatedString(mixed $string, mixed $format): ?int { $result = date_parse_from_format($format, $string); - return mktime((int)$result['hour'], (int)$result['minute'], (int)$result['second'], (int)$result['month'], - (int)$result['day'], (int)$result['year']); + return mktime( + (int)$result['hour'], + (int)$result['minute'], + (int)$result['second'], + (int)$result['month'], + (int)$result['day'], + (int)$result['year'] + ) ?: null; } } diff --git a/src/Rule/DateTime.php b/src/Rule/DateTime.php index a48e7f7..c6d63f6 100644 --- a/src/Rule/DateTime.php +++ b/src/Rule/DateTime.php @@ -1,14 +1,15 @@ 'Y-m-d H:i:s' - ); + ]; } diff --git a/src/Rule/Email.php b/src/Rule/Email.php index be8005f..77d7b43 100755 --- a/src/Rule/Email.php +++ b/src/Rule/Email.php @@ -1,14 +1,15 @@ value = $value; $this->success = (filter_var((string)$value, FILTER_VALIDATE_EMAIL) !== false); diff --git a/src/Rule/EmailDomain.php b/src/Rule/EmailDomain.php index 5d3ed6c..a7cf903 100755 --- a/src/Rule/EmailDomain.php +++ b/src/Rule/EmailDomain.php @@ -1,18 +1,24 @@ value = $value; // Check if the email domain has a valid MX record - $this->success = (bool)checkdnsrr(preg_replace('/^[^@]+@/', '', $value), 'MX'); + $host = preg_replace('/^[^@]+@/', '', $value); + if ($host) { + $this->success = (bool)checkdnsrr($host, 'MX'); + } else { + $this->success = false; + } return $this->success; } diff --git a/src/Rule/Equal.php b/src/Rule/Equal.php index d931162..f6adc57 100644 --- a/src/Rule/Equal.php +++ b/src/Rule/Equal.php @@ -1,19 +1,20 @@ self::OPTION_VALUE - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (isset($this->options[self::OPTION_VALUE])) { diff --git a/src/Rule/File/Extension.php b/src/Rule/File/Extension.php index dc7e140..69c1c80 100644 --- a/src/Rule/File/Extension.php +++ b/src/Rule/File/Extension.php @@ -1,23 +1,24 @@ array() - ); + protected array $options = [ + self::OPTION_ALLOWED_EXTENSIONS => [] + ]; - public function setOption($name, $value) + public function setOption(string $name, mixed $value): static { if ($name == self::OPTION_ALLOWED_EXTENSIONS) { if (is_string($value)) { @@ -30,7 +31,7 @@ public function setOption($name, $value) return parent::setOption($name, $value); } - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (!file_exists($value)) { @@ -46,15 +47,13 @@ public function validate($value, $valueIdentifier = null) return $this->success; } - public function getPotentialMessage() + public function getPotentialMessage(): ErrorMessage { $message = parent::getPotentialMessage(); $fileExtensions = array_map('strtoupper', $this->options[self::OPTION_ALLOWED_EXTENSIONS]); - $message->setVariables( - array( - 'file_extensions' => implode(', ', $fileExtensions) - ) - ); + $message->setVariables([ + 'file_extensions' => implode(', ', $fileExtensions) + ]); return $message; } diff --git a/src/Rule/File/Image.php b/src/Rule/File/Image.php index 00abe80..0454609 100644 --- a/src/Rule/File/Image.php +++ b/src/Rule/File/Image.php @@ -1,33 +1,38 @@ array('jpg', 'png', 'gif') - ); + protected array $options = [ + self::OPTION_ALLOWED_IMAGES => ['jpg', 'png', 'gif'] + ]; - protected $imageTypesMap = array( - IMAGETYPE_GIF => 'gif', - IMAGETYPE_JPEG => 'jpg', + /** + * @var array + */ + protected array $imageTypesMap = [ + IMAGETYPE_GIF => 'gif', + IMAGETYPE_JPEG => 'jpg', IMAGETYPE_JPEG2000 => 'jpg', - IMAGETYPE_PNG => 'png', - IMAGETYPE_PSD => 'psd', - IMAGETYPE_BMP => 'bmp', - IMAGETYPE_ICO => 'ico', - ); + IMAGETYPE_PNG => 'png', + IMAGETYPE_PSD => 'psd', + IMAGETYPE_BMP => 'bmp', + IMAGETYPE_ICO => 'ico', + IMAGETYPE_WEBP => 'webp', + ]; - public function setOption($name, $value) + public function setOption(string $name, mixed $value): static { if ($name == self::OPTION_ALLOWED_IMAGES) { if (is_string($value)) { @@ -40,29 +45,31 @@ public function setOption($name, $value) return parent::setOption($name, $value); } - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (!file_exists($value)) { $this->success = false; } else { $imageInfo = getimagesize($value); - $extension = isset($this->imageTypesMap[$imageInfo[2]]) ? $this->imageTypesMap[$imageInfo[2]] : false; - $this->success = ($extension && in_array($extension, $this->options[self::OPTION_ALLOWED_IMAGES])); + if (!is_array($imageInfo)) { + $this->success = false; + } else { + $extension = $this->imageTypesMap[$imageInfo[2]] ?? false; + $this->success = ($extension && in_array($extension, $this->options[self::OPTION_ALLOWED_IMAGES])); + } } return $this->success; } - public function getPotentialMessage() + public function getPotentialMessage(): ErrorMessage { $message = parent::getPotentialMessage(); $imageTypes = array_map('strtoupper', $this->options[self::OPTION_ALLOWED_IMAGES]); - $message->setVariables( - array( - 'image_types' => implode(', ', $imageTypes) - ) - ); + $message->setVariables([ + 'image_types' => implode(', ', $imageTypes) + ]); return $message; } diff --git a/src/Rule/File/ImageHeight.php b/src/Rule/File/ImageHeight.php index 610a5e7..1675bfc 100644 --- a/src/Rule/File/ImageHeight.php +++ b/src/Rule/File/ImageHeight.php @@ -1,4 +1,6 @@ 1000000, self::OPTION_MIN => 0, - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (!file_exists($value)) { @@ -25,7 +27,9 @@ public function validate($value, $valueIdentifier = null) } else { $imageInfo = getimagesize($value); $height = isset($imageInfo[1]) ? $imageInfo[1] : 0; - $this->success = $height && $height <= $this->options[self::OPTION_MAX] && $height >= $this->options[self::OPTION_MIN]; + $this->success = $height && + $height <= $this->options[self::OPTION_MAX] && + $height >= $this->options[self::OPTION_MIN]; } return $this->success; diff --git a/src/Rule/File/ImageRatio.php b/src/Rule/File/ImageRatio.php index dd89169..706b865 100644 --- a/src/Rule/File/ImageRatio.php +++ b/src/Rule/File/ImageRatio.php @@ -1,7 +1,10 @@ 0, + protected array $options = [ + self::OPTION_RATIO => 0, self::OPTION_ERROR_MARGIN => 0, - ); + ]; - protected function normalizeRatio($ratio) + protected function normalizeRatio(mixed $ratio): float { if (is_numeric($ratio) || $ratio == filter_var($ratio, FILTER_SANITIZE_NUMBER_FLOAT)) { return floatval($ratio); @@ -28,24 +31,30 @@ protected function normalizeRatio($ratio) if (strpos($ratio, ':') !== false) { list($width, $height) = explode(':', $ratio); - return $width / $height; + return (float) $width / (float) $height; } return 0; } - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; - $ratio = $this->normalizeRatio($this->options[self::OPTION_RATIO]); + $ratio = RuleHelper::normalizeImageRatio($this->options[self::OPTION_RATIO]); if (!file_exists($value)) { $this->success = false; } elseif ($ratio == 0) { $this->success = true; } else { $imageInfo = getimagesize($value); - $actualRatio = $imageInfo[0] / $imageInfo[1]; - $this->success = abs($actualRatio - $ratio) <= $this->options[self::OPTION_ERROR_MARGIN]; + + if (is_array($imageInfo)) { + $actualRatio = $imageInfo[0] / $imageInfo[1]; + $this->success = abs($actualRatio - $ratio) <= $this->options[self::OPTION_ERROR_MARGIN]; + } else { + // no image size computed => no valid image + return $this->success = false; + } } return $this->success; diff --git a/src/Rule/File/ImageWidth.php b/src/Rule/File/ImageWidth.php index dadb32a..b44b592 100644 --- a/src/Rule/File/ImageWidth.php +++ b/src/Rule/File/ImageWidth.php @@ -1,4 +1,6 @@ 1000000, self::OPTION_MIN => 0, - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (!file_exists($value)) { @@ -25,7 +27,9 @@ public function validate($value, $valueIdentifier = null) } else { $imageInfo = getimagesize($value); $width = isset($imageInfo[0]) ? $imageInfo[0] : 0; - $this->success = $width && $width <= $this->options[self::OPTION_MAX] && $width >= $this->options[self::OPTION_MIN]; + $this->success = $width && + $width <= $this->options[self::OPTION_MAX] && + $width >= $this->options[self::OPTION_MIN]; } return $this->success; diff --git a/src/Rule/File/Size.php b/src/Rule/File/Size.php index b61ed0d..f53bd83 100644 --- a/src/Rule/File/Size.php +++ b/src/Rule/File/Size.php @@ -1,7 +1,10 @@ '2M' - ); - - protected function normalizeSize($size) - { - $units = array('B' => 0, 'K' => 1, 'M' => 2, 'G' => 3); - $unit = strtoupper(substr($size, strlen($size) - 1, 1)); - if (!isset($units[$unit])) { - $normalizedSize = filter_var($size, FILTER_SANITIZE_NUMBER_INT); - } else { - $size = filter_var(substr($size, 0, strlen($size) - 1), FILTER_SANITIZE_NUMBER_FLOAT); - $normalizedSize = $size * pow(1024, $units[$unit]); - } - - return $normalizedSize; - } + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (!file_exists($value)) { $this->success = false; } else { $fileSize = @filesize($value); - $limit = $this->normalizeSize($this->options[self::OPTION_SIZE]); + $limit = RuleHelper::normalizeFileSize($this->options[self::OPTION_SIZE]); $this->success = $fileSize && $fileSize <= $limit; } diff --git a/src/Rule/FullName.php b/src/Rule/FullName.php index 95d0b1b..b93905d 100755 --- a/src/Rule/FullName.php +++ b/src/Rule/FullName.php @@ -1,16 +1,17 @@ value = $value; diff --git a/src/Rule/GreaterThan.php b/src/Rule/GreaterThan.php index b57c5de..62b77ab 100755 --- a/src/Rule/GreaterThan.php +++ b/src/Rule/GreaterThan.php @@ -1,25 +1,26 @@ true - ); + ]; - protected $optionsIndexMap = array( + protected array $optionsIndexMap = [ 0 => self::OPTION_MIN, 1 => self::OPTION_INCLUSIVE - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (!isset($this->options['min'])) { diff --git a/src/Rule/InList.php b/src/Rule/InList.php index a25940c..6fe8772 100755 --- a/src/Rule/InList.php +++ b/src/Rule/InList.php @@ -1,20 +1,20 @@ self::OPTION_LIST - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (!isset($this->options['list'])) { diff --git a/src/Rule/Integer.php b/src/Rule/Integer.php index 8c99723..58dc2b5 100644 --- a/src/Rule/Integer.php +++ b/src/Rule/Integer.php @@ -1,16 +1,17 @@ value = $value; - $this->success = (bool)filter_var($value, FILTER_VALIDATE_INT) || (string)$value === '0'; + $this->success = (bool)filter_var($value, FILTER_VALIDATE_INT) || $value === '0'; return $this->success; } diff --git a/src/Rule/IpAddress.php b/src/Rule/IpAddress.php index d477d42..648dea6 100755 --- a/src/Rule/IpAddress.php +++ b/src/Rule/IpAddress.php @@ -1,21 +1,22 @@ value = $value; // Do not allow private and reserved range IPs $flags = FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE; if (strpos($value, ':') !== false) { - $this->success = (bool)filter_var($value, FILTER_VALIDATE_IP, $flags | FILTER_FLAG_IPV6); + $this->success = (bool)filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_IPV6); } else { - $this->success = (bool)filter_var($value, FILTER_VALIDATE_IP, $flags | FILTER_FLAG_IPV4); + $this->success = (bool)filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_IPV4); } return $this->success; diff --git a/src/Rule/Length.php b/src/Rule/Length.php index 60f919e..b963f9c 100755 --- a/src/Rule/Length.php +++ b/src/Rule/Length.php @@ -1,30 +1,31 @@ self::OPTION_MIN, - 1 => self::OPTION_MAX - ); + 1 => self::OPTION_MAX, + 2 => self::OPTION_ENCODING + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; - $maxValidator = new MinLength(); + $maxValidator = new MaxLength(); if (isset($this->options['max'])) { $maxValidator->setOption('max', $this->options['max']); } - $minValidator = new MaxLength(); + $minValidator = new MinLength(); if (isset($this->options['min'])) { $minValidator->setOption('min', $this->options['min']); } diff --git a/src/Rule/LessThan.php b/src/Rule/LessThan.php index 5d2c149..08d14aa 100755 --- a/src/Rule/LessThan.php +++ b/src/Rule/LessThan.php @@ -1,25 +1,26 @@ true - ); + ]; - protected $optionsIndexMap = array( + protected array $optionsIndexMap = [ 0 => self::OPTION_MAX, 1 => self::OPTION_INCLUSIVE - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (!isset($this->options['max'])) { diff --git a/src/Rule/Match.php b/src/Rule/Matching.php similarity index 57% rename from src/Rule/Match.php rename to src/Rule/Matching.php index 3360efe..4df3900 100644 --- a/src/Rule/Match.php +++ b/src/Rule/Matching.php @@ -1,23 +1,24 @@ self::OPTION_ITEM - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (isset($this->options[self::OPTION_ITEM])) { - $this->success = ($value == $this->context->getItemValue($this->options[self::OPTION_ITEM])); + $this->success = $this->context && ($value == $this->context->getItemValue($this->options[self::OPTION_ITEM])); } else { $this->success = true; } diff --git a/src/Rule/MaxLength.php b/src/Rule/MaxLength.php index 7a60d2d..90ecbc6 100755 --- a/src/Rule/MaxLength.php +++ b/src/Rule/MaxLength.php @@ -1,27 +1,28 @@ self::OPTION_MAX - ); + protected array $optionsIndexMap = [ + 0 => self::OPTION_MAX, + 1 => self::OPTION_ENCODING + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (!isset($this->options['max'])) { $this->success = true; } else { - $this->success = strlen($value) <= $this->options['max']; + $this->success = $this->getStringLength($value) <= $this->options['max']; } return $this->success; diff --git a/src/Rule/MinLength.php b/src/Rule/MinLength.php index 2c95f8a..9c58134 100755 --- a/src/Rule/MinLength.php +++ b/src/Rule/MinLength.php @@ -1,27 +1,28 @@ self::OPTION_MIN - ); + protected array $optionsIndexMap = [ + 0 => self::OPTION_MIN, + 1 => self::OPTION_ENCODING + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (!isset($this->options['min'])) { $this->success = true; } else { - $this->success = strlen($value) >= $this->options['min']; + $this->success = $this->getStringLength($value) >= $this->options['min']; } return $this->success; diff --git a/src/Rule/NotEqual.php b/src/Rule/NotEqual.php new file mode 100644 index 0000000..436898e --- /dev/null +++ b/src/Rule/NotEqual.php @@ -0,0 +1,18 @@ +success = !$this->success; + + return $this->success; + } +} diff --git a/src/Rule/NotInList.php b/src/Rule/NotInList.php index b1bf8d9..165b5c1 100755 --- a/src/Rule/NotInList.php +++ b/src/Rule/NotInList.php @@ -1,19 +1,20 @@ self::OPTION_LIST - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (!isset($this->options['list'])) { diff --git a/src/Rule/NotMatch.php b/src/Rule/NotMatch.php new file mode 100644 index 0000000..207ef71 --- /dev/null +++ b/src/Rule/NotMatch.php @@ -0,0 +1,18 @@ +success = !$this->success; + + return $this->success; + } +} diff --git a/src/Rule/NotRegex.php b/src/Rule/NotRegex.php index 25c2a42..6197e65 100755 --- a/src/Rule/NotRegex.php +++ b/src/Rule/NotRegex.php @@ -1,13 +1,14 @@ success = !$this->success; diff --git a/src/Rule/Number.php b/src/Rule/Number.php index e6029b0..04b7dcf 100644 --- a/src/Rule/Number.php +++ b/src/Rule/Number.php @@ -1,13 +1,14 @@ value = $value; $this->success = (bool)filter_var($value, FILTER_VALIDATE_FLOAT) || (string)$value === '0'; diff --git a/src/Rule/Regex.php b/src/Rule/Regex.php index 0333b35..eb4d9c9 100755 --- a/src/Rule/Regex.php +++ b/src/Rule/Regex.php @@ -1,19 +1,20 @@ self::OPTION_PATTERN - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (isset($this->options['pattern'])) { diff --git a/src/Rule/Required.php b/src/Rule/Required.php index f8c1679..e84f07e 100755 --- a/src/Rule/Required.php +++ b/src/Rule/Required.php @@ -1,13 +1,14 @@ value = $value; $this->success = ($value !== null && $value !== ''); diff --git a/src/Rule/RequiredWhen.php b/src/Rule/RequiredWhen.php index b806ec7..58a8c93 100644 --- a/src/Rule/RequiredWhen.php +++ b/src/Rule/RequiredWhen.php @@ -1,4 +1,6 @@ options[self::OPTION_RULE_OPTIONS])) ? (array)$this->options[self::OPTION_RULE_OPTIONS] : array(); + $ruleOptions = (isset($this->options[self::OPTION_RULE_OPTIONS])) ? + (array)$this->options[self::OPTION_RULE_OPTIONS] : + []; if (is_string($this->options[self::OPTION_RULE])) { $ruleClass = $this->options[self::OPTION_RULE]; @@ -34,26 +37,25 @@ public function getItemRule() 'Validator for the other item is not valid or cannot be constructed based on the data provided' ); } - $context = $this->context ? $this->context : array(); - $rule->setContext($context); + /* @var $rule AbstractRule */ + $rule->setContext($this->context ?? []); // @phpstan-ignore-line - return $rule; + return $rule; // @phpstan-ignore-line } - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; if (!isset($this->options[self::OPTION_ITEM])) { $this->success = true; } else { - - $relatedItemPath = $this->getRelatedValueIdentifier($valueIdentifier, $this->options[self::OPTION_ITEM]); - $relatedItemValue = $relatedItemPath !== null ? $this->context->getItemValue($relatedItemPath) : null; + $relatedItemPath = $this->getRelatedValueIdentifier((string)$valueIdentifier, $this->options[self::OPTION_ITEM]); + $relatedItemValue = $this->context && $relatedItemPath !== null ? $this->context->getItemValue($relatedItemPath) : null; $itemRule = $this->getItemRule(); - if ($itemRule->validate($relatedItemValue, $relatedItemPath)) { - $this->success = ($value !== null || trim($value) !== ''); + if ($itemRule && $itemRule->validate($relatedItemValue, $relatedItemPath)) { + $this->success = ($value !== null && (!is_string($value) || trim($value) !== '')); } else { $this->success = true; } diff --git a/src/Rule/RequiredWith.php b/src/Rule/RequiredWith.php index 18d82d1..bb5cd27 100644 --- a/src/Rule/RequiredWith.php +++ b/src/Rule/RequiredWith.php @@ -1,4 +1,6 @@ self::OPTION_ITEM - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; - $relatedItemPath = $this->getRelatedValueIdentifier($valueIdentifier, $this->options[self::OPTION_ITEM]); - $relatedItemValue = $relatedItemPath !== null ? $this->context->getItemValue($relatedItemPath) : null; + $relatedItemPath = $this->getRelatedValueIdentifier((string) $valueIdentifier, $this->options[self::OPTION_ITEM]); + $relatedItemValue = $this->context && $relatedItemPath !== null ? $this->context->getItemValue($relatedItemPath) : null; if (isset($this->options[self::OPTION_ITEM]) && $relatedItemValue !== null) { - $this->success = ($value !== null || trim($value) !== ''); + $this->success = ($value !== null && (!is_string($value) || trim($value) !== '')); } else { $this->success = true; } diff --git a/src/Rule/RequiredWithout.php b/src/Rule/RequiredWithout.php index 2832e72..384e0b6 100644 --- a/src/Rule/RequiredWithout.php +++ b/src/Rule/RequiredWithout.php @@ -1,4 +1,6 @@ self::OPTION_ITEM - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; - $relatedItemPath = $this->getRelatedValueIdentifier($valueIdentifier, $this->options[self::OPTION_ITEM]); - $relatedItemValue = $relatedItemPath !== null ? $this->context->getItemValue($relatedItemPath) : null; + $relatedItemPath = $this->getRelatedValueIdentifier((string) $valueIdentifier, $this->options[self::OPTION_ITEM]); + $relatedItemValue = $this->context && $relatedItemPath !== null ? $this->context->getItemValue($relatedItemPath) : null; if (isset($this->options[self::OPTION_ITEM]) && $relatedItemValue === null) { - $this->success = ($value !== null || trim($value) !== ''); + $this->success = ($value !== null && (!is_string($value) || trim($value) !== '')); } else { $this->success = true; } diff --git a/src/Rule/Time.php b/src/Rule/Time.php index de8ad09..0439f3c 100644 --- a/src/Rule/Time.php +++ b/src/Rule/Time.php @@ -1,15 +1,14 @@ 'H:i:s' - ); - + ]; } diff --git a/src/Rule/Upload/Extension.php b/src/Rule/Upload/Extension.php index 5809c1d..34b99ce 100644 --- a/src/Rule/Upload/Extension.php +++ b/src/Rule/Upload/Extension.php @@ -1,23 +1,24 @@ array() - ); + protected array $options = [ + self::OPTION_ALLOWED_EXTENSIONS => [] + ]; - public function setOption($name, $value) + public function setOption(string $name, mixed $value): static { if ($name == self::OPTION_ALLOWED_EXTENSIONS) { if (is_string($value)) { @@ -30,11 +31,13 @@ public function setOption($name, $value) return parent::setOption($name, $value); } - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; - if (!is_array($value) || !isset($value['tmp_name']) || !file_exists($value['tmp_name'])) { + if (!is_array($value) || !isset($value['tmp_name'])) { $this->success = false; + } elseif (!file_exists($value['tmp_name'])) { + $this->success = $value['error'] === UPLOAD_ERR_NO_FILE; } else { $extension = strtolower(substr($value['name'], strrpos($value['name'], '.') + 1, 10)); $this->success = is_array($this->options[self::OPTION_ALLOWED_EXTENSIONS]) && in_array( @@ -46,15 +49,13 @@ public function validate($value, $valueIdentifier = null) return $this->success; } - public function getPotentialMessage() + public function getPotentialMessage(): ErrorMessage { $message = parent::getPotentialMessage(); $fileExtensions = array_map('strtoupper', $this->options[self::OPTION_ALLOWED_EXTENSIONS]); - $message->setVariables( - array( - 'file_extensions' => implode(', ', $fileExtensions) - ) - ); + $message->setVariables([ + 'file_extensions' => implode(', ', $fileExtensions) + ]); return $message; } diff --git a/src/Rule/Upload/Image.php b/src/Rule/Upload/Image.php index f68947b..99ae24c 100644 --- a/src/Rule/Upload/Image.php +++ b/src/Rule/Upload/Image.php @@ -1,33 +1,38 @@ array('jpg', 'png', 'gif') - ); + protected array $options = [ + self::OPTION_ALLOWED_IMAGES => ['jpg', 'png', 'gif'] + ]; - protected $imageTypesMap = array( - IMAGETYPE_GIF => 'gif', - IMAGETYPE_JPEG => 'jpg', + /** + * @var array + */ + protected $imageTypesMap = [ + IMAGETYPE_GIF => 'gif', + IMAGETYPE_JPEG => 'jpg', IMAGETYPE_JPEG2000 => 'jpg', - IMAGETYPE_PNG => 'png', - IMAGETYPE_PSD => 'psd', - IMAGETYPE_BMP => 'bmp', - IMAGETYPE_ICO => 'ico', - ); + IMAGETYPE_PNG => 'png', + IMAGETYPE_PSD => 'psd', + IMAGETYPE_BMP => 'bmp', + IMAGETYPE_ICO => 'ico', + IMAGETYPE_WEBP => 'webp', + ]; - public function setOption($name, $value) + public function setOption(string $name, mixed $value): static { if ($name == self::OPTION_ALLOWED_IMAGES) { if (is_string($value)) { @@ -40,29 +45,33 @@ public function setOption($name, $value) return parent::setOption($name, $value); } - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; - if (!is_array($value) || !isset($value['tmp_name']) || !file_exists($value['tmp_name'])) { + if (!is_array($value) || !isset($value['tmp_name'])) { $this->success = false; + } elseif (!file_exists($value['tmp_name'])) { + $this->success = $value['error'] === UPLOAD_ERR_NO_FILE; } else { $imageInfo = getimagesize($value['tmp_name']); - $extension = isset($this->imageTypesMap[$imageInfo[2]]) ? $this->imageTypesMap[$imageInfo[2]] : false; - $this->success = ($extension && in_array($extension, $this->options[self::OPTION_ALLOWED_IMAGES])); + if (!is_array($imageInfo)) { + $this->success = false; + } else { + $extension = $this->imageTypesMap[$imageInfo[2]] ?? false; + $this->success = ($extension && in_array($extension, $this->options[self::OPTION_ALLOWED_IMAGES])); + } } return $this->success; } - public function getPotentialMessage() + public function getPotentialMessage(): ErrorMessage { $message = parent::getPotentialMessage(); $imageTypes = array_map('strtoupper', $this->options[self::OPTION_ALLOWED_IMAGES]); - $message->setVariables( - array( - 'image_types' => implode(', ', $imageTypes) - ) - ); + $message->setVariables([ + 'image_types' => implode(', ', $imageTypes) + ]); return $message; } diff --git a/src/Rule/Upload/ImageHeight.php b/src/Rule/Upload/ImageHeight.php index e933ecd..ba28835 100644 --- a/src/Rule/Upload/ImageHeight.php +++ b/src/Rule/Upload/ImageHeight.php @@ -1,4 +1,6 @@ 1000000, self::OPTION_MIN => 0, - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; - if (!is_array($value) || !isset($value['tmp_name']) || !file_exists($value['tmp_name'])) { + if (!is_array($value) || !isset($value['tmp_name'])) { $this->success = false; + } elseif (!file_exists($value['tmp_name'])) { + $this->success = $value['error'] === UPLOAD_ERR_NO_FILE; } else { $imageInfo = getimagesize($value['tmp_name']); $height = isset($imageInfo[1]) ? $imageInfo[1] : 0; - $this->success = $height && $height <= $this->options[self::OPTION_MAX] && $height >= $this->options[self::OPTION_MIN]; + $this->success = $height && + $height <= $this->options[self::OPTION_MAX] && + $height >= $this->options[self::OPTION_MIN]; } return $this->success; diff --git a/src/Rule/Upload/ImageRatio.php b/src/Rule/Upload/ImageRatio.php index 2fd1b0b..865c260 100644 --- a/src/Rule/Upload/ImageRatio.php +++ b/src/Rule/Upload/ImageRatio.php @@ -1,7 +1,10 @@ 0, + protected array $options = [ + self::OPTION_RATIO => 0, self::OPTION_ERROR_MARGIN => 0, - ); + ]; - protected function normalizeRatio($ratio) - { - if (is_numeric($ratio) || $ratio == filter_var($ratio, FILTER_SANITIZE_NUMBER_FLOAT)) { - return floatval($ratio); - } - if (strpos($ratio, ':') !== false) { - list($width, $height) = explode(':', $ratio); - - return $width / $height; - } - - return 0; - } - - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; - $ratio = $this->normalizeRatio($this->options[self::OPTION_RATIO]); - if (!is_array($value) || !isset($value['tmp_name']) || !file_exists($value['tmp_name'])) { + $ratio = RuleHelper::normalizeImageRatio($this->options[self::OPTION_RATIO]); + if (!is_array($value) || !isset($value['tmp_name'])) { $this->success = false; + } elseif (!file_exists($value['tmp_name'])) { + $this->success = $value['error'] === UPLOAD_ERR_NO_FILE; } elseif ($ratio == 0) { $this->success = true; } else { $imageInfo = getimagesize($value['tmp_name']); - $actualRatio = $imageInfo[0] / $imageInfo[1]; - $this->success = abs($actualRatio - $ratio) <= $this->options[self::OPTION_ERROR_MARGIN]; + + if (is_array($imageInfo)) { + $actualRatio = $imageInfo[0] / $imageInfo[1]; + $this->success = abs($actualRatio - $ratio) <= $this->options[self::OPTION_ERROR_MARGIN]; + } else { + // no image size computed => no valid image + return $this->success = false; + } } return $this->success; diff --git a/src/Rule/Upload/ImageWidth.php b/src/Rule/Upload/ImageWidth.php index 03439b0..753d495 100644 --- a/src/Rule/Upload/ImageWidth.php +++ b/src/Rule/Upload/ImageWidth.php @@ -1,4 +1,6 @@ 1000000, self::OPTION_MIN => 0, - ); + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; - if (!is_array($value) || !isset($value['tmp_name']) || !file_exists($value['tmp_name'])) { + if (!is_array($value) || !isset($value['tmp_name'])) { $this->success = false; + } elseif (!file_exists($value['tmp_name'])) { + $this->success = $value['error'] === UPLOAD_ERR_NO_FILE; } else { $imageInfo = getimagesize($value['tmp_name']); $width = isset($imageInfo[0]) ? $imageInfo[0] : 0; - $this->success = $width && $width <= $this->options[self::OPTION_MAX] && $width >= $this->options[self::OPTION_MIN]; + $this->success = $width && + $width <= $this->options[self::OPTION_MAX] && + $width >= $this->options[self::OPTION_MIN]; } return $this->success; diff --git a/src/Rule/Upload/Required.php b/src/Rule/Upload/Required.php new file mode 100644 index 0000000..253b820 --- /dev/null +++ b/src/Rule/Upload/Required.php @@ -0,0 +1,32 @@ +value = $value; + if (!is_array($value) || !isset($value['tmp_name']) || + !file_exists($value['tmp_name']) || $value['error'] !== UPLOAD_ERR_OK) { + $this->success = false; + } else { + $this->success = true; + } + + return $this->success; + } +} diff --git a/src/Rule/Upload/Size.php b/src/Rule/Upload/Size.php index 9b49182..b9fc0a6 100644 --- a/src/Rule/Upload/Size.php +++ b/src/Rule/Upload/Size.php @@ -1,7 +1,10 @@ '2M' - ); - - protected function normalizeSize($size) - { - $units = array('B' => 0, 'K' => 1, 'M' => 2, 'G' => 3); - $unit = strtoupper(substr($size, strlen($size) - 1, 1)); - if (!isset($units[$unit])) { - $normalizedSize = filter_var($size, FILTER_SANITIZE_NUMBER_INT); - } else { - $size = filter_var(substr($size, 0, strlen($size) - 1), FILTER_SANITIZE_NUMBER_FLOAT); - $normalizedSize = $size * pow(1024, $units[$unit]); - } - - return $normalizedSize; - } + ]; - public function validate($value, $valueIdentifier = null) + public function validate(mixed $value, string $valueIdentifier = null): bool { $this->value = $value; - if (!is_array($value) || !isset($value['tmp_name']) || !file_exists($value['tmp_name'])) { + if (!is_array($value) || !isset($value['tmp_name'])) { $this->success = false; + } elseif (!file_exists($value['tmp_name'])) { + $this->success = $value['error'] === UPLOAD_ERR_NO_FILE; } else { $fileSize = @filesize($value['tmp_name']); - $limit = $this->normalizeSize($this->options[self::OPTION_SIZE]); + $limit = RuleHelper::normalizeFileSize($this->options[self::OPTION_SIZE]); $this->success = $fileSize && $fileSize <= $limit; } diff --git a/src/Rule/Url.php b/src/Rule/Url.php index 6179de7..ce9bd8f 100755 --- a/src/Rule/Url.php +++ b/src/Rule/Url.php @@ -1,16 +1,17 @@ value = $value; - $this->success = (bool)filter_var($value, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED); + $this->success = (bool)filter_var($value, FILTER_VALIDATE_URL); return $this->success; } diff --git a/src/Rule/Website.php b/src/Rule/Website.php index e15210d..591ac86 100755 --- a/src/Rule/Website.php +++ b/src/Rule/Website.php @@ -1,22 +1,22 @@ value = $value; $this->success = (substr($value, 0, 2) == '//') || (preg_match(static::WEBSITE_REGEX, $value) && filter_var( $value, - FILTER_VALIDATE_URL, - FILTER_FLAG_HOST_REQUIRED + FILTER_VALIDATE_URL )); return $this->success; diff --git a/src/RuleCollection.php b/src/RuleCollection.php index 42dfd53..a7109ec 100644 --- a/src/RuleCollection.php +++ b/src/RuleCollection.php @@ -1,18 +1,23 @@ contains($rule)) { return; } if ($rule instanceof Rule\Required) { - $rules = array(); + $rules = []; foreach ($this as $r) { $rules[] = $r; $this->detach($r); @@ -25,13 +30,13 @@ public function attach($rule, $data = null) return; } - return parent::attach($rule); + parent::attach($rule); } - public function getHash($rule) + #[ReturnTypeWillChange] + public function getHash($rule): string { - /* @var $rule Rule\AbstractValidator */ + /** @var AbstractRule $rule */ return $rule->getUniqueId(); } - } diff --git a/src/RuleFactory.php b/src/RuleFactory.php index 262a2c6..2cb5360 100644 --- a/src/RuleFactory.php +++ b/src/RuleFactory.php @@ -1,8 +1,11 @@ */ - protected $validatorsMap = array(); + protected array $validatorsMap = []; - protected $errorMessages = array(); + /** + * @var array + */ + protected array $errorMessages = []; - protected $labeledErrorMessages = array(); + /** + * @var array + */ + protected array $labeledErrorMessages = []; - function __construct() + /** + * Constructor + */ + public function __construct() { $this->registerDefaultRules(); } - protected function registerDefaultRules() + /** + * Set up the default rules that come with the library + */ + protected function registerDefaultRules(): void { - $rulesClasses = array( + $rulesClasses = [ 'Alpha', 'AlphaNumeric', 'AlphaNumHyphen', @@ -47,10 +62,11 @@ protected function registerDefaultRules() 'IpAddress', 'Length', 'LessThan', - 'Match', 'MaxLength', 'MinLength', + 'NotEqual', 'NotInList', + 'NotMatch', 'NotRegex', 'Number', 'Regex', @@ -67,13 +83,14 @@ protected function registerDefaultRules() 'File\ImageRatio', 'File\ImageWidth', 'File\Size', + 'Upload\Required', 'Upload\Extension', 'Upload\Image', 'Upload\ImageHeight', 'Upload\ImageRatio', 'Upload\ImageWidth', 'Upload\Size', - ); + ]; foreach ($rulesClasses as $class) { $fullClassName = '\\' . __NAMESPACE__ . '\Rule\\' . $class; $name = strtolower(str_replace('\\', '', $class)); @@ -81,6 +98,7 @@ protected function registerDefaultRules() $labeledErrorMessage = constant($fullClassName . '::LABELED_MESSAGE'); $this->register($name, $fullClassName, $errorMessage, $labeledErrorMessage); } + $this->register('match', Matching::class, Matching::MESSAGE, Matching::LABELED_MESSAGE); } @@ -88,10 +106,11 @@ protected function registerDefaultRules() * Register a class to be used when creating validation rules * * @param string $name - * @param string $class - * @return \Sirius\Validation\RuleFactory + * @param string|class-string $class + * + * @return $this */ - public function register($name, $class, $errorMessage = '', $labeledErrorMessage = '') + public function register(string $name, string $class, string $errorMessage = '', string $labeledErrorMessage = ''): self { if (is_subclass_of($class, '\Sirius\Validation\Rule\AbstractRule')) { $this->validatorsMap[$name] = $class; @@ -109,18 +128,15 @@ public function register($name, $class, $errorMessage = '', $labeledErrorMessage /** * Factory method to construct a validator based on options that are used most of the times * - * @param string|callable $name + * @param string|callable|mixed $name * name of a validator class or a callable object/function - * @param string|array $options + * @param string|array|null $options * validator options (an array, JSON string or QUERY string) - * @param string $messageTemplate - * error message template - * @param string $label - * label of the form input field or model attribute + * + * @return AbstractRule * @throws \InvalidArgumentException - * @return \Sirius\Validation\Rule\AbstractValidator */ - public function createRule($name, $options = null, $messageTemplate = null, $label = null) + public function createRule(mixed $name, mixed $options = null, string $messageTemplate = null, string $label = null): AbstractRule { $validator = $this->construcRuleByNameAndOptions($name, $options); @@ -139,47 +155,59 @@ public function createRule($name, $options = null, $messageTemplate = null, $lab return $validator; } + /** + * Set default error message for a rule + * + * @return $this + */ + public function setMessages(string $rule, string $messageWithoutLabel = null, string $messageWithLabel = null) + { + if ($messageWithoutLabel) { + $this->errorMessages[$rule] = $messageWithoutLabel; + } + if ($messageWithLabel) { + $this->labeledErrorMessages[$rule] = $messageWithLabel; + } + + return $this; + } + /** * Get the error message saved in the registry for a rule, where the message * is with or without a the label - * - * @param string $name name of the rule - * @param bool $withLabel - * @return string|NULL */ - protected function getSuggestedMessageTemplate($name, $withLabel) + protected function getSuggestedMessageTemplate(string $name, bool $withLabel): ?string { $noLabelMessage = is_string($name) && isset($this->errorMessages[$name]) ? $this->errorMessages[$name] : null; if ($withLabel) { - return is_string($name) && isset($this->labeledErrorMessages[$name]) ? $this->labeledErrorMessages[$name] : $noLabelMessage; + return is_string($name) && isset($this->labeledErrorMessages[$name]) ? + $this->labeledErrorMessages[$name] : + $noLabelMessage; } return $noLabelMessage; } /** - * @param $name - * @param $options + * @param string|array|null $options * - * @return CallbackRule + * @return CallbackRule|AbstractRule */ - protected function construcRuleByNameAndOptions($name, $options) + protected function construcRuleByNameAndOptions(string $name, mixed $options = null) { if (is_callable($name)) { - $validator = new CallbackRule( - array( - 'callback' => $name, - 'arguments' => $options - ) - ); - } else { + $validator = new CallbackRule([ + 'callback' => $name, + 'arguments' => $options + ]); + } elseif (is_string($name)) { $name = trim($name); // use the validator map if (isset($this->validatorsMap[strtolower($name)])) { $name = $this->validatorsMap[strtolower($name)]; } // try if the validator is the name of a class in the package - if (class_exists('\Sirius\Validation\Rule\\' . $name)) { + if (class_exists('\Sirius\Validation\Rule\\' . $name, false)) { $name = '\Sirius\Validation\Rule\\' . $name; } // at this point we should have a class that can be instanciated @@ -196,5 +224,4 @@ protected function construcRuleByNameAndOptions($name, $options) return $validator; } - } diff --git a/src/Util/Arr.php b/src/Util/Arr.php index f11d09c..62876e5 100644 --- a/src/Util/Arr.php +++ b/src/Util/Arr.php @@ -1,4 +1,5 @@ */ - protected static function getSelectorParts($selector) + protected static function getSelectorParts(string $selector): array { $firstOpen = strpos($selector, '['); if ($firstOpen === false) { - return array($selector, ''); + return [$selector, '']; } $firstClose = strpos($selector, ']'); $container = substr($selector, 0, $firstOpen); @@ -27,7 +27,7 @@ protected static function getSelectorParts($selector) $firstClose + 1 ); - return array($container, $subselector); + return [$container, $subselector]; } /** @@ -37,51 +37,45 @@ protected static function getSelectorParts($selector) * key[subkey] * key[0][subkey] * - * @param array $array - * @param string $path + * @param array $array + * * @return mixed */ - public static function getByPath($array, $path = self::PATH_ROOT) + public static function getByPath(array $array, string $path = self::PATH_ROOT) { $path = trim($path); - if (!$path || $path == self::PATH_ROOT) { + if (!$path || $path === self::PATH_ROOT) { return $array; } // fix the path in case it was provided as `[item][subitem]` - if (strpos($path, '[') === 0) { + if (str_starts_with($path, '[')) { $path = preg_replace('/]/', '', ltrim($path, '['), 1); } - list($container, $subpath) = self::getSelectorParts($path); + list($container, $subpath) = self::getSelectorParts($path ?? ''); + $subarray = $array[$container] ?? null; if ($subpath === '') { - return array_key_exists($container, $array) ? $array[$container] : null; + return $subarray; } - return array_key_exists($container, $array) ? self::getByPath($array[$container], $subpath) : null; + return is_array($subarray) ? self::getByPath($subarray, $subpath) : null; } /** * Set values in the array by selector * + * @param array $array + * @param bool $overwrite true if the $value should overwrite the existing value + * + * @return array * @example * Arr::setBySelector($data, 'email', 'my@domain.com'); * Arr::setBySelector($data, 'addresses[0][line]', null); * Arr::setBySelector($data, 'addresses[*][line]', null); * - * @param array $array - * @param string $selector - * @param mixed $value - * @param bool $overwrite true if the $value should overwrite the existing value - * @return array */ - public static function setBySelector($array, $selector, $value, $overwrite = false) + public static function setBySelector(array $array, string $selector, mixed $value, bool $overwrite = false): array { - // make sure the array is an array in case we got here through a subsequent call - // so arraySetElementBySelector(array(), 'item[subitem]', 'value'); - // will call arraySetElementBySelector(null, 'subitem', 'value'); - if (!is_array($array)) { - $array = array(); - } list($container, $subselector) = self::getSelectorParts($selector); if (!$subselector) { if ($container !== '*') { @@ -95,7 +89,7 @@ public static function setBySelector($array, $selector, $value, $overwrite = fal // if we have a subselector the $array[$container] must be an array if ($container !== '*' && !array_key_exists($container, $array)) { - $array[$container] = array(); + $array[$container] = []; } // we got here through something like *[subitem] if ($container === '*') { @@ -112,28 +106,25 @@ public static function setBySelector($array, $selector, $value, $overwrite = fal /** * Get values in the array by selector * + * @param array $array + * @return array * @example * Arr::getBySelector($data, 'email'); * Arr::getBySelector($data, 'addresses[0][line]'); * Arr::getBySelector($data, 'addresses[*][line]'); * - * @param $array - * @param $selector - * @return array */ - public static function getBySelector($array, $selector) + public static function getBySelector(array $array, string $selector): array { - if (strpos($selector, '[*]') === false) { - return array( - $selector => self::getByPath($array, $selector) - ); + if (!str_contains($selector, '[*]')) { + return [$selector => self::getByPath($array, $selector)]; } - $result = array(); + $result = []; list($preffix, $suffix) = explode('[*]', $selector, 2); $base = self::getByPath($array, $preffix); if (!is_array($base)) { - $base = array(); + $base = []; } // we don't have a suffix, the selector was something like path[subpath][*] if (!$suffix) { diff --git a/src/Util/RuleHelper.php b/src/Util/RuleHelper.php new file mode 100644 index 0000000..9a0bca5 --- /dev/null +++ b/src/Util/RuleHelper.php @@ -0,0 +1,160 @@ + $optionsIndexMap + * + * @return array + * @throws \InvalidArgumentException + */ + public static function normalizeOptions(mixed $options, array $optionsIndexMap = []): array + { + if ('0' === $options && count($optionsIndexMap) > 0) { + $options = [$optionsIndexMap[0] => '0']; + } + if (!$options) { + return []; + } + + if (is_array($options) && static::arrayIsAssoc($options)) { + return $options; + } + + $result = $options; + if ($options && is_string($options)) { + $startChar = substr($options, 0, 1); + if ($startChar == '{') { + $result = json_decode($options, true); + } elseif (strpos($options, '=') !== false) { + $result = static::parseHttpQueryString($options); + } else { + $result = static::parseCsvString($options, $optionsIndexMap); + } + } + + if (!is_array($result)) { + throw new \InvalidArgumentException('Validator options should be an array, JSON string or query string'); + } + + return $result; + } + + /** + * Converts a HTTP query string to an array + * + * @return array|bool + */ + public static function parseHttpQueryString(string $str): array|bool + { + parse_str($str, $arr); + + return static::convertBooleanStrings($arr); + } + + /** + * Converts 'true' and 'false' strings to TRUE and FALSE + * + * @param $arr + * + * @return bool|array|mixed + */ + public static function convertBooleanStrings(mixed $arr): mixed + { + if (is_array($arr)) { + return array_map([__CLASS__, 'convertBooleanStrings'], $arr); + } + if ($arr === 'true') { + return true; + } + if ($arr === 'false') { + return false; + } + + return $arr; + } + + + /** + * Parses a CSV string and converts the result into an "options" array + * (an associative array that contains the options for the validation rule) + * + * @param array $optionsIndexMap + * + * @return array|bool + */ + public static function parseCsvString(string $str, array $optionsIndexMap = []): array|bool + { + if (empty($optionsIndexMap)) { + throw new \InvalidArgumentException( + '`$optionsIndexMap` argument must be provided for CSV-type parameters' + ); + } + + $options = explode(',', $str); + $result = []; + foreach ($options as $k => $v) { + if (!isset($optionsIndexMap[$k])) { + throw new \InvalidArgumentException(sprintf( + '`$optionsIndexMap` for the validator is missing the %s index', + $k + )); + } + $result[$optionsIndexMap[$k]] = $v; + } + + return static::convertBooleanStrings($result); + } + + /** + * Checks if an array is associative (ie: the keys are not numbers in sequence) + * + * @param array $arr + * + * @return bool + */ + public static function arrayIsAssoc(array $arr): bool + { + return array_keys($arr) !== range(0, count($arr)); + } + + public static function normalizeFileSize(string|int|float $size): int + { + $size = (string)$size; + $units = ['B' => 0, 'K' => 1, 'M' => 2, 'G' => 3]; + $unit = strtoupper(substr($size, strlen($size) - 1, 1)); + if (!isset($units[$unit])) { + $normalizedSize = filter_var($size, FILTER_SANITIZE_NUMBER_INT); + } else { + $size = (float) filter_var(substr($size, 0, strlen($size) - 1), FILTER_SANITIZE_NUMBER_FLOAT); + $normalizedSize = $size * pow(1024, $units[$unit]); + } + + return (int) $normalizedSize; + } + + + public static function normalizeImageRatio(mixed $ratio): float + { + if (is_numeric($ratio) || $ratio == filter_var($ratio, FILTER_SANITIZE_NUMBER_FLOAT)) { + return floatval($ratio); + } + if (strpos($ratio, ':') !== false) { + list($width, $height) = explode(':', $ratio); + + return (float) $width / (float) $height; + } + + return 0; + } +} diff --git a/src/Validator.php b/src/Validator.php index edba6c3..6441a01 100755 --- a/src/Validator.php +++ b/src/Validator.php @@ -1,11 +1,12 @@ */ - protected $rules = array(); + protected array $rules = []; /** - * @var array + * @var array */ - protected $messages = array(); + protected array $messages = []; - /** - * @var \Sirius\Validation\RuleFactory - */ - protected $ruleFactory; + protected ?RuleFactory $ruleFactory = null; - /** - * @var ErrorMessage - */ - protected $errorMessagePrototype; + protected ?ErrorMessage $errorMessagePrototype = null; /** * The object that will contain the data - * - * @var \Sirius\Validation\DataWrapper\WrapperInterface */ - protected $dataWrapper; + protected ?WrapperInterface $dataWrapper = null; public function __construct(RuleFactory $ruleFactory = null, ErrorMessage $errorMessagePrototype = null) { @@ -139,66 +137,46 @@ public function __construct(RuleFactory $ruleFactory = null, ErrorMessage $error /** * Retrieve the rule factory - * - * @return \Sirius\Validation\RuleFactory */ - public function getRuleFactory() + public function getRuleFactory(): ?RuleFactory { return $this->ruleFactory; } - /** - * @param ErrorMessage $errorMessagePrototype - * - * @throws \InvalidArgumentException - * - * @return \Sirius\Validation\Rule\AbstractValidator - */ - public function setErrorMessagePrototype(ErrorMessage $errorMessagePrototype) + public function setErrorMessagePrototype(ErrorMessage $errorMessagePrototype): static { $this->errorMessagePrototype = $errorMessagePrototype; return $this; } - /** - * Retrieve the error message prototype - * - * @return ErrorMessage - */ - public function getErroMessagePrototype() + public function getErrorMessagePrototype(): ?ErrorMessage { return $this->errorMessagePrototype; } /** - * @example // add multiple rules at once - * $validator->add(array( - * 'field_a' => 'required', - * 'field_b' => array('required', array('email', null, '{label} must be an email', 'Field B')), - * )); - * // add multiple rules using arrays - * $validator->add('field', array('required', 'email')); - * // add multiple rules using a string - * $validator->add('field', 'required | email'); - * // add validator with options - * $validator->add('field:Label', 'minlength', array('min' => 2), '{label} should have at least {min} characters'); - * // add validator with string and parameters as JSON string - * $validator->add('field:Label', 'minlength({"min": 2})({label} should have at least {min} characters)'); - * // add validator with string and parameters as query string - * $validator->add('field:label', 'minlength(min=2)({label} should have at least {min} characters)'); + * @example + * // add multiple rules at once + * $validator->add(array( + * 'field_a' => 'required', + * 'field_b' => ['required', ['email', null, '{label} must be an email', 'Field B']], + * )); + * + * // add multiple rules using arrays + * $validator->add('field', ['required', 'email']); * - * @param string $selector - * @param string|callback $name - * @param string|array $options - * @param string $messageTemplate - * @param string $label + * // add multiple rules using a string + * $validator->add('field', 'required | email'); * - * @throws \InvalidArgumentException + * // add validator with options + * $validator->add('field:Label', 'minlength', ['min' => 2], '{label} should have at least {min} characters'); + * + * // add validator with string and parameters as query string + * $validator->add('field:label', 'minlength(min=2)({label} should have at least {min} characters)'); * - * @return Validator */ - public function add($selector, $name = null, $options = null, $messageTemplate = null, $label = null) + public function add($selector, $name = null, $options = null, $messageTemplate = null, $label = null): static { // the $selector is an associative array with $selector => $rules if (func_num_args() == 1) { @@ -209,29 +187,28 @@ public function add($selector, $name = null, $options = null, $messageTemplate = return $this->addMultiple($selector); } + $selector = (string)$selector; // @phpstan-ignore-line // check if the selector is in the form of 'selector:Label' - if (strpos($selector, ':') !== false) { + if (str_contains($selector, ':')) { list($selector, $label) = explode(':', $selector, 2); } - $this->ensureSelectorRulesExist($selector); - call_user_func(array($this->rules[$selector], 'add'), $name, $options, $messageTemplate, $label); + $this->ensureSelectorRulesExist($selector, $label ?? ''); + call_user_func([$this->rules[$selector], 'add'], $name, $options, $messageTemplate, $label); // @phpstan-ignore-line return $this; } /** - * @param array $selectorRulesCollection - * - * @return Validator + * @param array $selectorRulesCollection */ - public function addMultiple($selectorRulesCollection) + public function addMultiple(array $selectorRulesCollection): static { foreach ($selectorRulesCollection as $selector => $rules) { - // a single rule was passed for the $valueSelector if (!is_array($rules)) { - return $this->add($selector, $rules); + $this->add($selector, $rules); + continue; } // multiple rules were passed for the same $valueSelector @@ -239,13 +216,7 @@ public function addMultiple($selectorRulesCollection) // the rule is an array, this means it contains $name, $options, $messageTemplate, $label if (is_array($rule)) { array_unshift($rule, $selector); - call_user_func_array( - array( - $this, - 'add' - ), - $rule - ); + call_user_func_array([$this, 'add'], $rule); // the rule is only the name of the validator } else { $this->add($selector, $rule); @@ -256,16 +227,7 @@ public function addMultiple($selectorRulesCollection) return $this; } - /** - * @param string $selector - * data selector - * @param mixed $name - * rule name or true if all rules should be deleted for that selector - * @param mixed $options - * rule options, necessary for rules that depend on params for their ID - * @return self - */ - public function remove($selector, $name = true, $options = null) + public function remove($selector, $name = true, $options = null): self { if (!array_key_exists($selector, $this->rules)) { return $this; @@ -282,9 +244,10 @@ public function remove($selector, $name = true, $options = null) * This way you can validate anything, not just arrays (which is the default) * * @param mixed $data - * @return \Sirius\Validation\DataWrapper\WrapperInterface + * + * @return WrapperInterface */ - public function getDataWrapper($data = null) + public function getDataWrapper($data = null): WrapperInterface { // if $data is set reconstruct the data wrapper if (!$this->dataWrapper || $data) { @@ -294,60 +257,52 @@ public function getDataWrapper($data = null) return $this->dataWrapper; } - public function setData($data) + /** + * @param array|\ArrayObject|object $data + */ + public function setData(mixed $data): static { $this->getDataWrapper($data); $this->wasValidated = false; // reset messages - $this->messages = array(); + $this->messages = []; return $this; } /** - * Performs the validation - * - * @param mixed $data - * array to be validated - * @return boolean + * @param array|\ArrayObject|object|null $data */ - public function validate($data = null) + public function validate(mixed $data = null): bool { if ($data !== null) { $this->setData($data); } // data was already validated, return the results immediately - if ($this->wasValidated === true) { - return $this->wasValidated && count($this->messages) === 0; - } - foreach ($this->rules as $selector => $valueValidator) { - foreach ($this->getDataWrapper()->getItemsBySelector($selector) as $valueIdentifier => $value) { - /* @var $valueValidator \Sirius\Validation\ValueValidator */ - if (!$valueValidator->validate($value, $valueIdentifier, $this->getDataWrapper())) { - foreach ($valueValidator->getMessages() as $message) { - $this->addMessage($valueIdentifier, $message); + if (!$this->wasValidated === true) { + foreach ($this->rules as $selector => $valueValidator) { + foreach ($this->getDataWrapper()->getItemsBySelector($selector) as $valueIdentifier => $value) { + /* @var $valueValidator \Sirius\Validation\ValueValidator */ + if (!$valueValidator->validate($value, $valueIdentifier, $this->getDataWrapper())) { + foreach ($valueValidator->getMessages() as $message) { + $this->addMessage($valueIdentifier, $message); + } } } + $this->wasValidated = true; } } - $this->wasValidated = true; - return $this->wasValidated && count($this->messages) === 0; + return count($this->messages) === 0; } - /** - * @param string $item - * data identifier (eg: 'email', 'addresses[0][state]') - * @param string $message - * @return self - */ - public function addMessage($item, $message = null) + public function addMessage(string $item, string|ErrorMessage $message = null): static { if ($message === null || $message === '') { return $this; } if (!array_key_exists($item, $this->messages)) { - $this->messages[$item] = array(); + $this->messages[$item] = []; } $this->messages[$item][] = $message; @@ -356,18 +311,15 @@ public function addMessage($item, $message = null) /** * Clears the messages of an item - * - * @param string $item - * @return self */ - public function clearMessages($item = null) + public function clearMessages(string $item = null): static { if (is_string($item)) { if (array_key_exists($item, $this->messages)) { unset($this->messages[$item]); } } elseif ($item === null) { - $this->messages = array(); + $this->messages = []; } return $this; @@ -376,30 +328,35 @@ public function clearMessages($item = null) /** * @param string $item * key of the messages array (eg: 'password', 'addresses[0][line_1]') - * @return array + * + * @return array */ - public function getMessages($item = null) + public + function getMessages(string $item = null): array { if (is_string($item)) { - return array_key_exists($item, $this->messages) ? $this->messages[$item] : array(); + return array_key_exists($item, $this->messages) ? $this->messages[$item] : []; } return $this->messages; } - public function getRules() + /** + * @return array + */ + public function getRules(): array { return $this->rules; } - /** - * @param $selector - */ - protected function ensureSelectorRulesExist($selector) + protected function ensureSelectorRulesExist(string $selector, string $label = ''): void { if (!isset($this->rules[$selector])) { - $this->rules[$selector] = new ValueValidator($this->getRuleFactory(), $this->getErroMessagePrototype()); + $this->rules[$selector] = new ValueValidator( + $this->getRuleFactory(), + $this->getErrorMessagePrototype(), + $label + ); } } - } diff --git a/src/ValidatorInterface.php b/src/ValidatorInterface.php index 104f070..7f6aec9 100644 --- a/src/ValidatorInterface.php +++ b/src/ValidatorInterface.php @@ -1,13 +1,35 @@ $selector + * @param string|callable $name + * @param string|array $options + * @param string $messageTemplate + * @param string $label + * + * @throws \InvalidArgumentException + */ + public function add($selector, $name = null, $options = null, $messageTemplate = null, $label = null): self; - public function add($selector, $name = null, $options = null, $messageTemplate = null, $label = null); + /** + * @param string $selector + * data selector + * @param mixed $name + * rule name or true if all rules should be deleted for that selector + * @param mixed $options + * rule options, necessary for rules that depend on params for their ID + * + * @return self + */ + public function remove($selector, $name = true, $options = null): self; - public function remove($selector, $name = true, $options = null); - - public function validate($data = array()); + /** + * @param array $data + */ + public function validate(array $data = []): bool; } diff --git a/src/ValueValidator.php b/src/ValueValidator.php index 3f0cdc7..dde50e1 100644 --- a/src/ValueValidator.php +++ b/src/ValueValidator.php @@ -1,8 +1,10 @@ */ - protected $messages = array(); + protected $messages = []; - /** - * Will be used to construct the rules - * - * @var \Sirius\Validation\RuleFactory - */ - protected $ruleFactory; + protected RuleFactory $ruleFactory; - /** - * The prototype that will be used to generate the error message - * - * @var \Sirius\Validation\ErrorMessage - */ - protected $errorMessagePrototype; + protected ErrorMessage $errorMessagePrototype; - /** - * The rule collections for the validation - * - * @var \Sirius\Validation\RuleCollection - */ - protected $rules; - - /** - * The label of the value to be validated - * - * @var string - */ - protected $label; + protected RuleCollection $rules; + protected string $label; - function __construct(RuleFactory $ruleFactory = null, ErrorMessage $errorMessagePrototype = null, $label = null) + public function __construct( + RuleFactory $ruleFactory = null, + ErrorMessage $errorMessagePrototype = null, + string $label = '' + ) { if (!$ruleFactory) { $ruleFactory = new RuleFactory(); @@ -59,7 +44,7 @@ function __construct(RuleFactory $ruleFactory = null, ErrorMessage $errorMessage $this->rules = new RuleCollection; } - public function setLabel($label = null) + public function setLabel(string $label = ''): self { $this->label = $label; @@ -69,45 +54,50 @@ public function setLabel($label = null) /** * Add 1 or more validation rules * - * @example // add multiple rules at once - * $validator->add(array( - * 'required', - * array('required', array('email', null, '{label} must be an email', 'Field B')), - * )); - * // add multiple rules using a string - * $validator->add('required | email'); - * // add validator with options - * $validator->add('minlength', array('min' => 2), '{label} should have at least {min} characters', 'Field label'); - * // add validator with string and parameters as JSON string - * $validator->add('minlength({"min": 2})({label} should have at least {min} characters)(Field label)'); - * // add validator with string and parameters as query string - * $validator->add('minlength(min=2)({label} should have at least {min} characters)(Field label)'); - * - * @param string|callback $name - * @param string|array $options - * @param string $messageTemplate - * @param string $label + * @param string|callable $name + * @param string|array|null $options + * @param string|null $messageTemplate + * @param string|null $label * * @return ValueValidator + * @example + * // add multiple rules at once + * $validator->add(array( + * 'required', + * ['required', ['email', null, '{label} must be an email', 'Field B']], + * )); + * + * // add multiple rules using a string + * $validator->add('required | email'); + * + * // add validator with options + * $validator->add('minlength', ['min' => 2], '{label} should have at least {min} characters', 'Field label'); + * + * // add validator with string and parameters as JSON string + * $validator->add('minlength({"min": 2})({label} should have at least {min} characters)(Field label)'); + * + * // add validator with string and parameters as query string + * $validator->add('minlength(min=2)({label} should have at least {min} characters)(Field label)'); + * */ - public function add($name, $options = null, $messageTemplate = null, $label = null) + public function add(mixed $name, $options = null, string $messageTemplate = null, string $label = null): self { - if (is_array($name) && !is_callable($name)) { + if (is_array($name)) { return $this->addMultiple($name); } if (is_string($name)) { // rule was supplied like 'required | email' if (strpos($name, ' | ') !== false) { - return $this->add(explode(' | ', $name)); + return $this->addMultiple(explode(' | ', $name)); } // rule was supplied like this 'length(2,10)(error message template)(label)' if (strpos($name, '(') !== false) { - list ($name, $options, $messageTemplate, $label) = $this->parseRule($name); + list($name, $options, $messageTemplate, $label) = $this->parseRule($name); } } // check for the default label - if (!$label and $this->label) { + if (!$label && isset($this->label)) { $label = $this->label; } @@ -117,35 +107,20 @@ public function add($name, $options = null, $messageTemplate = null, $label = nu } /** - * @param array $rules - * - * @return ValueValidator + * @param array $rules */ - public function addMultiple($rules) + public function addMultiple($rules): self { foreach ($rules as $singleRule) { // make sure the rule is an array (the parameters of subsequent calls); - $singleRule = is_array($singleRule) ? $singleRule : array( - $singleRule - ); - call_user_func_array( - array( - $this, - 'add' - ), - $singleRule - ); + $singleRule = is_array($singleRule) ? $singleRule : [$singleRule]; + call_user_func_array([$this, 'add'], $singleRule); } return $this; } - /** - * @param AbstractValidator $validationRule - * - * @return ValueValidator - */ - public function addRule(AbstractRule $validationRule) + public function addRule(AbstractRule $validationRule): self { $validationRule->setErrorMessagePrototype($this->errorMessagePrototype); $this->rules->attach($validationRule); @@ -158,13 +133,14 @@ public function addRule(AbstractRule $validationRule) * * @param mixed $name * rule name or true if all rules should be deleted for that selector - * @param mixed $options + * @param string|array $options * rule options, necessary for rules that depend on params for their ID + * + * @return self * @throws \InvalidArgumentException * @internal param string $selector data selector - * @return self */ - public function remove($name = true, $options = null) + public function remove($name = true, $options = null): self { if ($name === true) { $this->rules = new RuleCollection(); @@ -180,98 +156,137 @@ public function remove($name = true, $options = null) /** * Converts a rule that was supplied as string into a set of options that define the rule * + * @param string $ruleAsString + * + * @return array * @example 'minLength({"min":2})({label} must have at least {min} characters)(Street)' * * will be converted into * - * array( - * 'minLength', // validator name - * array('min' => 2'), // validator options - * '{label} must have at least {min} characters', - * 'Street' // label - * ) - * @param string $ruleAsString - * @return array + * [ + * 'minLength', // validator name + * ['min' => 2'], // validator options + * '{label} must have at least {min} characters', + * 'Street' // label + * ] + * */ - protected function parseRule($ruleAsString) + protected function parseRule($ruleAsString): array { $ruleAsString = trim($ruleAsString); - $options = array(); + $name = $ruleAsString; + $options = []; $messageTemplate = null; $label = null; - $name = substr($ruleAsString, 0, strpos($ruleAsString, '(')); - $ruleAsString = substr($ruleAsString, strpos($ruleAsString, '(')); - $matches = array(); - preg_match_all('/\(([^\)]*)\)/', $ruleAsString, $matches); - - if (isset($matches[1])) { - if (isset($matches[1][0]) && $matches[1][0]) { - $options = $matches[1][0]; - } - if (isset($matches[1][1]) && $matches[1][1]) { - $messageTemplate = $matches[1][1]; - } - if (isset($matches[1][2]) && $matches[1][2]) { - $label = $matches[1][2]; + if (str_contains($ruleAsString, '(')) { + $name = substr($ruleAsString, 0, strpos($ruleAsString, '(')); //@phpstan-ignore-line + $ruleAsString = substr($ruleAsString, strpos($ruleAsString, '(')); //@phpstan-ignore-line + $matches = []; + preg_match_all('/\(([^\)]*)\)/', $ruleAsString, $matches); + + if (isset($matches[1])) { + if (isset($matches[1][0]) && $matches[1][0] !== '') { + $options = $matches[1][0]; + } + if (isset($matches[1][1]) && $matches[1][1]) { + $messageTemplate = $matches[1][1]; + } + if (isset($matches[1][2]) && $matches[1][2]) { + $label = $matches[1][2]; + } } } - return array( + return [ $name, $options, $messageTemplate, $label - ); + ]; } - public function validate($value, $valueIdentifier = null, DataWrapper\WrapperInterface $context = null) + /** + * @param DataWrapper\WrapperInterface|null $context + */ + public function validate(mixed $value, string $valueIdentifier = '', DataWrapper\WrapperInterface $context = null): bool { - $this->messages = array(); + $this->messages = []; $isRequired = false; + + // evaluate the required rules + /** @var AbstractRule $rule */ foreach ($this->rules as $rule) { if ($rule instanceof Rule\Required) { $isRequired = true; - break; + + if (!$this->validateRule($rule, $value, $valueIdentifier, $context)) { + return false; + } } } - if (!$isRequired && $value === null) { + // avoid future rule evaluations if value is null or empty string + if ($this->isEmpty($value)) { return true; } - /* @var $rule \Sirius\Validation\Rule\AbstractValidator */ + // evaluate the non-required rules + /** @var AbstractRule $rule */ foreach ($this->rules as $rule) { - $rule->setContext($context); - if (!$rule->validate($value, $valueIdentifier)) { - $this->addMessage($rule->getMessage()); - } - // if field is required and we have an error, - // do not continue with the rest of rules - if ($isRequired && count($this->messages)) { - break; + if (!($rule instanceof Rule\Required)) { + $this->validateRule($rule, $value, $valueIdentifier, $context); + + // if field is required and we have an error, + // do not continue with the rest of rules + if ($isRequired && count($this->messages)) { //@phpstan-ignore-line + break; + } } } return count($this->messages) === 0; } - public function getMessages() + /** + * @param DataWrapper\WrapperInterface|null $context + */ + private function validateRule(AbstractRule $rule, mixed $value, string $valueIdentifier, $context): bool + { + $rule->setContext($context); + if (!$rule->validate($value, $valueIdentifier)) { + $this->addMessage($rule->getMessage()); + return false; + } + return true; + } + + /** + * @return mixed[] + */ + public function getMessages(): array { return $this->messages; } - public function addMessage($message) + public function addMessage(mixed $message): ValueValidator { array_push($this->messages, $message); return $this; } - public function getRules() + public function getRules(): RuleCollection { return $this->rules; } + /** + * @param mixed $value + */ + protected function isEmpty($value): bool + { + return in_array($value, [null, ''], true); + } } diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..5949c61 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,45 @@ +in('Feature'); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +expect()->extend('toBeOne', function () { + return $this->toBe(1); +}); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +function something() +{ + // .. +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..cfb05b6 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ + - + @@ -9,4 +18,9 @@ ./src/ + + + ./../src + + diff --git a/tests/phpunit_bootstrap.php b/tests/phpunit_bootstrap.php index ada46a3..7e0761a 100644 --- a/tests/phpunit_bootstrap.php +++ b/tests/phpunit_bootstrap.php @@ -1,5 +1,5 @@ validator = new Validator(); - $this->validator - ->add('email', 'email | required')// does the order matter? - ->add('email_confirm', 'required | email | match(item=email)') - ->add('password', 'required') - ->add('password_confirm', 'required | match(item=password)') - ->add('feedback', 'requiredwith(item=agree_to_provide_feedback)') - ->add('birthday', 'requiredwhen', array('item' => 'email_confirm', 'rule' => 'Email')) - // the lines below don't match the example but that's ok, - // the individual rules have tests - ->add('lines[*][price]', 'requiredwith(item=lines[*][quantity])'); - } - - function notestWithCorrectData() - { - $data = array( - 'email' => 'me@domain.com', - 'email_confirm' => 'me@domain.com', - 'password' => '1234', - 'password_confirm' => '1234', - 'agree_to_provide_feedback' => true, - 'feedback' => 'This is great!', - 'birthday' => '1980-01-01', - 'lines' => array( - array('quantity' => 10, 'price' => 20) - ) - ); - $this->assertTrue($this->validator->validate($data)); - } - - function testWithInvalidData() - { - $data = array( - 'email_confirm' => 'me@domain.com', - 'password' => '1234', - 'password_confirm' => '123456', - 'agree_to_provide_feedback' => true, - 'lines' => array( - array('quantity' => 10, 'price' => null) - ) - ); - $this->validator->validate($data); - $messages = $this->validator->getMessages(); - - $this->assertEquals('This field is required', (string)$messages['email'][0]); - $this->assertEquals('This input does not match password', (string)$messages['password_confirm'][0]); - $this->assertEquals('This field is required', (string)$messages['feedback'][0]); - $this->assertEquals('This field is required', (string)$messages['lines[0][price]'][0]); - } - -} +use \Sirius\Validation\Validator; + +beforeEach(function () { + $this->validator = new Validator(); + $this->validator + ->add('email', 'email | required')// does the order matter? + ->add('email_confirm', 'required | email | match(item=email)') + ->add('password', 'required | notmatch(item=email)') + ->add('password_confirm', 'required | match(item=password)') + ->add('feedback', 'requiredwith(item=agree_to_provide_feedback)') + ->add('birthday', 'requiredwhen', array( 'item' => 'email_confirm', 'rule' => 'Email' )) + // the lines below don't match the example but that's ok, + // the individual rules have tests + ->add('lines[*][price]', 'requiredwith(item=lines[*][quantity])'); +}); + +test('with invalid data', function () { + $data = array( + 'email' => 'me@domain.com', + 'password' => 'me@domain.com', + 'password_confirm' => '123456', + 'agree_to_provide_feedback' => true, + 'lines' => array( + array( 'quantity' => 10, 'price' => null ) + ) + ); + $this->validator->validate($data); + $messages = $this->validator->getMessages(); + + expect((string) $messages['email_confirm'][0])->toEqual('This field is required'); + expect((string) $messages['password'][0])->toEqual('This input does match email'); + expect((string) $messages['password_confirm'][0])->toEqual('This input does not match password'); + expect((string) $messages['feedback'][0])->toEqual('This field is required'); + expect((string) $messages['lines[0][price]'][0])->toEqual('This field is required'); +}); diff --git a/tests/src/ErrorMessageTest.php b/tests/src/ErrorMessageTest.php index f7bb31b..15bdfe8 100644 --- a/tests/src/ErrorMessageTest.php +++ b/tests/src/ErrorMessageTest.php @@ -1,36 +1,25 @@ validator = new Validator(); - $this->validator->setErrorMessagePrototype(new CustomErrorMessage()); - } +beforeEach(function () { + $this->validator = new Validator(); + $this->validator->setErrorMessagePrototype(new CustomErrorMessage()); +}); - function testErrorMessage() - { - $this->validator->add('email', 'email'); - $this->validator->validate(array('email' => 'not_an_email')); +test('error message', function () { + $this->validator->add('email', 'email'); + $this->validator->validate(array( 'email' => 'not_an_email' )); - $messages = $this->validator->getMessages('email'); - $this->assertEquals(1, count($messages)); + $messages = $this->validator->getMessages('email'); + expect(count($messages))->toEqual(1); - $this->assertEquals('!!!This input must be a valid email address', (string)$messages[0]); - } - - -} + expect((string) $messages[0])->toEqual('!!!This input must be a valid email address'); +}); diff --git a/tests/src/HelperTest.php b/tests/src/HelperTest.php index 4267590..873f5d3 100755 --- a/tests/src/HelperTest.php +++ b/tests/src/HelperTest.php @@ -1,692 +1,678 @@ assertTrue(Helper::methodExists('email')); - $this->assertFalse(Helper::methodExists('nonExistingMethod')); - } - - function testOfRequired() - { - $pool = array( - 'abc' => true, - 1.2 => true, - '' => false - ); - foreach ($pool as $key => $value) { - $this->assertSame(Helper::required($key), $value); +use \Sirius\Validation\Helper; + +test('method exists', function () { + expect(Helper::methodExists('email'))->toBeTrue(); + expect(Helper::methodExists('nonExistingMethod'))->toBeFalse(); +}); + +test('required', function () { + $pool = array( + ['abc', true], + [1.2, true], + ['', false] + ); + foreach ($pool as list($key, $value)) { + expect($value)->toBe(Helper::required($key)); + } + expect(false)->toBe(Helper::required(null)); +}); + +test('truthy', function () { + $pool = array( + ['abc', true], + [1.2, true], + [0, false], + ['', false], + ); + foreach ($pool as list($key, $value)) { + expect($value)->toBe(Helper::truthy($key)); + } +}); + +test('falsy', function () { + $pool = array( + ['abc', false], + [1.2, false], + [0, true], + ['', true], + ); + foreach ($pool as list($key, $value)) { + expect($value)->toBe(Helper::falsy($key)); + } +}); + +test('callback', function () { + expect(Helper::callback( + 3, + function ($value) { + return $value === 3; } - $this->assertSame(Helper::required(null), false); - } - - function testOfTruthy() - { - $pool = array( - 'abc' => true, - 1.2 => true, - 0 => false, - '' => false - ); - foreach ($pool as $key => $value) { - $this->assertSame(Helper::truthy($key), $value); + ))->toBeTrue(); +}); + +test('email', function () { + $pool = array( + '-fa /lse@gmail.com' => false, + '12345@hotmail.com' => true, + 'xxx.yyyy-zzz@domain.com.noc' => true, + 'weird.name-99-@yahoo.com' => true, + 'shit' => false + ); + foreach ($pool as $key => $value) { + if ($value) { + $message = $key . ' is a valid email'; + } else { + $message = $key . ' is NOT a valid email'; } - } - - function testOfFalsy() - { - $pool = array( - 'abc' => false, - 1.2 => false, - 0 => true, - '' => true - ); - foreach ($pool as $key => $value) { - $this->assertSame(Helper::falsy($key), $value); + expect($value)->toBe(Helper::email($key), $message); + } +}); + +test('number', function () { + $pool = array( + '1' => true, + '1,5' => false, + '2.5' => true, + 'abc' => false + ); + foreach ($pool as $key => $value) { + if ($value) { + $message = $key . ' is a valid number'; + } else { + $message = $key . ' is NOT a valid number'; } - } - - function testOfCallback() - { - $this->assertTrue( - Helper::callback( - 3, - function ($value) { - return $value === 3; - } - ) - ); - } - - function testOfEmail() - { - $pool = array( - '-fa /lse@gmail.com' => false, - '12345@hotmail.com' => true, - 'xxx.yyyy-zzz@domain.com.noc' => true, - 'weird.name-99-@yahoo.com' => true, - 'shit' => false - ); - foreach ($pool as $key => $value) { - if ($value) { - $message = $key . ' is a valid email'; - } else { - $message = $key . ' is NOT a valid email'; - } - $this->assertSame(Helper::email($key), $value, $message); + expect($value)->toBe(Helper::number($key), $message); + } +}); + +test('integer', function () { + $pool = array( + '1' => true, + '12345' => true, + '1.00' => true, + '1.24' => false + ); + foreach ($pool as $key => $value) { + if ($value) { + $message = $key . ' is a valid integer'; + } else { + $message = $key . ' is NOT a valid integer'; } - } - - function testOfNumber() - { - $pool = array( - '1' => true, - '1,5' => false, - '2.5' => true, - 'abc' => false - ); - foreach ($pool as $key => $value) { - if ($value) { - $message = $key . ' is a valid number'; - } else { - $message = $key . ' is NOT a valid number'; - } - $this->assertSame(Helper::number($key), $value, $message); + expect($value)->toBe(Helper::integer($key), $message); + } +}); + +test('less than', function () { + $pool = array( + array( + 1, + 0.5, + false + ), + array( + 1, + 1.2, + true + ) + ); + foreach ($pool as $sample) { + if ($sample[2]) { + $message = $sample[0] . ' is less than ' . $sample[1]; + } else { + $message = $sample[0] . ' is NOT less than ' . $sample[1]; } - } - - function testOfInteger() - { - $pool = array( - '1' => true, - '12345' => true, - '1.00' => true, - '1.24' => false - ); - foreach ($pool as $key => $value) { - if ($value) { - $message = $key . ' is a valid integer'; - } else { - $message = $key . ' is NOT a valid integer'; - } - $this->assertSame(Helper::integer($key), $value, $message); + expect($sample[2])->toBe(Helper::lessThan($sample[0], $sample[1]), $message); + } +}); + +test('greater than', function () { + $pool = array( + array( + 1, + 0.5, + true + ), + array( + 1, + 1.2, + false + ) + ); + foreach ($pool as $sample) { + if ($sample[2]) { + $message = $sample[0] . ' is less than ' . $sample[1]; + } else { + $message = $sample[0] . ' is NOT less than ' . $sample[1]; } - } - - function testOfLessThan() - { - $pool = array( - array( - 1, - 0.5, - false - ), - array( - 1, - 1.2, - true - ) - ); - foreach ($pool as $sample) { - if ($sample[2]) { - $message = $sample[0] . ' is less than ' . $sample[1]; - } else { - $message = $sample[0] . ' is NOT less than ' . $sample[1]; - } - $this->assertSame(Helper::lessThan($sample[0], $sample[1]), $sample[2], $message); - } - } - - function testOfGreaterThan() - { - $pool = array( - array( - 1, - 0.5, - true - ), - array( - 1, - 1.2, - false - ) - ); - foreach ($pool as $sample) { - if ($sample[2]) { - $message = $sample[0] . ' is less than ' . $sample[1]; - } else { - $message = $sample[0] . ' is NOT less than ' . $sample[1]; - } - $this->assertSame(Helper::greaterThan($sample[0], $sample[1]), $sample[2], $message); - } - } - - function testOfBetween() - { - $pool = array( - array( - 1, - 0.5, - 0.8, - false - ), - array( - 1, - 0.9, - 1.2, - true - ) - ); - foreach ($pool as $sample) { - if ($sample[2]) { - $message = $sample[0] . ' is between ' . $sample[1] . ' and ' . $sample[2]; - } else { - $message = $sample[0] . ' is NOT between ' . $sample[1] . ' and ' . $sample[2]; - } - $this->assertSame(Helper::between($sample[0], $sample[1], $sample[2]), $sample[3], $message); - } - } - - function testOfExactly() - { - $pool = array( - array( - 1, - '1', - true - ), - array( - 1, - 1.0, - true - ), - array( - 1, - 01, - true - ), - array( - 1, - 'a', - false - ) - ); - foreach ($pool as $sample) { - if ($sample[2]) { - $message = $sample[0] . ' is exactly ' . $sample[1]; - } else { - $message = $sample[0] . ' is NOT exactly ' . $sample[1]; - } - $this->assertSame(Helper::exactly($sample[0], $sample[1]), $sample[2], $message); + expect($sample[2])->toBe(Helper::greaterThan($sample[0], $sample[1]), $message); + } +}); + +test('between', function () { + $pool = array( + array( + 1, + 0.5, + 0.8, + false + ), + array( + 1, + 0.9, + 1.2, + true + ) + ); + foreach ($pool as $sample) { + if ($sample[2]) { + $message = $sample[0] . ' is between ' . $sample[1] . ' and ' . $sample[2]; + } else { + $message = $sample[0] . ' is NOT between ' . $sample[1] . ' and ' . $sample[2]; } - } - - function testOfNot() - { - $pool = array( - array( - 1, - '1', - false - ), - array( - 1, - 1.0, - false - ), - array( - 1, - 01, - false - ), - array( - 1, - 'a', - true - ) - ); - foreach ($pool as $sample) { - if ($sample[2]) { - $message = $sample[0] . ' is not ' . $sample[1]; - } else { - $message = $sample[0] . ' is ' . $sample[1]; - } - $this->assertSame(Helper::not($sample[0], $sample[1]), $sample[2], $message); + expect($sample[3])->toBe(Helper::between($sample[0], $sample[1], $sample[2]), $message); + } +}); + +test('exactly', function () { + $pool = array( + array( + 1, + '1', + true + ), + array( + 1, + 1.0, + true + ), + array( + 1, + 01, + true + ), + array( + 1, + 'a', + false + ) + ); + foreach ($pool as $sample) { + if ($sample[2]) { + $message = $sample[0] . ' is exactly ' . $sample[1]; + } else { + $message = $sample[0] . ' is NOT exactly ' . $sample[1]; } - } - - function testOfAlpha() - { - $pool = array( - 'Some Random String ' => true, - '123' => false, - 'With other chars :' => false - ); - foreach ($pool as $key => $value) { - if ($value) { - $message = $key . ' is a alphabetic'; - } else { - $message = $key . ' is NOT a alphabetic'; - } - $this->assertSame(Helper::alpha($key), $value, $message); + expect($sample[2])->toBe(Helper::exactly($sample[0], $sample[1]), $message); + } +}); + +test('not', function () { + $pool = array( + array( + 1, + '1', + false + ), + array( + 1, + 1.0, + false + ), + array( + 1, + 01, + false + ), + array( + 1, + 'a', + true + ) + ); + foreach ($pool as $sample) { + if ($sample[2]) { + $message = $sample[0] . ' is not ' . $sample[1]; + } else { + $message = $sample[0] . ' is ' . $sample[1]; } - } - - function testOfAlphanumeric() - { - $pool = array( - 'Some Random String ' => true, - 'Letters and 123' => true, - 'With other chars :' => false - ); - foreach ($pool as $key => $value) { - if ($value) { - $message = $key . ' is a alphanumeric'; - } else { - $message = $key . ' is NOT a alphanumeric'; - } - $this->assertSame(Helper::alphanumeric($key), $value, $message); + expect($sample[2])->toBe(Helper::not($sample[0], $sample[1]), $message); + } +}); + +test('alpha', function () { + $pool = array( + 'Some Random String ' => true, + '123' => false, + 'With other chars :' => false + ); + foreach ($pool as $key => $value) { + if ($value) { + $message = $key . ' is a alphabetic'; + } else { + $message = $key . ' is NOT a alphabetic'; } - } - - function testOfAlphanumhyphen() - { - $pool = array( - 'Some Random String ' => true, - 'Letters and 123' => true, - 'With other hyphens _ -' => true, - '? - this is not acceptable' => false - ); - foreach ($pool as $key => $value) { - if ($value) { - $message = $key . ' is a alphanumhyphen'; - } else { - $message = $key . ' is NOT a alphanumhyphen'; - } - $this->assertSame(Helper::alphanumhyphen($key), $value, $message); + expect($value)->toBe(Helper::alpha($key), $message); + } +}); + +test('alphanumeric', function () { + $pool = array( + 'Some Random String ' => true, + 'Letters and 123' => true, + 'With other chars :' => false + ); + foreach ($pool as $key => $value) { + if ($value) { + $message = $key . ' is a alphanumeric'; + } else { + $message = $key . ' is NOT a alphanumeric'; } - } - - function testOfLength() - { - $this->assertTrue(Helper::length('abc', 1, 5)); - } - - function testOfMinLength() - { - $pool = array( - array( - 'abcde', - 7, - false - ), - array( - 'abcde', - 4, - true - ) - ); - foreach ($pool as $sample) { - if ($sample[2]) { - $message = $sample[0] . ' has more than ' . $sample[1] . ' characters'; - } else { - $message = $sample[0] . ' does NOT have more than ' . $sample[1] . ' characters'; - } - $this->assertSame(Helper::minLength($sample[0], $sample[1]), $sample[2], $message); + expect($value)->toBe(Helper::alphanumeric($key), $message); + } +}); + +test('alphanumhyphen', function () { + $pool = array( + 'Some Random String ' => true, + 'Letters and 123' => true, + 'With other hyphens _ -' => true, + '? - this is not acceptable' => false + ); + foreach ($pool as $key => $value) { + if ($value) { + $message = $key . ' is a alphanumhyphen'; + } else { + $message = $key . ' is NOT a alphanumhyphen'; } - } - - function testOfMaxLength() - { - $pool = array( - array( - 'abcde', - 4, - false - ), - array( - 'abcde', - 6, - true - ) - ); - foreach ($pool as $sample) { - if ($sample[2]) { - $message = $sample[0] . ' has less than ' . $sample[1] . ' characters'; - } else { - $message = $sample[0] . ' does NOT have less than ' . $sample[1] . ' characters'; - } - $this->assertSame(Helper::maxLength($sample[0], $sample[1]), $sample[2], $message); + expect($value)->toBe(Helper::alphanumhyphen($key), $message); + } +}); + +test('length', function () { + expect(Helper::length('abc', 1, 5))->toBeTrue(); +}); + +test('min length', function () { + $pool = array( + array( + 'abcde', + 7, + false + ), + array( + 'abcde', + 4, + true + ) + ); + foreach ($pool as $sample) { + if ($sample[2]) { + $message = $sample[0] . ' has more than ' . $sample[1] . ' characters'; + } else { + $message = $sample[0] . ' does NOT have more than ' . $sample[1] . ' characters'; } - } - - function testOfIn() - { - $pool = array( - array( - '6', - array( - '5', - '8' - ), - false - ), - array( - '5', - array( - '5', - '8' - ), - true - ) - ); - foreach ($pool as $sample) { - $this->assertSame(Helper::in($sample[0], $sample[1]), $sample[2]); + expect($sample[2])->toBe(Helper::minLength($sample[0], $sample[1]), $message); + } +}); + +test('max length', function () { + $pool = array( + array( + 'abcde', + 4, + false + ), + array( + 'abcde', + 6, + true + ) + ); + foreach ($pool as $sample) { + if ($sample[2]) { + $message = $sample[0] . ' has less than ' . $sample[1] . ' characters'; + } else { + $message = $sample[0] . ' does NOT have less than ' . $sample[1] . ' characters'; } + expect($sample[2])->toBe(Helper::maxLength($sample[0], $sample[1]), $message); } +}); - function testOfNotIn() - { - $pool = array( +test('in', function () { + $pool = array( + array( + '6', array( '5', - array( - '5', - '8' - ), - false - ), - array( - '6', - array( - '5', - '8' - ), - true - ) - ); - foreach ($pool as $sample) { - $this->assertSame(Helper::notIn($sample[0], $sample[1]), $sample[2]); - } - } - - function testOfRegex() - { - $pool = array( - array( - 'abc', - '/[0-9]+/', - false - ), - array( - '123', - '/[0-9]+/', - true - ) - ); - foreach ($pool as $sample) { - $this->assertSame(Helper::regex($sample[0], $sample[1]), $sample[2]); - } - } - - function testOfNotRegex() - { - $pool = array( - array( - 'abc', - '/[0-9]+/', - true - ), - array( - '123', - '/[0-9]+/', - false - ) - ); - foreach ($pool as $sample) { - $this->assertSame(Helper::notRegex($sample[0], $sample[1]), $sample[2]); - } - } - - function testOfEqualToWithContext() - { - $pool = array( - array( - 'value', - 'element_1', - true - ), - array( - 'value', - 'element_2', - false - ), - array( - 'new value', - 'element_3[sub_element_1][sub_element_2]', - true - ) - ); - $context = array( - 'element_1' => 'value', - 'element_2' => 'another_value', - 'element_3' => array( - 'sub_element_1' => array( - 'sub_element_2' => 'new value' - ) - ) - ); - foreach ($pool as $sample) { - $this->assertSame( - Helper::equalTo($sample[0], $sample[1], $context), - $sample[2], - sprintf("%s %s", $sample[0], $sample[1]) - ); - } - } - - function testOfEqualToWithoutContext() - { - $this->assertTrue(Helper::equalTo(5, '5')); - $this->assertFalse(Helper::equalTo(5, 'a')); - } - - function testOfWebsite() - { - $pool = array( - array( - 'https://www.domain.co.uk/', - true - ), - array( - 'https://www.domain.co.uk/folder/page.html?var=value#!fragment', - true - ), - array( - '123', - false - ) - ); - foreach ($pool as $sample) { - $this->assertSame(Helper::website($sample[0]), $sample[1]); - } - } - - function testOfUrl() - { - $pool = array( - array( - 'ftp://ftp.domain.co.uk/', - true - ), - array( - 'ftp://username:password@domain.co.uk/folder/', - true - ), - array( - '123', - false - ) - ); - foreach ($pool as $sample) { - $this->assertSame(Helper::url($sample[0]), $sample[1]); - } - } - - function testOfIp() - { - $pool = array( - array( - '196.168.100.1', - true + '8' ), + false + ), + array( + '5', array( - '256.5765.53.21', - false + '5', + '8' ), - array( - '2001:db8:85a3:8d3:1319:8a2e:370:7348', - true - ) //IPv6 - ); - - foreach ($pool as $sample) { - $this->assertSame(Helper::ip($sample[0]), $sample[1]); - } + true + ) + ); + foreach ($pool as $sample) { + expect($sample[2])->toBe(Helper::inList($sample[0], $sample[1])); } +}); - function testOfSetMaxSize() - { - $set = array( - 'element_1' => 'value', - 'element_2' => 'another_value', - 'element_3' => array( - 'sub_element_1' => 'new value' - ) - ); - $pool = array( +test('not in', function () { + $pool = array( + array( + '5', array( - '4', - true + '5', + '8' ), + false + ), + array( + '6', array( - '2', - false - ) - ); - foreach ($pool as $sample) { - $this->assertSame(Helper::setMaxSize($set, $sample[0]), $sample[1]); - } - } - - function testOfSetMinSize() - { - $set = array( - 'element_1' => 'value', - 'element_2' => 'another_value', - 'element_3' => array( - 'sub_element_1' => 'new value' - ) - ); - $pool = array( - array( - '4', - false + '5', + '8' ), - array( - '2', - true + true + ) + ); + foreach ($pool as $sample) { + expect($sample[2])->toBe(Helper::notInList($sample[0], $sample[1])); + } +}); + +test('regex', function () { + $pool = array( + array( + 'abc', + '/[0-9]+/', + false + ), + array( + '123', + '/[0-9]+/', + true + ) + ); + foreach ($pool as $sample) { + expect($sample[2])->toBe(Helper::regex($sample[0], $sample[1])); + } +}); + +test('not regex', function () { + $pool = array( + array( + 'abc', + '/[0-9]+/', + true + ), + array( + '123', + '/[0-9]+/', + false + ) + ); + foreach ($pool as $sample) { + expect($sample[2])->toBe(Helper::notRegex($sample[0], $sample[1])); + } +}); + +test('equal to with context', function () { + $pool = array( + array( + 'value', + 'element_1', + true + ), + array( + 'value', + 'element_2', + false + ), + array( + 'new value', + 'element_3[sub_element_1][sub_element_2]', + true + ) + ); + $context = array( + 'element_1' => 'value', + 'element_2' => 'another_value', + 'element_3' => array( + 'sub_element_1' => array( + 'sub_element_2' => 'new value' ) - ); - foreach ($pool as $sample) { - $this->assertSame(Helper::setMinSize($set, $sample[0]), $sample[1]); - } - } - - function testOfSetSize() - { - $set = array( - 'element_1' => 'value', - 'element_2' => 'another_value', - 'element_3' => array( - 'sub_element_1' => 'new value' + ) + ); + foreach ($pool as $sample) { + expect($sample[2])->toBe(Helper::equalTo($sample[0], $sample[1], $context), sprintf("%s %s", $sample[0], $sample[1])); + } +}); + +test('not equal to with context', function () { + $pool = array( + array( + 'value', + 'element_1', + false + ), + array( + 'value', + 'element_2', + true + ), + array( + 'new value', + 'element_3[sub_element_1][sub_element_2]', + false + ) + ); + $context = array( + 'element_1' => 'value', + 'element_2' => 'another_value', + 'element_3' => array( + 'sub_element_1' => array( + 'sub_element_2' => 'new value' ) - ); - $pool = array( - array( - 2, - 5, - true - ), - array( - 6, - 8, - false - ) - ); - foreach ($pool as $sample) { - $this->assertSame(Helper::setSize($set, $sample[0], $sample[1]), $sample[2]); - } - } - - function validationCallback($value, $options = false, $context = null) - { - return ($value == 5 and $options = 3 and $context == null); - } - - function testOfValidAddMethodCalls() - { - Helper::addMethod( - 'myValidation', - array( - $this, - 'validationCallback' - ) - ); - $this->assertTrue(Helper::myValidation(5, 3)); - $this->assertFalse(Helper::myValidation(5, 3, 3)); - } - - function testOfInvalidAddMethodCalls() - { - $this->setExpectedException('InvalidArgumentException'); - Helper::addMethod('mySecondValidation', 'nonexistantcallback'); - $this->assertTrue(Helper::mySecondValidation(5)); - } - - function testOfFullName() - { - $this->assertTrue(Helper::fullName('First Last')); - $this->assertFalse(Helper::fullName('F Last')); - $this->assertFalse(Helper::fullName('First L')); - $this->assertFalse(Helper::fullName('Fi La')); - } - - function testOfEmailDomain() - { - $this->assertTrue(Helper::emailDomain('me@hotmail.com')); - $this->assertFalse(Helper::emailDomain('me@hotmail.com.not.valid')); - } - - function testOfDate() - { - $this->assertTrue(Helper::date('2012-07-13', 'Y-m-d')); - $this->assertFalse(Helper::date('2012-07-13', 'Y/m/d')); - } - - function testOfDateTime() - { - $this->assertTrue(Helper::dateTime('2012-07-13 20:00:15', 'Y-m-d H:i:s')); - $this->assertFalse(Helper::dateTime('2012-07-13')); - } - - function testOfTime() - { - $this->assertTrue(Helper::time('20:00:15', 'H:i:s')); - $this->assertFalse(Helper::time('20:00:99')); - } - + ) + ); + foreach ($pool as $sample) { + expect($sample[2])->toBe(Helper::notEqualTo($sample[0], $sample[1], $context), sprintf("%s %s", $sample[0], $sample[1])); + } +}); + +test('equal to without context', function () { + expect(Helper::equalTo(5, '5'))->toBeTrue(); + expect(Helper::equalTo(5, 'a'))->toBeFalse(); +}); + +test('not equal to without context', function () { + expect(Helper::NotEqualTo(5, 'a'))->toBeTrue(); + expect(Helper::NotEqualTo(5, '5'))->toBeFalse(); +}); + +test('website', function () { + $pool = array( + array( + 'https://www.domain.co.uk/', + true + ), + array( + 'https://www.domain.co.uk/folder/page.html?var=value#!fragment', + true + ), + array( + '123', + false + ) + ); + foreach ($pool as $sample) { + expect($sample[1])->toBe(Helper::website($sample[0])); + } +}); + +test('url', function () { + $pool = array( + array( + 'ftp://ftp.domain.co.uk/', + true + ), + array( + 'ftp://username:password@domain.co.uk/folder/', + true + ), + array( + '123', + false + ) + ); + foreach ($pool as $sample) { + expect($sample[1])->toBe(Helper::url($sample[0])); + } +}); + +test('ip', function () { + $pool = array( + array( + '196.168.100.1', + true + ), + array( + '256.576a.53.21', + false + ), + array( + '2002:51c4:7c3c:0000:0000:0000:0000:0000', + true + ) //IPv6 + ); + + foreach ($pool as $sample) { + expect($sample[1])->toBe(Helper::ipAddress($sample[0]), $sample[0]); + } +}); + +test('set max size', function () { + $set = array( + 'element_1' => 'value', + 'element_2' => 'another_value', + 'element_3' => array( + 'sub_element_1' => 'new value' + ) + ); + $pool = array( + array( + '4', + true + ), + array( + '2', + false + ) + ); + foreach ($pool as $sample) { + expect($sample[1])->toBe(Helper::setMaxSize($set, $sample[0])); + } +}); + +test('set min size', function () { + $set = array( + 'element_1' => 'value', + 'element_2' => 'another_value', + 'element_3' => array( + 'sub_element_1' => 'new value' + ) + ); + $pool = array( + array( + '4', + false + ), + array( + '2', + true + ) + ); + foreach ($pool as $sample) { + expect($sample[1])->toBe(Helper::setMinSize($set, $sample[0])); + } +}); + +test('set size', function () { + $set = array( + 'element_1' => 'value', + 'element_2' => 'another_value', + 'element_3' => array( + 'sub_element_1' => 'new value' + ) + ); + $pool = array( + array( + 2, + 5, + true + ), + array( + 6, + 8, + false + ) + ); + foreach ($pool as $sample) { + expect($sample[2])->toBe(Helper::setSize($set, $sample[0], $sample[1])); + } +}); + +function validationCallback($value, $options = false, $context = null) +{ + return ($value == 5 and $options = 3 and $context == null); } + +test('valid add method calls', function () { + Helper::addMethod( + 'myValidation', + 'validationCallback' + ); + expect(Helper::myValidation(5, 3))->toBeTrue(); + expect(Helper::myValidation(5, 3, 3))->toBeFalse(); +}); + +test('invalid add method calls', function () { + $this->expectException('InvalidArgumentException'); + Helper::addMethod('mySecondValidation', 'nonexistantcallback'); + expect(Helper::mySecondValidation(5))->toBeTrue(); +}); + +test('full name', function () { + expect(Helper::fullName('First Last'))->toBeTrue(); + expect(Helper::fullName('F Last'))->toBeFalse(); + expect(Helper::fullName('First L'))->toBeFalse(); + expect(Helper::fullName('Fi La'))->toBeFalse(); +}); + +test('email domain', function () { + expect(Helper::emailDomain('me@hotmail.com'))->toBeTrue(); + expect(Helper::emailDomain('me@hotmail.com.not.valid'))->toBeFalse(); +}); + +test('date', function () { + expect(Helper::date('2012-07-13', 'Y-m-d'))->toBeTrue(); + expect(Helper::date('2012-07-13', 'Y/m/d'))->toBeFalse(); +}); + +test('date time', function () { + expect(Helper::dateTime('2012-07-13 20:00:15', 'Y-m-d H:i:s'))->toBeTrue(); + expect(Helper::dateTime('2012-07-13'))->toBeFalse(); +}); + +test('time', function () { + expect(Helper::time('20:00:15', 'H:i:s'))->toBeTrue(); + expect(Helper::time('20:00:99'))->toBeFalse(); +}); diff --git a/tests/src/Rule/AbstractValidatorTest.php b/tests/src/Rule/AbstractValidatorTest.php index 2944d7a..4d84398 100755 --- a/tests/src/Rule/AbstractValidatorTest.php +++ b/tests/src/Rule/AbstractValidatorTest.php @@ -1,11 +1,10 @@ value = $value; $this->success = (bool)$value && isset($this->context) && $this->context->getItemValue('key'); @@ -14,55 +13,49 @@ function validate($value, $valueIdentifier = null) } } - -class AbstractRuleTest extends \PHPUnit_Framework_TestCase -{ - - function setUp() - { - $this->rule = new FakeRule(); - } - - function testErrorMessagePrototype() - { - // we always have an error message prototype - $this->assertTrue($this->rule->getErrorMessagePrototype() instanceof \Sirius\Validation\ErrorMessage); - $proto = new \Sirius\Validation\ErrorMessage('Not valid'); - $this->rule->setErrorMessagePrototype($proto); - $this->assertEquals('Not valid', (string)$this->rule->getErrorMessagePrototype()); - } - - function testMessageIsGeneratedCorrectly() - { - $this->rule->setOption('label', 'Accept'); - $this->rule->setMessageTemplate('Field "{label}" must be true, {value} was provided'); - $this->rule->validate('false'); - $this->assertEquals('Field "Accept" must be true, false was provided', (string)$this->rule->getMessage()); - } - - function testNoMessageWhenValidationPasses() - { - $this->rule->setContext(array('key' => true)); - $this->assertTrue($this->rule->validate(true)); - $this->assertNull($this->rule->getMessage()); - } - - function testContext() - { - $this->assertFalse($this->rule->validate(true)); - $this->rule->setContext(array('key' => true)); - $this->assertTrue($this->rule->validate(true)); - } - - function testErrorMessageTemplateIsUsed() - { - $this->rule->setMessageTemplate('Custom message'); - $this->assertEquals('Custom message', (string)$this->rule->getPotentialMessage()); - } - - function testErrorThrownOnInvalidContext() - { - $this->setExpectedException('\InvalidArgumentException'); - $this->rule->setContext(new \stdClass()); - } -} +beforeEach(function () { + $this->rule = new FakeRule(); +}); + +test('error message prototype', function () { + // we always have an error message prototype + expect($this->rule->getErrorMessagePrototype())->toBeInstanceOf(ErrorMessage::class); + $proto = new ErrorMessage('Not valid'); + $this->rule->setErrorMessagePrototype($proto); + expect((string) $this->rule->getErrorMessagePrototype())->toEqual('Not valid'); +}); + +test('message is generated correctly', function () { + $this->rule->setOption('label', 'Accept'); + $this->rule->setMessageTemplate('Field "{label}" must be true, {value} was provided'); + $this->rule->validate('false'); + expect((string) $this->rule->getMessage())->toEqual('Field "Accept" must be true, false was provided'); +}); + +test('no message when validation passes', function () { + $this->rule->setContext(array( 'key' => true )); + expect($this->rule->validate(true))->toBeTrue(); + expect($this->rule->getMessage())->toBeNull(); +}); + +test('context', function () { + expect($this->rule->validate(true))->toBeFalse(); + $this->rule->setContext(array( 'key' => true )); + expect($this->rule->validate(true))->toBeTrue(); +}); + +test('error message template is used', function () { + $this->rule->setMessageTemplate('Custom message'); + expect((string) $this->rule->getPotentialMessage())->toEqual('Custom message'); +}); + +test('error thrown on invalid context', function () { + $this->expectException('\InvalidArgumentException'); + $this->rule->setContext(new \stdClass()); +}); + +test('get option', function () { + $this->rule->setOption('label', 'Accept'); + expect($this->rule->getOption('label'))->toEqual('Accept'); + expect($this->rule->getOption('notExist'))->toBeNull(); +}); diff --git a/tests/src/Rule/ArrayMaxLengthTest.php b/tests/src/Rule/ArrayMaxLengthTest.php index c11a310..12870c7 100755 --- a/tests/src/Rule/ArrayMaxLengthTest.php +++ b/tests/src/Rule/ArrayMaxLengthTest.php @@ -1,19 +1,12 @@ rule = new Rule(); - } +beforeEach(function () { + $this->rule = new Rule(); +}); - function testValidationWithoutALimit() - { - $this->assertTrue($this->rule->validate(array())); - } -} +test('validation without a limit', function () { + expect($this->rule->validate(array()))->toBeTrue(); +}); diff --git a/tests/src/Rule/ArrayMinLengthTest.php b/tests/src/Rule/ArrayMinLengthTest.php index 46f2b3c..3243a31 100755 --- a/tests/src/Rule/ArrayMinLengthTest.php +++ b/tests/src/Rule/ArrayMinLengthTest.php @@ -1,19 +1,12 @@ rule = new Rule(); - } +beforeEach(function () { + $this->rule = new Rule(); +}); - function testValidationWithoutALimit() - { - $this->assertTrue($this->rule->validate(array())); - } -} +test('validation without a limit', function () { + expect($this->rule->validate(array()))->toBeTrue(); +}); diff --git a/tests/src/Rule/BetweenTest.php b/tests/src/Rule/BetweenTest.php index 22d9ebf..d546a98 100644 --- a/tests/src/Rule/BetweenTest.php +++ b/tests/src/Rule/BetweenTest.php @@ -1,24 +1,16 @@ rule = new Rule(); - } - function testValidation() - { - $this->rule->setOption('min', 50); - $this->rule->setOption('max', 100); - $this->assertFalse($this->rule->validate(40)); - $this->assertFalse($this->rule->validate(110)); - $this->assertTrue($this->rule->validate(80)); - } +beforeEach(function () { + $this->rule = new Rule(); +}); -} +test('validation', function () { + $this->rule->setOption('min', 50); + $this->rule->setOption('max', 100); + expect($this->rule->validate(40))->toBeFalse(); + expect($this->rule->validate(110))->toBeFalse(); + expect($this->rule->validate(80))->toBeTrue(); +}); diff --git a/tests/src/Rule/CallbackTest.php b/tests/src/Rule/CallbackTest.php index cb6a32b..732b2d4 100644 --- a/tests/src/Rule/CallbackTest.php +++ b/tests/src/Rule/CallbackTest.php @@ -1,58 +1,47 @@ rule = new Rule(); - } - - function testValidationWithoutAValidCallback() - { - $this->rule->setOption(Rule::OPTION_CALLBACK, 'ssss'); - $this->assertTrue($this->rule->validate('abc')); - } - - function testGetUniqueIdForCallbacksAsStrings() - { - $this->rule->setOption(Rule::OPTION_CALLBACK, 'is_int'); - $this->assertTrue(strpos($this->rule->getUniqueId(), '|is_int') !== false); - - $this->rule->setOption(Rule::OPTION_CALLBACK, 'Class::method'); - $this->assertTrue(strpos($this->rule->getUniqueId(), '|Class::method') !== false); - } - - function testGetUniqueIdForCallbacksAsArrays() - { - $this->rule->setOption(Rule::OPTION_CALLBACK, array('Class', 'method')); - $this->assertTrue(strpos($this->rule->getUniqueId(), '|Class::method') !== false); - - $this->rule->setOption(Rule::OPTION_CALLBACK, array($this, 'setUp')); - $this->assertTrue(strpos($this->rule->getUniqueId(), '->setUp') !== false); - } - - function testGetUniqueIdForCallbacksWithArguments() - { - $this->rule->setOption(Rule::OPTION_CALLBACK, 'is_int'); - $this->rule->setOption(Rule::OPTION_ARGUMENTS, array('b' => 2, 'a' => 1)); - - // arguments should be sorted by key so test for that too - $this->assertTrue(strpos($this->rule->getUniqueId(), '|{"a":1,"b":2}') !== false); - } - - function testGetUniqueIdForClosures() - { - $this->rule->setOption( - Rule::OPTION_CALLBACK, - function ($value, $valueIdentifier) { - return true; - } - ); - $this->assertNotNull($this->rule->getUniqueId()); - } -} + +beforeEach(function () { + $this->rule = new Rule(); +}); + +test('validation without a valid callback', function () { + $this->rule->setOption(Rule::OPTION_CALLBACK, 'ssss'); + expect($this->rule->validate('abc'))->toBeTrue(); +}); + +test('get unique id for callbacks as strings', function () { + $this->rule->setOption(Rule::OPTION_CALLBACK, 'is_int'); + expect(strpos($this->rule->getUniqueId(), '|is_int') !== false)->toBeTrue(); + + $this->rule->setOption(Rule::OPTION_CALLBACK, 'Class::method'); + expect(strpos($this->rule->getUniqueId(), '|Class::method') !== false)->toBeTrue(); +}); + +test('get unique id for callbacks as arrays', function () { + $this->rule->setOption(Rule::OPTION_CALLBACK, array( 'Class', 'method' )); + expect(strpos($this->rule->getUniqueId(), '|Class::method') !== false)->toBeTrue(); + + $this->rule->setOption(Rule::OPTION_CALLBACK, array( $this, 'setUp' )); + expect(strpos($this->rule->getUniqueId(), '->setUp') !== false)->toBeTrue(); +}); + +test('get unique id for callbacks with arguments', function () { + $this->rule->setOption(Rule::OPTION_CALLBACK, 'is_int'); + $this->rule->setOption(Rule::OPTION_ARGUMENTS, array( 'b' => 2, 'a' => 1 )); + + // arguments should be sorted by key so test for that too + expect(strpos($this->rule->getUniqueId(), '|{"a":1,"b":2}') !== false)->toBeTrue(); +}); + +test('get unique id for closures', function () { + $this->rule->setOption( + Rule::OPTION_CALLBACK, + function ($value, $valueIdentifier) { + return true; + } + ); + expect($this->rule->getUniqueId())->not->toBeNull(); +}); diff --git a/tests/src/Rule/EmailTest.php b/tests/src/Rule/EmailTest.php index 84adabd..2f8d88d 100644 --- a/tests/src/Rule/EmailTest.php +++ b/tests/src/Rule/EmailTest.php @@ -1,21 +1,13 @@ rule = new Rule(); - } - function testValidation() - { - $this->assertFalse($this->rule->validate('')); - $this->assertTrue($this->rule->validate('me@domain.com')); - } +beforeEach(function () { + $this->rule = new Rule(); +}); -} +test('validation', function () { + expect($this->rule->validate(''))->toBeFalse(); + expect($this->rule->validate('me@domain.com'))->toBeTrue(); +}); diff --git a/tests/src/Rule/EqualTest.php b/tests/src/Rule/EqualTest.php index a3c09a3..1322299 100644 --- a/tests/src/Rule/EqualTest.php +++ b/tests/src/Rule/EqualTest.php @@ -1,28 +1,19 @@ rule = new Rule(); - } - function testValidationWithOptionSet() - { - $this->rule->setOption(Rule::OPTION_VALUE, '123'); - $this->assertTrue($this->rule->validate('123')); - $this->assertFalse($this->rule->validate('abc')); - } +beforeEach(function () { + $this->rule = new Rule(); +}); - function testValidationWithoutOptionSet() - { - $this->assertTrue($this->rule->validate('abc')); - $this->assertTrue($this->rule->validate(null)); - } +test('validation with option set', function () { + $this->rule->setOption(Rule::OPTION_VALUE, '123'); + expect($this->rule->validate('123'))->toBeTrue(); + expect($this->rule->validate('abc'))->toBeFalse(); +}); -} +test('validation without option set', function () { + expect($this->rule->validate('abc'))->toBeTrue(); + expect($this->rule->validate(null))->toBeTrue(); +}); diff --git a/tests/src/Rule/File/ExtensionTest.php b/tests/src/Rule/File/ExtensionTest.php index a0f9b2c..8f3556a 100644 --- a/tests/src/Rule/File/ExtensionTest.php +++ b/tests/src/Rule/File/ExtensionTest.php @@ -1,43 +1,31 @@ validator = new Extension(); +}); - function setUp() - { - $this->validator = new Extension(); - } +test('existing files', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array( 'jpg' )); + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; + expect($this->validator->validate($file))->toBeTrue(); +}); - function testExistingFiles() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array('jpg')); - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; - $this->assertTrue($this->validator->validate($file)); - } +test('missing files', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array( 'jpg' )); + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'file_that_does_not_exist.jpg'; + expect($this->validator->validate($file))->toBeFalse(); +}); - function testMissingFiles() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array('jpg')); - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'file_that_does_not_exist.jpg'; - $this->assertFalse($this->validator->validate($file)); - } +test('set option as string', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, 'jpg, GIF'); + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; + expect($this->validator->validate($file))->toBeTrue(); +}); - function testSetOptionAsString() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, 'jpg, GIF'); - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; - $this->assertTrue($this->validator->validate($file)); - } - - function testPotentialMessage() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array('jpg', 'png')); - $this->validator->validate('no_file.jpg'); - $this->assertEquals( - 'The file does not have an acceptable extension (JPG, PNG)', - (string)$this->validator->getPotentialMessage() - ); - } -} +test('potential message', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array( 'jpg', 'png' )); + $this->validator->validate('no_file.jpg'); + expect((string) $this->validator->getPotentialMessage())->toEqual('The file does not have an acceptable extension (JPG, PNG)'); +}); diff --git a/tests/src/Rule/File/ImageHeightTest.php b/tests/src/Rule/File/ImageHeightTest.php index e93b0b9..d402859 100644 --- a/tests/src/Rule/File/ImageHeightTest.php +++ b/tests/src/Rule/File/ImageHeightTest.php @@ -1,32 +1,24 @@ validator = new ImageHeight(array( 'min' => 400 )); +}); - function setUp() - { - $this->validator = new ImageHeight(array('min' => 400)); - } +test('missing files', function () { + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'file_that_does_not_exist.jpg'; + expect($this->validator->validate($file))->toBeFalse(); +}); - function testMissingFiles() - { - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'file_that_does_not_exist.jpg'; - $this->assertFalse($this->validator->validate($file)); - } +test('file', function () { + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; + expect($this->validator->validate($file))->toBeTrue(); - function testFile() - { - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; - $this->assertTrue($this->validator->validate($file)); + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'square_image.gif'; + expect($this->validator->validate($file))->toBeFalse(); - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'square_image.gif'; - $this->assertFalse($this->validator->validate($file)); - - // change minimum - $this->validator->setOption(ImageHeight::OPTION_MIN, 200); - $this->assertTrue($this->validator->validate($file)); - } - -} + // change minimum + $this->validator->setOption(ImageHeight::OPTION_MIN, 200); + expect($this->validator->validate($file))->toBeTrue(); +}); diff --git a/tests/src/Rule/File/ImageRatioTest.php b/tests/src/Rule/File/ImageRatioTest.php index 57d1313..4e1da1a 100644 --- a/tests/src/Rule/File/ImageRatioTest.php +++ b/tests/src/Rule/File/ImageRatioTest.php @@ -1,56 +1,50 @@ validator = new ImageRatio(array('ratio' => 1)); - } - - function testMissingFiles() - { - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'file_that_does_not_exist.jpg'; - $this->assertFalse($this->validator->validate($file)); - } - - function testSquare() - { - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'square_image.gif'; - $this->assertTrue($this->validator->validate($file)); - } - - function testAlmostSquare() - { - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'almost_square_image.gif'; - $this->assertFalse($this->validator->validate($file)); - - // change the error margin - $this->validator->setOption(ImageRatio::OPTION_ERROR_MARGIN, 0.2); - $this->assertTrue($this->validator->validate($file)); - } - - function testRatioZero() - { - $this->validator->setOption(ImageRatio::OPTION_RATIO, 0); - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'almost_square_image.gif'; - $this->assertTrue($this->validator->validate($file)); - } - - function testInvalidRatio() - { - $this->validator->setOption(ImageRatio::OPTION_RATIO, 'abc'); - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'almost_square_image.gif'; - $this->assertTrue($this->validator->validate($file)); - } - - function testRatioAsString() - { - $this->validator->setOption(ImageRatio::OPTION_RATIO, '4:3'); - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . '4_by_3_image.jpg'; - $this->assertTrue($this->validator->validate($file)); - } - -} +use \Sirius\Validation\Rule\File\ImageRatio; + +beforeEach(function () { + $this->validator = new ImageRatio(array( 'ratio' => 1 )); +}); + +test('missing files', function () { + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'file_that_does_not_exist.jpg'; + expect($this->validator->validate($file))->toBeFalse(); +}); + +test('square', function () { + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'square_image.gif'; + expect($this->validator->validate($file))->toBeTrue(); +}); + +test('almost square', function () { + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'almost_square_image.gif'; + expect($this->validator->validate($file))->toBeFalse(); + + // change the error margin + $this->validator->setOption(ImageRatio::OPTION_ERROR_MARGIN, 0.2); + expect($this->validator->validate($file))->toBeTrue(); +}); + +test('ratio zero', function () { + $this->validator->setOption(ImageRatio::OPTION_RATIO, 0); + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'almost_square_image.gif'; + expect($this->validator->validate($file))->toBeTrue(); +}); + +test('invalid ratio', function () { + $this->validator->setOption(ImageRatio::OPTION_RATIO, 'abc'); + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'almost_square_image.gif'; + expect($this->validator->validate($file))->toBeTrue(); +}); + +test('ratio as string', function () { + $this->validator->setOption(ImageRatio::OPTION_RATIO, '4:3'); + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . '4_by_3_image.jpg'; + expect($this->validator->validate($file))->toBeTrue(); +}); + +test('file not an image', function () { + $this->validator->setOption(ImageRatio::OPTION_RATIO, '4:3'); + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'corrupt_image.jpg'; + expect($this->validator->validate($file))->toBeFalse(); +}); diff --git a/tests/src/Rule/File/ImageTest.php b/tests/src/Rule/File/ImageTest.php index ad1cc32..c754496 100644 --- a/tests/src/Rule/File/ImageTest.php +++ b/tests/src/Rule/File/ImageTest.php @@ -1,49 +1,37 @@ validator = new Image(); - } - - function testMissingFiles() - { - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'file_that_does_not_exist.jpg'; - $this->assertFalse($this->validator->validate($file)); - } - - function testRealImage() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array('jpg')); - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; - $this->assertTrue($this->validator->validate($file)); - } - - function testFakeImage() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array('jpg')); - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'fake_jpeg_file.jpg'; - $this->assertFalse($this->validator->validate($file)); - } - - function testExtensionsAsString() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, 'GIF, jpg'); - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; - $this->assertTrue($this->validator->validate($file)); - } - - function testPotentialMessage() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array('jpg', 'png')); - $this->validator->validate('no_file.jpg'); - $this->assertEquals( - 'The file is not a valid image (only JPG, PNG are allowed)', - (string)$this->validator->getPotentialMessage() - ); - } -} +use \Sirius\Validation\Rule\File\Extension; +use \Sirius\Validation\Rule\File\Image; + +beforeEach(function () { + $this->validator = new Image(); +}); + +test('missing files', function () { + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'file_that_does_not_exist.jpg'; + expect($this->validator->validate($file))->toBeFalse(); +}); + +test('real image', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array( 'jpg' )); + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; + expect($this->validator->validate($file))->toBeTrue(); +}); + +test('fake image', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array( 'jpg' )); + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'fake_jpeg_file.jpg'; + expect($this->validator->validate($file))->toBeFalse(); +}); + +test('extensions as string', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, 'GIF, jpg'); + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; + expect($this->validator->validate($file))->toBeTrue(); +}); + +test('potential message', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array( 'jpg', 'png' )); + $this->validator->validate('no_file.jpg'); + expect((string) $this->validator->getPotentialMessage())->toEqual('The file is not a valid image (only JPG, PNG are allowed)'); +}); diff --git a/tests/src/Rule/File/ImageWidthTest.php b/tests/src/Rule/File/ImageWidthTest.php index 7d3602a..5c8fd11 100644 --- a/tests/src/Rule/File/ImageWidthTest.php +++ b/tests/src/Rule/File/ImageWidthTest.php @@ -1,32 +1,24 @@ validator = new ImageWidth(array( 'min' => 500 )); +}); - function setUp() - { - $this->validator = new ImageWidth(array('min' => 500)); - } +test('missing files', function () { + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'file_that_does_not_exist.jpg'; + expect($this->validator->validate($file))->toBeFalse(); +}); - function testMissingFiles() - { - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'file_that_does_not_exist.jpg'; - $this->assertFalse($this->validator->validate($file)); - } +test('file', function () { + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; + expect($this->validator->validate($file))->toBeTrue(); - function testFile() - { - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; - $this->assertTrue($this->validator->validate($file)); + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'square_image.gif'; + expect($this->validator->validate($file))->toBeFalse(); - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'square_image.gif'; - $this->assertFalse($this->validator->validate($file)); - - // change minimum - $this->validator->setOption(ImageWidth::OPTION_MIN, 200); - $this->assertTrue($this->validator->validate($file)); - } - -} + // change minimum + $this->validator->setOption(ImageWidth::OPTION_MIN, 200); + expect($this->validator->validate($file))->toBeTrue(); +}); diff --git a/tests/src/Rule/File/SizeTest.php b/tests/src/Rule/File/SizeTest.php index f0270b0..810bdaf 100644 --- a/tests/src/Rule/File/SizeTest.php +++ b/tests/src/Rule/File/SizeTest.php @@ -1,39 +1,31 @@ validator = new Size(array('size' => '1M')); - } - - function testMissingFiles() - { - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'file_that_does_not_exist.jpg'; - $this->assertFalse($this->validator->validate($file)); - } - - function testFile() - { - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; - $this->assertTrue($this->validator->validate($file)); - - // change size - $this->validator->setOption(Size::OPTION_SIZE, '10K'); - $this->assertFalse($this->validator->validate($file)); - } - - function testSizeAsNumber() - { - $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; - $this->validator->setOption(Size::OPTION_SIZE, 1000000000000); - $this->assertTrue($this->validator->validate($file)); - - // change size - $this->validator->setOption(Size::OPTION_SIZE, 10000); - $this->assertFalse($this->validator->validate($file)); - } -} +use \Sirius\Validation\Rule\File\Size; + +beforeEach(function () { + $this->validator = new Size(array( 'size' => '1M' )); +}); + +test('missing files', function () { + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'file_that_does_not_exist.jpg'; + expect($this->validator->validate($file))->toBeFalse(); +}); + +test('file', function () { + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; + expect($this->validator->validate($file))->toBeTrue(); + + // change size + $this->validator->setOption(Size::OPTION_SIZE, '10K'); + expect($this->validator->validate($file))->toBeFalse(); +}); + +test('size as number', function () { + $file = realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . 'real_jpeg_file.jpg'; + $this->validator->setOption(Size::OPTION_SIZE, 1000000000000); + expect($this->validator->validate($file))->toBeTrue(); + + // change size + $this->validator->setOption(Size::OPTION_SIZE, 10000); + expect($this->validator->validate($file))->toBeFalse(); +}); diff --git a/tests/src/Rule/GreaterThanTest.php b/tests/src/Rule/GreaterThanTest.php index 9849f8f..18a27ee 100755 --- a/tests/src/Rule/GreaterThanTest.php +++ b/tests/src/Rule/GreaterThanTest.php @@ -1,26 +1,39 @@ rule = new Rule(); - } - - function testExclusiveValidation() - { - $this->rule->setOption('inclusive', false); - $this->rule->setOption('min', 100); - $this->assertFalse($this->rule->validate(100)); - } - - function testValidationWithoutALimit() - { - $this->assertTrue($this->rule->validate(0)); - } -} + +beforeEach(function () { + $this->rule = new Rule(); +}); + +test('default options', function () { + expect($this->rule->getOption('min'))->toBeNull(); + expect($this->rule->getOption('inclusive'))->toBeTrue(); +}); + +test('exclusive validation', function () { + $this->rule->setOption('inclusive', false); + $this->rule->setOption('min', 100); + expect($this->rule->validate(100))->toBeFalse(); +}); + +test('validation without a limit', function () { + expect($this->rule->validate(0))->toBeTrue(); +}); + +test('construct csv format min zero and inclusive false', function () { + $this->rule = new Rule('0,false'); + expect($this->rule->getOption('min'))->toBe('0'); + expect($this->rule->getOption('inclusive'))->toBe(false); +}); + +test('construct with min value zero query string format', function () { + $this->rule = new Rule('min=0'); + expect($this->rule->getOption('min'))->toBe('0'); +}); + +test('construct with min value zero csv format', function () { + $this->rule = new Rule('0'); + expect($this->rule->getOption('min'))->toBe('0'); +}); diff --git a/tests/src/Rule/InListTest.php b/tests/src/Rule/InListTest.php index e8cf996..f4aba1a 100755 --- a/tests/src/Rule/InListTest.php +++ b/tests/src/Rule/InListTest.php @@ -1,19 +1,12 @@ rule = new Rule(); - } +beforeEach(function () { + $this->rule = new Rule(); +}); - function testValidationWithoutALIstOfAcceptableValues() - { - $this->assertTrue($this->rule->validate('abc')); - } -} +test('validation without a l ist of acceptable values', function () { + expect($this->rule->validate('abc'))->toBeTrue(); +}); diff --git a/tests/src/Rule/IntegerTest.php b/tests/src/Rule/IntegerTest.php index c3cda25..cb20588 100644 --- a/tests/src/Rule/IntegerTest.php +++ b/tests/src/Rule/IntegerTest.php @@ -1,21 +1,14 @@ rule = new Rule(); - } +beforeEach(function () { + $this->rule = new Rule(); +}); - function testValidation() - { - $this->assertTrue($this->rule->validate('0')); - $this->assertTrue($this->rule->validate('10')); - $this->assertFalse($this->rule->validate('10.3')); - } -} +test('validation', function () { + expect($this->rule->validate('0'))->toBeTrue(); + expect($this->rule->validate('10'))->toBeTrue(); + expect($this->rule->validate('10.3'))->toBeFalse(); +}); diff --git a/tests/src/Rule/LessThanTest.php b/tests/src/Rule/LessThanTest.php index 9ef5726..7393bef 100755 --- a/tests/src/Rule/LessThanTest.php +++ b/tests/src/Rule/LessThanTest.php @@ -1,47 +1,39 @@ rule = new Rule(); - } - - function testExclusiveValidation() - { - $this->rule->setOption('inclusive', false); - $this->rule->setOption('max', 100); - $this->assertFalse($this->rule->validate(100)); - } - - function testValidationWithoutALimit() - { - $this->assertTrue($this->rule->validate(0)); - } - - function testOptionNormalizationForHttpQueryString() { - $this->rule = new Rule('max=100&inclusive=false'); - $this->assertFalse($this->rule->validate(100)); - - $this->rule = new Rule('max=100&inclusive=true'); - $this->assertTrue($this->rule->validate(100)); - } - - function testOptionNormalizationForJsonString() { - $this->rule = new Rule('{"max": 100, "inclusive": false}'); - $this->assertFalse($this->rule->validate(100)); - } - - function testOptionNormalizationForCsvString() { - $this->rule = new Rule('100,false'); - $this->assertFalse($this->rule->validate(100)); - - $this->rule = new Rule('100,true'); - $this->assertTrue($this->rule->validate(100)); - } -} + +beforeEach(function () { + $this->rule = new Rule(); +}); + +test('exclusive validation', function () { + $this->rule->setOption('inclusive', false); + $this->rule->setOption('max', 100); + expect($this->rule->validate(100))->toBeFalse(); +}); + +test('validation without a limit', function () { + expect($this->rule->validate(0))->toBeTrue(); +}); + +test('option normalization for http query string', function () { + $this->rule = new Rule('max=100&inclusive=false'); + expect($this->rule->validate(100))->toBeFalse(); + + $this->rule = new Rule('max=100&inclusive=true'); + expect($this->rule->validate(100))->toBeTrue(); +}); + +test('option normalization for json string', function () { + $this->rule = new Rule('{"max": 100, "inclusive": false}'); + expect($this->rule->validate(100))->toBeFalse(); +}); + +test('option normalization for csv string', function () { + $this->rule = new Rule('100,false'); + expect($this->rule->validate(100))->toBeFalse(); + + $this->rule = new Rule('100,true'); + expect($this->rule->validate(100))->toBeTrue(); +}); diff --git a/tests/src/Rule/MatchTest.php b/tests/src/Rule/MatchTest.php index 7355318..d545359 100644 --- a/tests/src/Rule/MatchTest.php +++ b/tests/src/Rule/MatchTest.php @@ -1,36 +1,27 @@ rule = new Rule(); - $this->rule->setContext( - new ArrayWrapper( - array( - 'password' => 'secret' - ) +beforeEach(function () { + $this->rule = new Rule(); + $this->rule->setContext( + new ArrayWrapper( + array( + 'password' => 'secret' ) - ); - } - - function testValidationWithItemPresent() - { - $this->rule->setOption(Rule::OPTION_ITEM, 'password'); - $this->assertTrue($this->rule->validate('secret')); - $this->assertFalse($this->rule->validate('abc')); - } + ) + ); +}); - function testValidationWithoutItemPresent() - { - $this->assertTrue($this->rule->validate('abc')); - $this->assertTrue($this->rule->validate(null)); - } +test('validation with item present', function () { + $this->rule->setOption(Rule::OPTION_ITEM, 'password'); + expect($this->rule->validate('secret'))->toBeTrue(); + expect($this->rule->validate('abc'))->toBeFalse(); +}); -} +test('validation without item present', function () { + expect($this->rule->validate('abc'))->toBeTrue(); + expect($this->rule->validate(null))->toBeTrue(); +}); diff --git a/tests/src/Rule/NotEqualTest.php b/tests/src/Rule/NotEqualTest.php new file mode 100644 index 0000000..113b6f0 --- /dev/null +++ b/tests/src/Rule/NotEqualTest.php @@ -0,0 +1,19 @@ +rule = new Rule(); +}); + +test('validation with option set', function () { + $this->rule->setOption(Rule::OPTION_VALUE, '123'); + expect($this->rule->validate('123'))->toBeFalse(); + expect($this->rule->validate('abc'))->toBeTrue(); +}); + +test('validation without option set', function () { + expect($this->rule->validate('abc'))->toBeFalse(); + expect($this->rule->validate(null))->toBeFalse(); +}); diff --git a/tests/src/Rule/NotInListTest.php b/tests/src/Rule/NotInListTest.php index 8c5384f..e9feeda 100755 --- a/tests/src/Rule/NotInListTest.php +++ b/tests/src/Rule/NotInListTest.php @@ -1,19 +1,12 @@ rule = new Rule(); - } +beforeEach(function () { + $this->rule = new Rule(); +}); - function testValidationWithoutAListOfForbiddenValues() - { - $this->assertTrue($this->rule->validate('abc')); - } -} +test('validation without a list of forbidden values', function () { + expect($this->rule->validate('abc'))->toBeTrue(); +}); diff --git a/tests/src/Rule/NotMatchTest.php b/tests/src/Rule/NotMatchTest.php new file mode 100644 index 0000000..6b1ce49 --- /dev/null +++ b/tests/src/Rule/NotMatchTest.php @@ -0,0 +1,27 @@ +rule = new Rule(); + $this->rule->setContext( + new ArrayWrapper( + array( + 'password' => 'secret' + ) + ) + ); +}); + +test('validation with item present', function () { + $this->rule->setOption(Rule::OPTION_ITEM, 'password'); + expect($this->rule->validate('secret'))->toBeFalse(); + expect($this->rule->validate('abc'))->toBeTrue(); +}); + +test('validation without item present', function () { + expect($this->rule->validate('abc'))->toBeFalse(); + expect($this->rule->validate(null))->toBeFalse(); +}); diff --git a/tests/src/Rule/NumberTest.php b/tests/src/Rule/NumberTest.php index ddf53fe..8db90d3 100644 --- a/tests/src/Rule/NumberTest.php +++ b/tests/src/Rule/NumberTest.php @@ -1,21 +1,14 @@ rule = new Rule(); - } +beforeEach(function () { + $this->rule = new Rule(); +}); - function testValidation() - { - $this->assertTrue($this->rule->validate('0')); - $this->assertTrue($this->rule->validate('0.3')); - $this->assertFalse($this->rule->validate('0,3')); - } -} +test('validation', function () { + expect($this->rule->validate('0'))->toBeTrue(); + expect($this->rule->validate('0.3'))->toBeTrue(); + expect($this->rule->validate('0,3'))->toBeFalse(); +}); diff --git a/tests/src/Rule/RegexTest.php b/tests/src/Rule/RegexTest.php index e4b581e..6bb38a1 100755 --- a/tests/src/Rule/RegexTest.php +++ b/tests/src/Rule/RegexTest.php @@ -1,20 +1,13 @@ rule = new Rule(); - } +beforeEach(function () { + $this->rule = new Rule(); +}); - function testValidationWithoutARegexPattern() - { - // pattern was not set, everything is valid - $this->assertTrue($this->rule->validate('abc')); - } -} +test('validation without a regex pattern', function () { + // pattern was not set, everything is valid + expect($this->rule->validate('abc'))->toBeTrue(); +}); diff --git a/tests/src/Rule/RequiredTest.php b/tests/src/Rule/RequiredTest.php index 67f0e47..4bd9597 100644 --- a/tests/src/Rule/RequiredTest.php +++ b/tests/src/Rule/RequiredTest.php @@ -1,35 +1,21 @@ rule = new Rule(); - } +beforeEach(function () { + $this->rule = new Rule(); +}); - function testValidationWithNull() - { - $this->assertFalse($this->rule->validate(null)); - } +test('validation with null', function () { + expect($this->rule->validate(null))->toBeFalse(); +}); - function testValidationWithEmptyString() - { - $this->assertFalse($this->rule->validate('')); - } +test('validation with empty string', function () { + expect($this->rule->validate(''))->toBeFalse(); +}); - function testValidationWithWhitespaceString() - { - $this->assertTrue($this->rule->validate(' ')); - } -} +test('validation with whitespace string', function () { + expect($this->rule->validate(' '))->toBeTrue(); +}); diff --git a/tests/src/Rule/RequiredWhenTest.php b/tests/src/Rule/RequiredWhenTest.php index aa5f975..d8d8ad6 100644 --- a/tests/src/Rule/RequiredWhenTest.php +++ b/tests/src/Rule/RequiredWhenTest.php @@ -1,89 +1,82 @@ rule = new Rule(); - } +beforeEach(function () { + $this->rule = new Rule(); +}); - function testValidationWithItemValid() - { - $this->rule->setOption(Rule::OPTION_ITEM, 'email'); - $this->rule->setOption(Rule::OPTION_RULE, 'Email'); - $this->rule->setContext( - new ArrayWrapper( - array( - 'email' => 'me@domain.com' - ) +test('validation with item valid', function () { + $this->rule->setOption(Rule::OPTION_ITEM, 'email'); + $this->rule->setOption(Rule::OPTION_RULE, 'Email'); + $this->rule->setContext( + new ArrayWrapper( + array( + 'email' => 'me@domain.com' ) - ); - $this->assertTrue($this->rule->validate('abc')); - $this->assertFalse($this->rule->validate(null)); - } + ) + ); + expect($this->rule->validate('abc'))->toBeTrue(); + expect($this->rule->validate(null))->toBeFalse(); + expect($this->rule->validate(''))->toBeFalse(); +}); - function testValidationWithItemNotValid() - { - $this->rule->setOption(Rule::OPTION_ITEM, 'email'); - $this->rule->setOption(Rule::OPTION_RULE, 'Sirius\Validation\Rule\Email'); - $this->rule->setContext( - new ArrayWrapper( - array( - 'email' => 'not_a_valid_email' - ) +test('validation with item not valid', function () { + $this->rule->setOption(Rule::OPTION_ITEM, 'email'); + $this->rule->setOption(Rule::OPTION_RULE, 'Sirius\Validation\Rule\Email'); + $this->rule->setContext( + new ArrayWrapper( + array( + 'email' => 'not_a_valid_email' ) - ); - $this->assertTrue($this->rule->validate('abc')); - $this->assertTrue($this->rule->validate(null)); - } + ) + ); + expect($this->rule->validate('abc'))->toBeTrue(); + expect($this->rule->validate(null))->toBeTrue(); + expect($this->rule->validate(''))->toBeTrue(); +}); - function testValidationWithoutItem() - { - $this->rule->setOption(Rule::OPTION_RULE, 'Sirius\Validation\Rule\Email'); - $this->rule->setContext( - new ArrayWrapper( - array( - 'email' => 'not_a_valid_email' - ) +test('validation without item', function () { + $this->rule->setOption(Rule::OPTION_RULE, 'Sirius\Validation\Rule\Email'); + $this->rule->setContext( + new ArrayWrapper( + array( + 'email' => 'not_a_valid_email' ) - ); - $this->assertTrue($this->rule->validate('abc')); - $this->assertTrue($this->rule->validate(null)); - } + ) + ); + expect($this->rule->validate('abc'))->toBeTrue(); + expect($this->rule->validate(null))->toBeTrue(); + expect($this->rule->validate(''))->toBeTrue(); +}); - function testItemRuleSetAsRuleObject() - { - $this->rule->setOption(Rule::OPTION_ITEM, 'email'); - $this->rule->setOption(Rule::OPTION_RULE, new \Sirius\Validation\Rule\Email); - $this->rule->setContext( - new ArrayWrapper( - array( - 'email' => 'me@domain.com' - ) +test('item rule set as rule object', function () { + $this->rule->setOption(Rule::OPTION_ITEM, 'email'); + $this->rule->setOption(Rule::OPTION_RULE, new \Sirius\Validation\Rule\Email); + $this->rule->setContext( + new ArrayWrapper( + array( + 'email' => 'me@domain.com' ) - ); - $this->assertTrue($this->rule->validate('abc')); - $this->assertFalse($this->rule->validate(null)); - } + ) + ); + expect($this->rule->validate('abc'))->toBeTrue(); + expect($this->rule->validate(null))->toBeFalse(); + expect($this->rule->validate(''))->toBeFalse(); +}); - function testExceptionThrownOnInvalidItemRule() - { - $this->setExpectedException('\InvalidArgumentException'); - $this->rule->setOption(Rule::OPTION_ITEM, 'email'); - $this->rule->setOption(Rule::OPTION_RULE, new \stdClass()); - $this->rule->setContext( - new ArrayWrapper( - array( - 'email' => 'me@domain.com' - ) +test('exception thrown on invalid item rule', function () { + $this->expectException('\InvalidArgumentException'); + $this->rule->setOption(Rule::OPTION_ITEM, 'email'); + $this->rule->setOption(Rule::OPTION_RULE, new \stdClass()); + $this->rule->setContext( + new ArrayWrapper( + array( + 'email' => 'me@domain.com' ) - ); - $this->assertTrue($this->rule->validate('abc')); - } -} + ) + ); + expect($this->rule->validate('abc'))->toBeTrue(); +}); diff --git a/tests/src/Rule/RequiredWithTest.php b/tests/src/Rule/RequiredWithTest.php index db953e0..9e01bb7 100644 --- a/tests/src/Rule/RequiredWithTest.php +++ b/tests/src/Rule/RequiredWithTest.php @@ -1,57 +1,44 @@ rule = new Rule(); - $this->rule->setContext( - new ArrayWrapper( - array( - 'item_1' => 'is_present' - ) +beforeEach(function () { + $this->rule = new Rule(); + $this->rule->setContext( + new ArrayWrapper( + array( + 'item_1' => 'is_present' ) - ); - } - - function testValidationWithItemPresent() - { - $this->rule->setOption(Rule::OPTION_ITEM, 'item_1'); - $this->assertTrue($this->rule->validate('abc')); - $this->assertFalse($this->rule->validate(null)); - } - - function testValidationWithoutItemPresent() - { - $this->rule->setOption(Rule::OPTION_ITEM, 'item_2'); - $this->assertTrue($this->rule->validate('abc')); - $this->assertTrue($this->rule->validate(null)); - } - - function testValidationWithDeepItems() - { - $this->rule->setOption(Rule::OPTION_ITEM, 'lines[*][quantity]'); - $this->rule->setContext(new ArrayWrapper( - array( - 'lines' => array( - 0 => array('quantity' => 10, 'price' => 10), - 1 => array('quantity' => 20, 'price' => null), - ) - )) - ); - $this->assertTrue($this->rule->validate(10, 'lines[0][price]')); - $this->assertFalse($this->rule->validate(null, 'lines[1][price]')); - } - -} + ) + ); +}); + +test('validation with item present', function () { + $this->rule->setOption(Rule::OPTION_ITEM, 'item_1'); + expect($this->rule->validate('abc'))->toBeTrue(); + expect($this->rule->validate(null))->toBeFalse(); + expect($this->rule->validate(''))->toBeFalse(); +}); + +test('validation without item present', function () { + $this->rule->setOption(Rule::OPTION_ITEM, 'item_2'); + expect($this->rule->validate('abc'))->toBeTrue(); + expect($this->rule->validate(null))->toBeTrue(); +}); + +test('validation with deep items', function () { + $this->rule->setOption(Rule::OPTION_ITEM, 'lines[*][quantity]'); + $this->rule->setContext(new ArrayWrapper( + array( + 'lines' => array( + 0 => array( 'quantity' => 10, 'price' => 10 ), + 1 => array( 'quantity' => 20, 'price' => null ), + ) + )) + ); + expect($this->rule->validate(10, 'lines[0][price]'))->toBeTrue(); + expect($this->rule->validate(null, 'lines[1][price]'))->toBeFalse(); + expect($this->rule->validate('', 'lines[1][price]'))->toBeFalse(); +}); diff --git a/tests/src/Rule/RequiredWithoutTest.php b/tests/src/Rule/RequiredWithoutTest.php index 1a2666e..836484a 100644 --- a/tests/src/Rule/RequiredWithoutTest.php +++ b/tests/src/Rule/RequiredWithoutTest.php @@ -1,56 +1,45 @@ rule = new Rule(); - $this->rule->setContext( - new ArrayWrapper( - array( - 'item_1' => 'is_present' - ) +beforeEach(function () { + $this->rule = new Rule(); + $this->rule->setContext( + new ArrayWrapper( + array( + 'item_1' => 'is_present' ) - ); - } - - function testValidationWithoutItemPresent() - { - $this->rule->setOption(Rule::OPTION_ITEM, 'item_2'); - $this->assertTrue($this->rule->validate('abc')); - $this->assertFalse($this->rule->validate(null)); - } - - function testValidationWithItemPresent() - { - $this->rule->setOption(Rule::OPTION_ITEM, 'item_1'); - $this->assertTrue($this->rule->validate('abc')); - $this->assertTrue($this->rule->validate(null)); - } - - function testValidationWithDeepItems() - { - $this->rule->setOption(Rule::OPTION_ITEM, 'lines[*][quantity]'); - $this->rule->setContext(new ArrayWrapper( - array( - 'lines' => array( - 0 => array('quantity' => null, 'price' => null), - 1 => array('quantity' => 20, 'price' => null), - ) - )) - ); - $this->assertFalse($this->rule->validate(null, 'lines[0][price]')); - $this->assertTrue($this->rule->validate(null, 'lines[1][price]')); - } -} + ) + ); +}); + +test('validation without item present', function () { + $this->rule->setOption(Rule::OPTION_ITEM, 'item_2'); + expect($this->rule->validate('abc'))->toBeTrue(); + expect($this->rule->validate(null))->toBeFalse(); + expect($this->rule->validate(''))->toBeFalse(); +}); + +test('validation with item present', function () { + $this->rule->setOption(Rule::OPTION_ITEM, 'item_1'); + expect($this->rule->validate('abc'))->toBeTrue(); + expect($this->rule->validate(null))->toBeTrue(); + expect($this->rule->validate(''))->toBeTrue(); +}); + +test('validation with deep items', function () { + $this->rule->setOption(Rule::OPTION_ITEM, 'lines[*][quantity]'); + $this->rule->setContext(new ArrayWrapper( + array( + 'lines' => array( + 0 => array( 'quantity' => null, 'price' => null ), + 1 => array( 'quantity' => 20, 'price' => null ), + ) + )) + ); + expect($this->rule->validate(null, 'lines[0][price]'))->toBeFalse(); + expect($this->rule->validate(null, 'lines[1][price]'))->toBeTrue(); + expect($this->rule->validate('', 'lines[1][price]'))->toBeTrue(); +}); diff --git a/tests/src/Rule/Upload/ExtensionTest.php b/tests/src/Rule/Upload/ExtensionTest.php index 23c4a73..9c6f020 100644 --- a/tests/src/Rule/Upload/ExtensionTest.php +++ b/tests/src/Rule/Upload/ExtensionTest.php @@ -1,63 +1,62 @@ validator = new Extension(); +}); - function setUp() - { - $this->validator = new Extension(); - } +test('existing files', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array( 'jpg' )); + $fileName = 'real_jpeg_file.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeTrue(); +}); - function testExistingFiles() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array('jpg')); - $fileName = 'real_jpeg_file.jpg'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertTrue($this->validator->validate($file)); - } +test('no upload', function () { + $file = array( + 'name' => 'not_required', + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => 'not_required', + 'error' => UPLOAD_ERR_NO_FILE + ); + expect($this->validator->validate($file))->toBeTrue(); +}); - function testMissingFiles() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array('jpg')); - $fileName = 'file_that_does_not_exist.jpg'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertFalse($this->validator->validate($file)); - } +test('missing files', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array( 'jpg' )); + $fileName = 'file_that_does_not_exist.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeFalse(); +}); - function testSetOptionAsString() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, 'jpg, GIF'); - $fileName = 'real_jpeg_file.jpg'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertTrue($this->validator->validate($file)); - } +test('set option as string', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, 'jpg, GIF'); + $fileName = 'real_jpeg_file.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeTrue(); +}); - function testPotentialMessage() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array('jpg', 'png')); - $this->assertEquals( - 'The file does not have an acceptable extension (JPG, PNG)', - (string)$this->validator->getPotentialMessage() - ); - } -} +test('potential message', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array( 'jpg', 'png' )); + expect((string) $this->validator->getPotentialMessage())->toEqual('The file does not have an acceptable extension (JPG, PNG)'); +}); diff --git a/tests/src/Rule/Upload/ImageHeightTest.php b/tests/src/Rule/Upload/ImageHeightTest.php index 8937d7f..109e047 100644 --- a/tests/src/Rule/Upload/ImageHeightTest.php +++ b/tests/src/Rule/Upload/ImageHeightTest.php @@ -1,53 +1,56 @@ validator = new ImageHeight(array('min' => 400)); - } - - function testMissingFiles() - { - $fileName = 'file_that_does_not_exist.jpg'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertFalse($this->validator->validate($file)); - } - - function testFile() - { - $fileName = 'real_jpeg_file.jpg'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertTrue($this->validator->validate($file)); - - $fileName = 'square_image.gif'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertFalse($this->validator->validate($file)); - - // change minimum - $this->validator->setOption(ImageHeight::OPTION_MIN, 200); - $this->assertTrue($this->validator->validate($file)); - } - -} +use \Sirius\Validation\Rule\Upload\ImageHeight; + +beforeEach(function () { + $this->validator = new ImageHeight(array( 'min' => 400 )); +}); + +test('missing files', function () { + $fileName = 'file_that_does_not_exist.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeFalse(); +}); + +test('no upload', function () { + $file = array( + 'name' => 'not_required', + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => 'not_required', + 'error' => UPLOAD_ERR_NO_FILE + ); + expect($this->validator->validate($file))->toBeTrue(); +}); + +test('file', function () { + $fileName = 'real_jpeg_file.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeTrue(); + + $fileName = 'square_image.gif'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeFalse(); + + // change minimum + $this->validator->setOption(ImageHeight::OPTION_MIN, 200); + expect($this->validator->validate($file))->toBeTrue(); +}); diff --git a/tests/src/Rule/Upload/ImageRatioTest.php b/tests/src/Rule/Upload/ImageRatioTest.php index f34d079..d47f674 100644 --- a/tests/src/Rule/Upload/ImageRatioTest.php +++ b/tests/src/Rule/Upload/ImageRatioTest.php @@ -1,98 +1,110 @@ validator = new ImageRatio(array( 'ratio' => 1 )); +}); - function setUp() - { - $this->validator = new ImageRatio(array('ratio' => 1)); - } +test('missing files', function () { + $fileName = 'file_that_does_not_exist.gif'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeFalse(); +}); - function testMissingFiles() - { - $fileName = 'file_that_does_not_exist.gif'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertFalse($this->validator->validate($file)); - } +test('no upload', function () { + $file = array( + 'name' => 'not_required', + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => 'not_required', + 'error' => UPLOAD_ERR_NO_FILE + ); + expect($this->validator->validate($file))->toBeTrue(); +}); - function testSquare() - { - $fileName = 'square_image.gif'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertTrue($this->validator->validate($file)); - } +test('square', function () { + $fileName = 'square_image.gif'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeTrue(); +}); - function testAlmostSquare() - { - $fileName = 'almost_square_image.gif'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertFalse($this->validator->validate($file)); +test('almost square', function () { + $fileName = 'almost_square_image.gif'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeFalse(); - // change the error margin - $this->validator->setOption(ImageRatio::OPTION_ERROR_MARGIN, 0.2); - $this->assertTrue($this->validator->validate($file)); - } + // change the error margin + $this->validator->setOption(ImageRatio::OPTION_ERROR_MARGIN, 0.2); + expect($this->validator->validate($file))->toBeTrue(); +}); - function testRatioZero() - { - $this->validator->setOption(ImageRatio::OPTION_RATIO, 0); - $fileName = 'almost_square_image.gif'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertTrue($this->validator->validate($file)); - } +test('ratio zero', function () { + $this->validator->setOption(ImageRatio::OPTION_RATIO, 0); + $fileName = 'almost_square_image.gif'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeTrue(); +}); - function testInvalidRatio() - { - $this->validator->setOption(ImageRatio::OPTION_RATIO, 'abc'); - $fileName = 'almost_square_image.gif'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertTrue($this->validator->validate($file)); - } +test('invalid ratio', function () { + $this->validator->setOption(ImageRatio::OPTION_RATIO, 'abc'); + $fileName = 'almost_square_image.gif'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeTrue(); +}); - function testRatioAsString() - { - $this->validator->setOption(ImageRatio::OPTION_RATIO, '4:3'); - $fileName = '4_by_3_image.jpg'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertTrue($this->validator->validate($file)); - } +test('ratio as string', function () { + $this->validator->setOption(ImageRatio::OPTION_RATIO, '4:3'); + $fileName = '4_by_3_image.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeTrue(); +}); -} +test('file not an image', function () { + $this->validator->setOption(ImageRatio::OPTION_RATIO, '4:3'); + $fileName = 'corrupt_image.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeFalse(); +}); diff --git a/tests/src/Rule/Upload/ImageTest.php b/tests/src/Rule/Upload/ImageTest.php index 4c2b99b..bf1b93f 100644 --- a/tests/src/Rule/Upload/ImageTest.php +++ b/tests/src/Rule/Upload/ImageTest.php @@ -1,76 +1,75 @@ validator = new Image(); +}); - function setUp() - { - $this->validator = new Image(); - } +test('missing files', function () { + $fileName = 'file_that_does_not_exist.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeFalse(); +}); - function testMissingFiles() - { - $fileName = 'file_that_does_not_exist.jpg'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertFalse($this->validator->validate($file)); - } +test('no upload', function () { + $file = array( + 'name' => 'not_required', + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => 'not_required', + 'error' => UPLOAD_ERR_NO_FILE + ); + expect($this->validator->validate($file))->toBeTrue(); +}); - function testRealImage() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array('jpg')); - $fileName = 'real_jpeg_file.jpg'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertTrue($this->validator->validate($file)); - } +test('real image', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array( 'jpg' )); + $fileName = 'real_jpeg_file.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeTrue(); +}); - function testFakeImage() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array('jpg')); - $fileName = 'fake_jpeg_file.jpg'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertFalse($this->validator->validate($file)); - } +test('fake image', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array( 'jpg' )); + $fileName = 'fake_jpeg_file.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeFalse(); +}); - function testExtensionsAsString() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, 'GIF, jpg'); - $fileName = 'real_jpeg_file.jpg'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertTrue($this->validator->validate($file)); - } +test('extensions as string', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, 'GIF, jpg'); + $fileName = 'real_jpeg_file.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeTrue(); +}); - function testPotentialMessage() - { - $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array('jpg', 'png')); - $this->assertEquals( - 'The file is not a valid image (only JPG, PNG are allowed)', - (string)$this->validator->getPotentialMessage() - ); - } -} +test('potential message', function () { + $this->validator->setOption(Extension::OPTION_ALLOWED_EXTENSIONS, array( 'jpg', 'png' )); + expect((string) $this->validator->getPotentialMessage())->toEqual('The file is not a valid image (only JPG, PNG are allowed)'); +}); diff --git a/tests/src/Rule/Upload/ImageWidthTest.php b/tests/src/Rule/Upload/ImageWidthTest.php index a7de83b..eaf7eb0 100644 --- a/tests/src/Rule/Upload/ImageWidthTest.php +++ b/tests/src/Rule/Upload/ImageWidthTest.php @@ -1,53 +1,56 @@ validator = new ImageWidth(array('min' => 500)); - } - - function testMissingFiles() - { - $fileName = 'file_that_does_not_exist.jpg'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertFalse($this->validator->validate($file)); - } - - function testFile() - { - $fileName = 'real_jpeg_file.jpg'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertTrue($this->validator->validate($file)); - - $fileName = 'square_image.gif'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertFalse($this->validator->validate($file)); - - // change minimum - $this->validator->setOption(ImageWidth::OPTION_MIN, 200); - $this->assertTrue($this->validator->validate($file)); - } - -} +use \Sirius\Validation\Rule\Upload\ImageWidth; + +beforeEach(function () { + $this->validator = new ImageWidth(array( 'min' => 500 )); +}); + +test('missing files', function () { + $fileName = 'file_that_does_not_exist.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeFalse(); +}); + +test('no upload', function () { + $file = array( + 'name' => 'not_required', + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => 'not_required', + 'error' => UPLOAD_ERR_NO_FILE + ); + expect($this->validator->validate($file))->toBeTrue(); +}); + +test('file', function () { + $fileName = 'real_jpeg_file.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeTrue(); + + $fileName = 'square_image.gif'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeFalse(); + + // change minimum + $this->validator->setOption(ImageWidth::OPTION_MIN, 200); + expect($this->validator->validate($file))->toBeTrue(); +}); diff --git a/tests/src/Rule/Upload/RequiredTest.php b/tests/src/Rule/Upload/RequiredTest.php new file mode 100644 index 0000000..fc52395 --- /dev/null +++ b/tests/src/Rule/Upload/RequiredTest.php @@ -0,0 +1,66 @@ +validator = new Required(); +}); + +test('missing files', function () { + $fileName = 'file_that_does_not_exist.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeFalse(); +}); + +test('upload ok', function () { + $fileName = 'real_jpeg_file.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeTrue(); +}); + +test('upload not ok', function () { + $fileName = 'real_jpeg_file.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_PARTIAL + ); + expect($this->validator->validate($file))->toBeFalse(); +}); + +test('no upload', function () { + $file = array( + 'name' => 'not_required', + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => 'not_required', + 'error' => UPLOAD_ERR_NO_FILE + ); + expect($this->validator->validate($file))->toBeFalse(); +}); + +test('file', function () { + $fileName = 'real_jpeg_file.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeTrue(); +}); diff --git a/tests/src/Rule/Upload/SizeTest.php b/tests/src/Rule/Upload/SizeTest.php index 35546a9..66f86f0 100644 --- a/tests/src/Rule/Upload/SizeTest.php +++ b/tests/src/Rule/Upload/SizeTest.php @@ -1,60 +1,63 @@ validator = new Size(array('size' => '1M')); - } - - function testMissingFiles() - { - $fileName = 'file_that_does_not_exist.jpg'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertFalse($this->validator->validate($file)); - } - - function testFile() - { - $fileName = 'real_jpeg_file.jpg'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->assertTrue($this->validator->validate($file)); - - // change size - $this->validator->setOption(Size::OPTION_SIZE, '10K'); - $this->assertFalse($this->validator->validate($file)); - } - - function testSizeAsNumber() - { - $fileName = 'real_jpeg_file.jpg'; - $file = array( - 'name' => $fileName, - 'type' => 'not_required', - 'size' => 'not_required', - 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, - 'error' => UPLOAD_ERR_OK - ); - $this->validator->setOption(Size::OPTION_SIZE, 1000000000000); - $this->assertTrue($this->validator->validate($file)); - - // change size - $this->validator->setOption(Size::OPTION_SIZE, 10000); - $this->assertFalse($this->validator->validate($file)); - } -} +use \Sirius\Validation\Rule\Upload\Size; + +beforeEach(function () { + $this->validator = new Size(array( 'size' => '1M' )); +}); + + +test('missing files', function () { + $fileName = 'file_that_does_not_exist.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeFalse(); +}); + +test('no upload', function () { + $file = array( + 'name' => 'not_required', + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => 'not_required', + 'error' => UPLOAD_ERR_NO_FILE + ); + expect($this->validator->validate($file))->toBeTrue(); +}); + +test('file', function () { + $fileName = 'real_jpeg_file.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + expect($this->validator->validate($file))->toBeTrue(); + + // change size + $this->validator->setOption(Size::OPTION_SIZE, '10K'); + expect($this->validator->validate($file))->toBeFalse(); +}); + +test('size as number', function () { + $fileName = 'real_jpeg_file.jpg'; + $file = array( + 'name' => $fileName, + 'type' => 'not_required', + 'size' => 'not_required', + 'tmp_name' => realpath(__DIR__ . '/../../../fixitures/') . DIRECTORY_SEPARATOR . $fileName, + 'error' => UPLOAD_ERR_OK + ); + $this->validator->setOption(Size::OPTION_SIZE, 1000000000000); + expect($this->validator->validate($file))->toBeTrue(); + + // change size + $this->validator->setOption(Size::OPTION_SIZE, 10000); + expect($this->validator->validate($file))->toBeFalse(); +}); diff --git a/tests/src/Rule/UrlTest.php b/tests/src/Rule/UrlTest.php new file mode 100644 index 0000000..5b9a4af --- /dev/null +++ b/tests/src/Rule/UrlTest.php @@ -0,0 +1,13 @@ +rule = new Rule(); +}); + +test('validation', function () { + expect($this->rule->validate(''))->toBeFalse(); + expect($this->rule->validate('http://www.google.com'))->toBeTrue(); +}); diff --git a/tests/src/Rule/WebsiteTest.php b/tests/src/Rule/WebsiteTest.php index 62f7448..9c0a970 100644 --- a/tests/src/Rule/WebsiteTest.php +++ b/tests/src/Rule/WebsiteTest.php @@ -1,19 +1,12 @@ rule = new Rule(); - } +beforeEach(function () { + $this->rule = new Rule(); +}); - function testNonHttpAddresses() - { - $this->assertTrue($this->rule->validate('//google.com')); - } -} +test('non http addresses', function () { + expect($this->rule->validate('//google.com'))->toBeTrue(); +}); diff --git a/tests/src/RuleCollectionTest.php b/tests/src/RuleCollectionTest.php index 7868554..8019c44 100644 --- a/tests/src/RuleCollectionTest.php +++ b/tests/src/RuleCollectionTest.php @@ -1,35 +1,31 @@ collection = new RuleCollection(); - } - - function testAddAndRemove() - { - $this->collection->attach(new Rule\Required); - $this->assertEquals(1, count($this->collection)); +beforeEach(function () { + $this->collection = new RuleCollection(); +}); - $this->collection->detach(new Rule\Required); - $this->assertEquals(0, count($this->collection)); - } +test('add and remove', function () { + $this->collection->attach(new Required()); + expect(count($this->collection))->toEqual(1); - function testIterator() - { - $this->collection->attach(new Rule\Email); - $this->collection->attach(new Rule\Required); + $this->collection->detach(new Required()); + expect(count($this->collection))->toEqual(0); +}); - $rules = array(); - foreach ($this->collection as $k => $rule) { - $rules[] = $rule; - } +test('iterator', function () { + $this->collection->attach(new Email); + $this->collection->attach(new Required); - // the required rule should be first - $this->assertTrue($rules[0] instanceof Rule\Required); - $this->assertTrue($rules[1] instanceof Rule\Email); + $rules = array(); + foreach ($this->collection as $k => $rule) { + $rules[] = $rule; } -} + + // the required rule should be first + expect($rules[0] instanceof Required)->toBeTrue(); + expect($rules[1] instanceof Email)->toBeTrue(); +}); diff --git a/tests/src/RuleFactoryTest.php b/tests/src/RuleFactoryTest.php index 95611a1..2f4b03c 100644 --- a/tests/src/RuleFactoryTest.php +++ b/tests/src/RuleFactoryTest.php @@ -1,49 +1,40 @@ ruleFactory = new RuleFactory(); +}); - function setUp() - { - $this->ruleFactory = new RuleFactory(); - } +test('registration of validator classes', function () { + $this->ruleFactory->register('even', TestingCustomRule::class); - function testRegistrationOfValidatorClasses() - { - $this->ruleFactory->register('even', '\Sirius\Validation\TestingCustomRule'); + $validator = $this->ruleFactory->createRule('even'); + expect($validator instanceof TestingCustomRule)->toBeTrue(); + expect($validator->validate(3))->toBeTrue(); + expect($validator->validate(4))->toBeFalse(); + expect((string)$validator->getMessage())->toEqual('Value is not valid'); +}); - $validator = $this->ruleFactory->createRule('even'); - $this->assertTrue($validator instanceof TestingCustomRule); - $this->assertTrue($validator->validate(3)); - $this->assertFalse($validator->validate(4)); - $this->assertEquals('Value is not valid', (string)$validator->getMessage()); - } +test('custom error messages', function () { + $this->ruleFactory->register('even', TestingCustomRule::class, 'This should be even', + '{label} should be even'); - function testCustomErrorMessages() - { - $this->ruleFactory->register('even', '\Sirius\Validation\TestingCustomRule', 'This should be even', - '{label} should be even'); - - $validatorWithLabel = $this->ruleFactory->createRule('even', null, null, 'Number'); - $validatorWithLabel->validate(4); - $this->assertEquals('Number should be even', (string)$validatorWithLabel->getMessage()); - - $validator = $validator = $this->ruleFactory->createRule('even'); - $validator->validate(4); - $this->assertEquals('This should be even', (string)$validator->getMessage()); + $validatorWithLabel = $this->ruleFactory->createRule('even', null, null, 'Number'); + $validatorWithLabel->validate(4); + expect((string)$validatorWithLabel->getMessage())->toEqual('Number should be even'); - } -} + $validator = $this->ruleFactory->createRule('even'); + $validator->validate(4); + expect((string)$validator->getMessage())->toEqual('This should be even'); +}); diff --git a/tests/src/Util/ArrTest.php b/tests/src/Util/ArrTest.php index a6461a4..5df11eb 100644 --- a/tests/src/Util/ArrTest.php +++ b/tests/src/Util/ArrTest.php @@ -1,139 +1,104 @@ data = array( - 'name' => 'John Doe', - 'addresses' => array( - 'billing' => array( - 'street' => '1st Ave' - ), - 'shipping' => array( - 'street' => '1st Boulevar' - ) +beforeEach(function () { + $this->data = array( + 'name' => 'John Doe', + 'addresses' => array( + 'billing' => array( + 'street' => '1st Ave' + ), + 'shipping' => array( + 'street' => '1st Boulevar' ) - ); - } - + ) + ); +}); - function testOfArrayGetByPath() - { - $this->assertEquals(Arr::getByPath($this->data, 'name'), $this->data['name']); - $this->assertEquals( - Arr::getByPath($this->data, 'addresses[shipping][street]'), - $this->data['addresses']['shipping']['street'] - ); - $this->assertEquals(Arr::getByPath($this->data, 'email'), null); - $this->assertEquals(Arr::getByPath($this->data, 'address[shipping][street]'), null); - } +use \Sirius\Validation\Util\Arr; - function testOfArrayGetByPathRoot() - { - $this->assertEquals($this->data, Arr::getByPath($this->data)); - } +test('array get by path', function () { + expect($this->data['name'])->toEqual(Arr::getByPath($this->data, 'name')); + expect($this->data['addresses']['shipping']['street'])->toEqual(Arr::getByPath($this->data, 'addresses[shipping][street]')); + expect(null)->toEqual(Arr::getByPath($this->data, 'email')); + expect(null)->toEqual(Arr::getByPath($this->data, 'address[shipping][street]')); +}); - function testOfArraySetByPath() - { - $this->data = Arr::setBySelector($this->data, 'email', 'my@domain.com'); - $this->assertEquals(Arr::getByPath($this->data, 'email'), 'my@domain.com'); +test('array get by path root', function () { + expect(Arr::getByPath($this->data))->toEqual($this->data); +}); - $this->data = Arr::setBySelector($this->data, 'newsletters[offers]', true); - $this->assertEquals(Arr::getByPath($this->data, 'newsletters'), array('offers' => true)); - $this->data = Arr::setBySelector($this->data, 'addresses[*][state]', 'California'); - $this->assertEquals(Arr::getByPath($this->data, 'addresses[shipping][state]'), 'California'); - $this->assertEquals(Arr::getByPath($this->data, 'addresses[billing][state]'), 'California'); - } +test('array set by path', function () { + $this->data = Arr::setBySelector($this->data, 'email', 'my@domain.com'); + expect('my@domain.com')->toEqual(Arr::getByPath($this->data, 'email')); - function testOfArraySetBySelectorDoesNotOverwriteTheExistingValues() - { - $this->data = Arr::setBySelector($this->data, 'name', 'Jane Fonda'); - $this->assertEquals(Arr::getByPath($this->data, 'name'), 'John Doe'); - } + $this->data = Arr::setBySelector($this->data, 'newsletters[offers]', true); + expect(array( 'offers' => true ))->toEqual(Arr::getByPath($this->data, 'newsletters')); + $this->data = Arr::setBySelector($this->data, 'addresses[*][state]', 'California'); + expect('California')->toEqual(Arr::getByPath($this->data, 'addresses[shipping][state]')); + expect('California')->toEqual(Arr::getByPath($this->data, 'addresses[billing][state]')); +}); - function testOfArraySetBySelectorEnsuresDataIsArray() - { - $this->data = Arr::setBySelector('string', 'name', 'Jane Fonda'); - $this->assertEquals(Arr::getByPath($this->data, 'name'), 'Jane Fonda'); - } +test('array set by selector does not overwrite the existing values', function () { + $this->data = Arr::setBySelector($this->data, 'name', 'Jane Fonda'); + expect('John Doe')->toEqual(Arr::getByPath($this->data, 'name')); +}); - function testOfArrayGetBySelectorDeepSearch() - { - $arr = array( - 'people' => array( - array( - 'name' => 'John', - 'address' => array( - 'city' => 'New York' - ) - ), - array( - 'name' => 'Marry', - 'address' => array( - 'state' => 'California' - ) - ), - ) - ); - $this->assertEquals( +test('array get by selector deep search', function () { + $arr = array( + 'people' => array( array( - 'people[0][address][city]' => 'New York', - 'people[1][address][city]' => null - ), - Arr::getBySelector($arr, 'people[*][address][city]') - ); - } - - function testOfArrayGetBySelectorUsingPath() - { - $arr = array( - 'recipients' => array( - array('name' => 'John'), - array('name' => 'Marry', 'email' => 'marry@gmail.com') - ) - ); - $this->assertEquals( - array( - 'recipients[0][email]' => null + 'name' => 'John', + 'address' => array( + 'city' => 'New York' + ) ), - Arr::getBySelector($arr, 'recipients[0][email]') - ); - $this->assertEquals( array( - 'recipients[1][email]' => 'marry@gmail.com' + 'name' => 'Marry', + 'address' => array( + 'state' => 'California' + ) ), - Arr::getBySelector($arr, 'recipients[1][email]') - ); - } + ) + ); + expect(Arr::getBySelector($arr, 'people[*][address][city]'))->toEqual(array( + 'people[0][address][city]' => 'New York', + 'people[1][address][city]' => null + )); +}); - function testOfArrayGetBySelectorWithEndingSelector() - { - $arr = array( - 'lines' => array( - 'quantities' => array(1, 2, 3) - ) - ); - $this->assertEquals( - array( - 'lines[quantities][0]' => 1, - 'lines[quantities][1]' => 2, - 'lines[quantities][2]' => 3 - ), - Arr::getBySelector($arr, 'lines[quantities][*]') - ); - } +test('array get by selector using path', function () { + $arr = array( + 'recipients' => array( + array( 'name' => 'John' ), + array( 'name' => 'Marry', 'email' => 'marry@gmail.com' ) + ) + ); + expect(Arr::getBySelector($arr, 'recipients[0][email]'))->toEqual(array( + 'recipients[0][email]' => null + )); + expect(Arr::getBySelector($arr, 'recipients[1][email]'))->toEqual(array( + 'recipients[1][email]' => 'marry@gmail.com' + )); +}); - function testOfArrayGetBySelectorWithWrongSelector() - { - $arr = array( - 'lines' => array( - 'quantities' => array(1, 2, 3) - ) - ); - $this->assertEquals(array(), Arr::getBySelector($arr, 'recipients[*]')); - } -} +test('array get by selector with ending selector', function () { + $arr = array( + 'lines' => array( + 'quantities' => array( 1, 2, 3 ) + ) + ); + expect(Arr::getBySelector($arr, 'lines[quantities][*]'))->toEqual(array( + 'lines[quantities][0]' => 1, + 'lines[quantities][1]' => 2, + 'lines[quantities][2]' => 3 + )); +}); + +test('array get by selector with wrong selector', function () { + $arr = array( + 'lines' => array( + 'quantities' => array( 1, 2, 3 ) + ) + ); + expect(Arr::getBySelector($arr, 'recipients[*]'))->toEqual(array()); +}); diff --git a/tests/src/ValidatorTest.php b/tests/src/ValidatorTest.php index 0de9540..3b98439 100755 --- a/tests/src/ValidatorTest.php +++ b/tests/src/ValidatorTest.php @@ -1,6 +1,8 @@ validator = new Validator(new RuleFactory, new ErrorMessage); +}); + +test('if messages can be set and cleared', function () { + expect(count($this->validator->getMessages()))->toEqual(0); + + // add empty message does nothing + $this->validator->addMessage('field_1'); + expect(count($this->validator->getMessages()))->toEqual(0); + + $this->validator->addMessage('field_1', 'Field is required'); + $this->validator->addMessage('field_2', 'Field should be an email'); + expect(count($this->validator->getMessages()))->toEqual(2); + + $this->validator->clearMessages('field_1'); + expect(count($this->validator->getMessages()))->toEqual(1); + $this->validator->clearMessages(); + expect(count($this->validator->getMessages()))->toEqual(0); +}); + +test('exception thrown when the data is not an array', function () { + $this->expectException('InvalidArgumentException'); + $this->validator->validate('string'); + $this->validator->validate(false); +}); + +test('if validate executes', function () { + $this->validator + ->add('field_1', 'Required', null) + ->add('field_2', 'Email', null, 'This field should be an email'); + + expect($this->validator->validate( + array( + 'field_1' => 'exists', + 'field_2' => 'not' + ) + ))->toBeFalse(); + + $this->validator->validate( + array( + 'field_1' => 'exists', + 'field_2' => 'me@domain.com' + ) + ); + + // execute the validation again without data + $this->validator->validate(); + expect(count($this->validator->getMessages()))->toEqual(0); +}); + +test('if missing items validate against the required rule', function () { + $this->validator->add('item', 'required', null, 'This field is required'); + $this->validator->add('items[subitem]', 'required', null, 'This field is required'); + $this->validator->setData(array()); + $this->validator->validate(); + expect(array('This field is required'))->toEqual($this->validator->getMessages('item')); + expect(array('This field is required'))->toEqual($this->validator->getMessages('items[subitem]')); +}); + +test('different data formats', function () { + $this->validator->add('email', 'email'); + + // test array objects + $data = new \ArrayObject(array(), \ArrayObject::ARRAY_AS_PROPS); + $data->email = 'not_an_email'; + + $this->validator->validate($data); + expect(count($this->validator->getMessages('email')))->toEqual(1); + + // test objects with a 'toArray' method + $data = new FakeObject(); + $data->email = 'not_an_email'; + $this->validator->validate($data); + expect(count($this->validator->getMessages('email')))->toEqual(1); +}); + +test('if exception is thrown on invalid rules', function () { + $this->expectException('\InvalidArgumentException'); + $this->validator->add('random_string'); +}); + +test('adding multiple rules at once', function () { + $this->validator->add( + array( + 'item' => array( + 'required', + array('minlength', 'min=4', '{label} should have at least {min} characters', 'Item') + ), + 'itema' => array('required', 'minLength(min=8)', 'required'), + 'itemb' => 'required' + ) + ); + $this->validator->validate( + array( + 'item' => 'ab', + 'itema' => 'abc' + ) + ); + expect($this->validator->getMessages('item'))->toEqual(array('Item should have at least 4 characters')); + expect($this->validator->getMessages('itema'))->toEqual(array('This input should have at least 8 characters')); + expect($this->validator->getMessages('itemb'))->toEqual(array('This field is required')); +}); + +test('adding validation rules via strings without label arg', function () { + $this->validator + // mixed rules in 1 string + ->add('item:Item', 'required | minLength({"min":4})') + // validator options as a QUERY string + ->add('itema:Item', 'minLength', 'min=8') + // validator without options and custom message + ->add('itemb:Item B', 'required') + // validator with defaults + ->add('itemc', 'email'); + $this->validator->validate(array('item' => 'ab', 'itema' => 'abc', 'itemc' => 'abc')); + expect(array((string)$this->validator->getMessages('item')[0]))->toEqual(array('Item should have at least 4 characters')); + expect($this->validator->getMessages('itema'))->toEqual(array('Item should have at least 8 characters')); + expect($this->validator->getMessages('itemb'))->toEqual(array('Item B is required')); + expect($this->validator->getMessages('itemc'))->toEqual(array('This input must be a valid email address')); +}); + +test('adding validation rules via strings', function () { + $this->validator + // mixed rules in 1 string + ->add('item', 'required | minLength({"min":4})({label} should have at least {min} characters)(Item)') + // validator options as a QUERY string + ->add('itema', 'minLength', 'min=8', '{label} should have at least {min} characters', 'Item') + // validator without options and custom message + ->add('itemb', 'required()(Item B is required)') + // validator with defaults + ->add('itemc', 'email'); + $this->validator->validate(array('item' => 'ab', 'itema' => 'abc', 'itemc' => 'abc')); + expect($this->validator->getMessages('item'))->toEqual(array('Item should have at least 4 characters')); + expect($this->validator->getMessages('itema'))->toEqual(array('Item should have at least 8 characters')); + expect($this->validator->getMessages('itemb'))->toEqual(array('Item B is required')); + expect($this->validator->getMessages('itemc'))->toEqual(array('This input must be a valid email address')); +}); + +test('exception on invalid validator options', function () { + $this->expectException('\InvalidArgumentException'); + $this->validator->add('item', 'required', new \stdClass()); +}); + +function fakeValidationMethod($value) { + return false; +} - function setUp() - { - $this->validator = new Validator(new RuleFactory, new ErrorMessage); - } - - function testIfMessagesCanBeSetAndCleared() - { - $this->assertEquals(0, count($this->validator->getMessages())); - - // add empty message does nothing - $this->validator->addMessage('field_1'); - $this->assertEquals(0, count($this->validator->getMessages())); - - $this->validator->addMessage('field_1', 'Field is required'); - $this->validator->addMessage('field_2', 'Field should be an email'); - $this->assertEquals(2, count($this->validator->getMessages())); - - $this->validator->clearMessages('field_1'); - $this->assertEquals(1, count($this->validator->getMessages())); - $this->validator->clearMessages(); - $this->assertEquals(0, count($this->validator->getMessages())); - } - - function testExceptionThrownWhenTheDataIsNotAnArray() - { - $this->setExpectedException('InvalidArgumentException'); - $this->validator->validate('string'); - $this->validator->validate(false); - } - - function testIfValidateExecutes() - { - $this->validator - ->add('field_1', 'Required', null) - ->add('field_2', 'Email', null, 'This field should be an email'); - - $this->assertFalse( - $this->validator->validate( - array( - 'field_1' => 'exists', - 'field_2' => 'not' - ) - ) - ); - - $this->validator->validate( - array( - 'field_1' => 'exists', - 'field_2' => 'me@domain.com' - ) - ); - - // execute the validation again without data - $this->validator->validate(); - $this->assertEquals(0, count($this->validator->getMessages())); - - } - - function testIfMissingItemsValidateAgainstTheRequiredRule() - { - $this->validator->add('item', 'required', null, 'This field is required'); - $this->validator->add('items[subitem]', 'required', null, 'This field is required'); - $this->validator->setData(array()); - $this->validator->validate(); - $this->assertEquals($this->validator->getMessages('item'), array('This field is required')); - $this->assertEquals($this->validator->getMessages('items[subitem]'), array('This field is required')); - } - - function testDifferentDataFormats() - { - $this->validator->add('email', 'email'); - - // test array objects - $data = new \ArrayObject(array(), \ArrayObject::ARRAY_AS_PROPS); - $data->email = 'not_an_email'; - - $this->validator->validate($data); - $this->assertEquals(1, count($this->validator->getMessages('email'))); - - // test objects with a 'toArray' method - $data = new FakeObject(); - $data->email = 'not_an_email'; - $this->validator->validate($data); - $this->assertEquals(1, count($this->validator->getMessages('email'))); - } - - function testIfExceptionIsThrownOnInvalidRules() - { - $this->setExpectedException('\InvalidArgumentException'); - $this->validator->add('random_string'); - } - - function testAddingMultipleRulesAtOnce() - { - $this->validator->add( - array( - 'item' => array( - 'required', - array('minlength', 'min=4', '{label} should have at least {min} characters', 'Item') - ), - 'itema' => array('required', 'minLength(min=8)', 'required'), - 'itemb' => 'required' - ) - ); - $this->validator->validate( - array( - 'item' => 'ab', - 'itema' => 'abc' - ) - ); - $this->assertEquals(array('Item should have at least 4 characters'), $this->validator->getMessages('item')); - $this->assertEquals( - array('This input should have at least 8 characters'), - $this->validator->getMessages('itema') - ); - $this->assertEquals(array('This field is required'), $this->validator->getMessages('itemb')); - } - - function testAddingValidationRulesViaStrings() - { - $this->validator - // mixed rules in 1 string - ->add('item', 'required | minLength({"min":4})({label} should have at least {min} characters)(Item)') - // validator options as a QUERY string - ->add('itema', 'minLength', 'min=8', '{label} should have at least {min} characters', 'Item') - // validator without options and custom message - ->add('itemb', 'required()(Item B is required)') - // validator with defaults - ->add('itemc', 'email'); - $this->validator->validate(array('item' => 'ab', 'itema' => 'abc', 'itemc' => 'abc')); - $this->assertEquals(array('Item should have at least 4 characters'), $this->validator->getMessages('item')); - $this->assertEquals(array('Item should have at least 8 characters'), $this->validator->getMessages('itema')); - $this->assertEquals(array('Item B is required'), $this->validator->getMessages('itemb')); - $this->assertEquals(array('This input must be a valid email address'), $this->validator->getMessages('itemc')); - } - - function testExceptionOnInvalidValidatorOptions() - { - $this->setExpectedException('\InvalidArgumentException'); - $this->validator->add('item', 'required', new \stdClass()); - } - - function fakeValidationMethod($value) - { - return false; - } - - static function fakeStaticValidationMethod($value, $return = false) - { - return $return; - } - - - function testCallbackValidators() - { - $this->validator->add('function', __NAMESPACE__ . '\fakeValidationFunction'); - $this->validator->add('method', array($this, 'fakeValidationMethod')); - $this->validator->add( - 'staticMethod', - array(__CLASS__, 'fakeStaticValidationMethod'), - array(true) - ); // this will return true - - $this->validator->validate( - array( - 'function' => true, - 'method' => true, - 'staticMethod' => true, - ) - ); - $this->assertEquals(2, count($this->validator->getMessages())); - } - - function testRemovingValidationRules() - { - $this->validator->add('item', 'required'); - $this->assertFalse($this->validator->validate(array())); - - $this->validator->remove('item', 'required'); - $this->assertTrue($this->validator->validate(array())); - } - - function testRemovingAllValidationRules() - { - $this->validator->remove('item', true); - $this->validator->add('item', 'required'); - $this->validator->add('item', 'email'); - $this->validator->setData(array()); - $this->assertFalse($this->validator->validate()); - - $this->validator->remove('item', true); - $rules = $this->validator->getRules(); - $this->assertEquals(count($rules['item']->getRules()), 0); - $this->assertTrue($this->validator->validate(array())); - } +function fakeStaticValidationMethod($value, $return = false) +{ + return $return; +} - function testMatchingRules() - { - $this->validator - ->add('items[*][key]', 'email', null, 'Key must be an email'); - $this->validator->validate( - array( - 'items' => array( - array('key' => 'sss'), - array('key' => 'sss') - ) +test('callback validators', function () { + $this->validator->add('function', 'fakeValidationFunction'); + + // this will return true + $this->validator->validate( + array( + 'function' => true, + ) + ); + expect(count($this->validator->getMessages()))->toEqual(1); +}); + +test('removing validation rules', function () { + $this->validator->add('item', 'required'); + expect($this->validator->validate(array()))->toBeFalse(); + + $this->validator->remove('item', 'required'); + expect($this->validator->validate(array()))->toBeTrue(); +}); + +test('removing all validation rules', function () { + $this->validator->remove('item', true); + $this->validator->add('item', 'required'); + $this->validator->add('item', 'email'); + $this->validator->setData(array()); + expect($this->validator->validate())->toBeFalse(); + + $this->validator->remove('item', true); + $rules = $this->validator->getRules(); + expect(0)->toEqual(count($rules['item']->getRules())); + expect($this->validator->validate(array()))->toBeTrue(); +}); + +test('matching rules', function () { + $this->validator + ->add('items[*][key]', 'email', null, 'Key must be an email'); + $this->validator->validate( + array( + 'items' => array( + array('key' => 'sss'), + array('key' => 'sss') ) - ); - $this->assertEquals(array('Key must be an email'), $this->validator->getMessages('items[0][key]')); - $this->assertEquals(array('Key must be an email'), $this->validator->getMessages('items[1][key]')); - } - - function testIfParametersAreSentToValidationMethods() - { - $this->validator - ->add('a', 'email', array(0, 1), 'This should be an email') - ->add('b', 'email', array(0, 1, 2), 'This should be an email') - ->add('c', 'email', array(0, 1, 2, 3), 'This should be an email'); - $this->validator->validate(array('a' => 'a', 'b' => 'b', 'c' => 'c')); - $messages = $this->validator->getMessages(); - foreach (array('a', 'b', 'c') as $k) { - $this->assertEquals(1, count($messages[$k])); - } - } - - function testIfExceptionIsThrownForInvalidValidationMethods() - { - $this->setExpectedException('\InvalidArgumentException'); - $this->validator->add('item', 'faker'); - $this->validator->validate(array('item' => true)); - } - -} + ) + ); + expect($this->validator->getMessages('items[0][key]'))->toEqual(array('Key must be an email')); + expect($this->validator->getMessages('items[1][key]'))->toEqual(array('Key must be an email')); +}); + +test('if parameters are sent to validation methods', function () { + $this->validator + ->add('a', 'email', array(0, 1), 'This should be an email') + ->add('b', 'email', array(0, 1, 2), 'This should be an email') + ->add('c', 'email', array(0, 1, 2, 3), 'This should be an email'); + $this->validator->validate(array('a' => 'a', 'b' => 'b', 'c' => 'c')); + $messages = $this->validator->getMessages(); + foreach (array('a', 'b', 'c') as $k) { + expect(count($messages[$k]))->toEqual(1); + } +}); + +test('if exception is thrown for invalid validation methods', function () { + $this->expectException('\InvalidArgumentException'); + $this->validator->add('item', 'faker'); + $this->validator->validate(array('item' => true)); +}); + +test('empty array validation', function () { + $this->validator->add(array( + 'a' => array('required'), + 'b' => array('required') + )); + $this->validator->validate(array()); + expect(count($this->validator->getMessages()))->toEqual(2); +}); + +test('validation require conditional', function () { + $this->validator->add(array( + 'a' => array('number', 'requiredWith(b)'), + 'b' => array('number', 'requiredWith(a)') + )); + expect($this->validator->validate(array()))->toBeTrue(); +}); diff --git a/tests/src/ValueValidatorTest.php b/tests/src/ValueValidatorTest.php index 33007d3..08aed6e 100644 --- a/tests/src/ValueValidatorTest.php +++ b/tests/src/ValueValidatorTest.php @@ -1,67 +1,80 @@ validator = new ValueValidator(); - } +use \Sirius\Validation\ValueValidator; - function testAddingValidationRulesRegularly() - { - $this->validator->add('required')->add('minlength', '{"min":4}', - '{label} should have at least {min} characters', 'Item'); - $this->validator->validate('ab'); - $this->assertEquals(array( - 'Item should have at least 4 characters' - ), $this->validator->getMessages()); - } +beforeEach(function () { + $this->validator = new ValueValidator(); +}); - function testAddingValidationRulesViaStrings() - { - $this->validator->add('required | minlength({"min":4})({label} should have at least {min} characters)(Item)'); - $this->validator->validate('ab'); - $this->assertEquals(array( - 'Item should have at least 4 characters' - ), $this->validator->getMessages()); - } +test('adding validation rules regularly', function () { + $this->validator->add('required')->add('minlength', '{"min":4}', + '{label} should have at least {min} characters', 'Item'); + $this->validator->validate('ab'); + expect($this->validator->getMessages())->toEqual(array( + 'Item should have at least 4 characters' + )); +}); - function testRemovingValidationRules() - { - $this->validator->add('required'); - $this->assertFalse($this->validator->validate(null)); - $this->validator->remove('required'); - $this->assertTrue($this->validator->validate(null)); - } +test('adding validation rules via strings', function () { + $this->validator->add('required | minlength({"min":4})({label} should have at least {min} characters)(Item)'); + $this->validator->validate('ab'); + expect($this->validator->getMessages())->toEqual(array( + 'Item should have at least 4 characters' + )); +}); - function testRemovingAllRules() - { - $this->validator->add('required')->add('minlength', '{"min":4}', - '{label} should have at least {min} characters', 'Item'); - $this->validator->validate('ab'); - $this->assertEquals(array( - 'Item should have at least 4 characters' - ), $this->validator->getMessages()); - $this->validator->remove(true); - $this->assertTrue($this->validator->validate(null)); - } +test('removing validation rules', function () { + $this->validator->add('required'); + expect($this->validator->validate(null))->toBeFalse(); + $this->validator->remove('required'); + expect($this->validator->validate(null))->toBeTrue(); +}); - function testNonRequiredRules() - { - $this->validator->add('email'); - $this->assertTrue($this->validator->validate(null)); - } +test('removing all rules', function () { + $this->validator->add('required')->add('minlength', '{"min":4}', + '{label} should have at least {min} characters', 'Item'); + $this->validator->validate('ab'); + expect($this->validator->getMessages())->toEqual(array( + 'Item should have at least 4 characters' + )); + $this->validator->remove(true); + expect($this->validator->validate(null))->toBeTrue(); +}); + +test('non required rules', function () { + $this->validator->add('email'); + expect($this->validator->validate(null))->toBeTrue(); + expect($this->validator->validate(''))->toBeTrue(); +}); - function testDefaultLabel() - { - $this->validator->setLabel('Item'); - $this->validator->add('required')->add('minlength', '{"min":4}', - '{label} should have at least {min} characters'); - $this->validator->validate('ab'); - $this->assertEquals(array( - 'Item should have at least 4 characters' - ), $this->validator->getMessages()); +test('default label', function () { + $this->validator->setLabel('Item'); + $this->validator->add('required')->add('minlength', '{"min":4}', + '{label} should have at least {min} characters'); + $this->validator->validate('ab'); + expect($this->validator->getMessages())->toEqual(array( + 'Item should have at least 4 characters' + )); +}); + +test('parse rule with zero value in csv format', function () { + $this->validator->add('GreaterThan(0)'); + + /** @var GreaterThan $rule */ + foreach ($this->validator->getRules() as $rule) { + break; } -} + + expect($rule->getOption('min'))->toBe('0'); + + $this->validator->validate(1); + expect($this->validator->getMessages())->toBeEmpty(); + + $this->validator->validate(0); + expect($this->validator->getMessages())->toBeEmpty(); + + $this->validator->validate(-1); + expect($this->validator->getMessages())->not->toBeEmpty(); +}); diff --git a/tools/php-cs-fixer/composer.json b/tools/php-cs-fixer/composer.json new file mode 100644 index 0000000..bc1aa7b --- /dev/null +++ b/tools/php-cs-fixer/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "friendsofphp/php-cs-fixer": "^3.34" + } +}