diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..4eecff5 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,3 @@ +service_name: travis-ci +coverage_clover: clover.xml +json_path: coveralls-upload.json diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..932c4ba --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +# Enforce Unix newlines +* text=lf + +# Exclude unused files +# see: https://redd.it/2jzp6k +/tests export-ignore +/.github export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.*.yml export-ignore +/phpcs.xml export-ignore +/phpunit.xml.dist export-ignore +/README.md export-ignore diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..9a4b075 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @traderinteractive/opensource diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..69512db --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,24 @@ +# Contribution Guidelines +We welcome you to report [issues](/../../issues) or submit [pull requests](/../../pulls). While the below guidelines are necessary to get code merged, you can +submit pull requests that do not adhere to them and we will try to take care of them in our spare time. We are a smallish group of developers, +though, so if you can make sure the build is passing 100%, that would be very useful. + +We recommend including details of your particular usecase(s) with any issues or pull requests. We love to hear how our libraries are being used +and we can get things merged in quicker when we understand its expected usage. + +## Pull Requests +Code changes should be sent through [GitHub Pull Requests](/../../pulls). Before submitting the pull request, make sure that phpunit reports success +by running: +```sh +./vendor/bin/phpunit +``` +And there are not coding standard violations by running +```sh +./vendor/bin/phpcs +``` + +## Builds +Our [Travis build](https://travis-ci.org/traderinteractive/filter-ints-php) executes [PHPUnit](http://www.phpunit.de) and uses [Coveralls](https://coveralls.io/) to enforce code coverage. +While the build does not strictly enforce 100% code coverage, it will not allow coverage to drop below its current percentage. +[Scrutinizer](https://scrutinizer-ci.com/) is used to ensure code quality and enforce the [coding standard](http://www.php-fig.org/psr/psr-2/). + diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..4eef22e --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,6 @@ +## Expected Behavior + +## Actual Behavior + +## Steps to reproduce the behavior + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c0d57b0 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +Fixes # . + +#### What does this PR do? + +#### Checklist +- [ ] Pull request contains a clear definition of changes +- [ ] Tests (either unit, integration, or acceptance) written and passing +- [ ] Relevant documentation produced and/or updated + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3be34b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/coverage/ +/vendor/ +/clover.xml +phpunit.xml +composer.lock diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..ef22543 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,22 @@ +filter: + excluded_paths: + - 'vendor/*' +before_commands: + - 'composer install' +tools: + php_analyzer: true + php_mess_detector: true + php_code_sniffer: + config: + standard: PSR2 + sensiolabs_security_checker: true + php_loc: + excluded_dirs: + - vendor + php_pdepend: true + php_sim: true +build_failure_conditions: + - 'elements.rating(< A).exists' + - 'issues.label("coding-style").exists' + - 'issues.severity(>= MAJOR).exists' + - 'project.metric("scrutinizer.quality", < 9)' diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..36bdffb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +sudo: false +language: php +php: + - 7.0 + - 7.1 + - 7.2 + - nightly +env: + - PREFER_LOWEST="--prefer-lowest --prefer-stable" + - PREFER_LOWEST="" +matrix: + fast_finish: true + allow_failures: + - php: nightly +before_script: + - composer update $PREFER_LOWEST +script: ./vendor/bin/phpunit --coverage-clover clover.xml +after_success: ./vendor/bin/coveralls -v diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a7a5819 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017 Trader Interactive + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 40f08b0..10fedfa 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,64 @@ # filter-ints-php -A filtering implementation for verifying correct data and performing typical modifications to integers. + +[![Build Status](https://travis-ci.org/traderinteractive/filter-ints-php.svg?branch=master)](https://travis-ci.org/traderinteractive/filter-ints-php) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/traderinteractive/filter-ints-php/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/traderinteractive/filter-ints-php/?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/traderinteractive/filter-ints-php/badge.svg?branch=master)](https://coveralls.io/github/traderinteractive/filter-ints-php?branch=master) + +[![Latest Stable Version](https://poser.pugx.org/traderinteractive/filter-ints/v/stable)](https://packagist.org/packages/traderinteractive/filter-ints) +[![Latest Unstable Version](https://poser.pugx.org/traderinteractive/filter-ints/v/unstable)](https://packagist.org/packages/traderinteractive/filter-ints) +[![License](https://poser.pugx.org/traderinteractive/filter-ints/license)](https://packagist.org/packages/traderinteractive/filter-ints) + +[![Total Downloads](https://poser.pugx.org/traderinteractive/filter-ints/downloads)](https://packagist.org/packages/traderinteractive/filter-ints) +[![Daily Downloads](https://poser.pugx.org/traderinteractive/filter-ints/d/daily)](https://packagist.org/packages/traderinteractive/filter-ints) +[![Monthly Downloads](https://poser.pugx.org/traderinteractive/filter-ints/d/monthly)](https://packagist.org/packages/traderinteractive/filter-ints) + +A filtering implementation for verifying correct data and performing typical modifications to data. + +## Requirements + +Requires PHP 7.0 or newer and uses composer to install further PHP dependencies. See the [composer specification](composer.json) for more details. + +## Composer + +To add the library as a local, per-project dependency use [Composer](http://getcomposer.org)! Simply add a dependency on +`traderinteractive/filter-ints` to your project's `composer.json` file such as: + +```sh +composer require traderinteractive/filter-ints +``` + +### Functionality + +#### Ints/UnsignedInt::filter + +These filters verify that the arguments are of the proper numeric type and +allow for bounds checking. The second parameter to each of them can be set to `true` to allow null values through without an error (they will +stay null and not get converted to false). The next two parameters are the min and max bounds and can be used to limit the domain of allowed +numbers. + +Non-numeric strings will fail validation, and numeric strings will be cast. + +The following checks that `$value` is an integer between 1 and 100 inclusive, and returns the integer (after casting it if it was a string). + +```php +$value = \TraderInteractive\Filter\UnsignedInt::filter($value, false, 1, 100); +``` + +## Contact + +Developers may be contacted at: + + * [Pull Requests](https://github.com/traderinteractive/filter-ints-php/pulls) + * [Issues](https://github.com/traderinteractive/filter-ints-php/issues) + +## Project Build + +With a checkout of the code get [Composer](http://getcomposer.org) in your PATH and run: + +```bash +composer install +./vendor/bin/phpcs +./vendor/bin/phpunit +``` + +For more information on our build process, read through out our [Contribution Guidelines](CONTRIBUTING.md). diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..3b5719b --- /dev/null +++ b/composer.json @@ -0,0 +1,27 @@ +{ + "name": "traderinteractive/filter-ints", + "description": "A filtering implementation for verifying correct data and performing typical modifications to data", + "keywords": [ + "integer", + "ints", + "int", + "unsigned", + "uint" + ], + "config": { + "sort-packages": true + }, + "license": "MIT", + "require": { + "php": "^7.0", + "traderinteractive/exceptions": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^1.0", + "phpunit/phpunit": "^6.0", + "squizlabs/php_codesniffer": "^3.2" + }, + "autoload": { + "psr-4": { "TraderInteractive\\": "src/" } + } +} diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..449f1e8 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,8 @@ + + + . + */vendor/* + + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..ba76cec --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,10 @@ + + + ./tests + + + + src + + + diff --git a/src/Filter/Ints.php b/src/Filter/Ints.php new file mode 100644 index 0000000..affdce6 --- /dev/null +++ b/src/Filter/Ints.php @@ -0,0 +1,144 @@ + $maxValue) { + throw new FilterException("{$valueInt} is greater than {$maxValue}"); + } + } + + private static function enforcePhpIntMax(string $value, int $casted) + { + if ($casted === PHP_INT_MAX && $value !== (string)PHP_INT_MAX) { + throw new FilterException("{$value} was greater than a max int of " . PHP_INT_MAX); + } + } + + private static function enforcePhpIntMin(string $value, int $casted, int $phpIntMin) + { + if ($casted === $phpIntMin && $value !== (string)$phpIntMin) { + throw new FilterException("{$value} was less than a min int of {$phpIntMin}"); + } + } +} diff --git a/src/Filter/UnsignedInt.php b/src/Filter/UnsignedInt.php new file mode 100644 index 0000000..a39a3ac --- /dev/null +++ b/src/Filter/UnsignedInt.php @@ -0,0 +1,44 @@ + + */ +final class IntsTest extends TestCase +{ + /** + * @test + * @covers ::filter + */ + public function filterAllowNullIsTrueAndNullValue() + { + $result = Ints::filter(null, true); + $this->assertSame(null, $result); + } + + /** + * @test + * @covers ::filter + */ + public function filterPositiveInt() + { + $this->assertSame(123, Ints::filter(123)); + } + + /** + * @test + * @covers ::filter + */ + public function filterNegativeInt() + { + $this->assertSame(-123, Ints::filter(-123)); + } + + /** + * @test + * @covers ::filter + */ + public function filterZeroInt() + { + $positiveZero = + 0; + $this->assertSame(0, Ints::filter($positiveZero)); + $this->assertSame(0, Ints::filter(-0)); + } + + /** + * @test + * @covers ::filter + */ + public function filterPositiveString() + { + $this->assertSame(123, Ints::filter(' 123 ')); + $this->assertSame(123, Ints::filter(' +123 ')); + $this->assertSame(0, Ints::filter(' +0 ')); + } + + /** + * @test + * @covers ::filter + */ + public function filterNegativeString() + { + $this->assertSame(-123, Ints::filter(' -123 ')); + $this->assertSame(0, Ints::filter(' -0 ')); + } + + /** + * @test + * @covers ::filter + * @expectedException \TraderInteractive\Exceptions\FilterException + * @expectedExceptionMessage "true" $value is not a string + */ + public function filterNonStringOrInt() + { + Ints::filter(true); + } + + /** + * @test + * @covers ::filter + * @expectedException \TraderInteractive\Exceptions\FilterException + * @expectedExceptionMessage $value string length is zero + */ + public function filterEmptyString() + { + Ints::filter(''); + } + + /** + * @test + * @covers ::filter + * @expectedException \TraderInteractive\Exceptions\FilterException + * @expectedExceptionMessage $value string length is zero + */ + public function filterWhitespaceString() + { + Ints::filter(' '); + } + + /** + * @test + * @covers ::filter + */ + public function nonDigitString() + { + try { + Ints::filter('123.4'); + $this->fail("No exception thrown"); + } catch (\Throwable $e) { + $this->assertSame( + "123.4 does not contain all digits, optionally prepended by a '+' or '-' and optionally surrounded by " + . "whitespace", + $e->getMessage() + ); + } + } + + /** + * @test + * @covers ::filter + * @expectedException \TraderInteractive\Exceptions\FilterException + */ + public function filterGreaterThanPhpIntMax() + { + //32, 64 and 128 bit and their +1 's + $maxes = [ + '2147483647' => '2147483648', + '9223372036854775807' => '9223372036854775808', + '170141183460469231731687303715884105727' => '170141183460469231731687303715884105728', + ]; + $oneOverMax = $maxes[(string)PHP_INT_MAX]; + Ints::filter($oneOverMax); + } + + /** + * @test + * @covers ::filter + * @expectedException \TraderInteractive\Exceptions\FilterException + */ + public function filterLessThanPhpIntMin() + { + //32, 64 and 128 bit and their -1 's + $mins = [ + '-2147483648' => '-2147483649', + '-9223372036854775808' => '-9223372036854775809', + '-170141183460469231731687303715884105728' => '-170141183460469231731687303715884105729', + ]; + $oneUnderMin = $mins[(string)~PHP_INT_MAX]; + Ints::filter($oneUnderMin); + } + + /** + * @test + * @covers ::filter + * @expectedException \TraderInteractive\Exceptions\FilterException + * @expectedExceptionMessage -1 is less than 0 + */ + public function filterLessThanMin() + { + Ints::filter(-1, false, 0); + } + + /** + * @test + * @covers ::filter + */ + public function filterEqualToMin() + { + $this->assertSame(0, Ints::filter(0, false, 0)); + } + + /** + * @test + * @covers ::filter + * @expectedException \TraderInteractive\Exceptions\FilterException + * @expectedExceptionMessage 1 is greater than 0 + */ + public function filterGreaterThanMax() + { + Ints::filter(1, false, null, 0); + } + + /** + * @test + * @covers ::filter + */ + public function filterEqualToMax() + { + $this->assertSame(0, Ints::filter(0, false, null, 0)); + } +} diff --git a/tests/Filter/UnsignedIntTest.php b/tests/Filter/UnsignedIntTest.php new file mode 100644 index 0000000..eee038a --- /dev/null +++ b/tests/Filter/UnsignedIntTest.php @@ -0,0 +1,94 @@ + + */ +final class UnsignedIntTest extends TestCase +{ + /** + * @test + * @covers ::filter + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage -1 was not greater or equal to zero + */ + public function filterMinValueNegative() + { + UnsignedInt::filter('1', false, -1); + } + + /** + * @test + * @covers ::filter + */ + public function filterMinValueNullSuccess() + { + $this->assertSame(1, UnsignedInt::filter('1', false, null)); + } + + /** + * @test + * @covers ::filter + * @expectedException \TraderInteractive\Exceptions\FilterException + * @expectedExceptionMessage -1 is less than 0 + */ + public function filterMinValueNullFail() + { + UnsignedInt::filter('-1', false, null); + } + + /** + * @test + * @covers ::filter + */ + public function filterBasicUse() + { + $this->assertSame(123, UnsignedInt::filter('123')); + } + + /** + * @test + * @covers ::filter + */ + public function filterAllowNullSuccess() + { + $this->assertSame(null, UnsignedInt::filter(null, true)); + } + + /** + * @test + * @covers ::filter + * @expectedException \TraderInteractive\Exceptions\FilterException + * @expectedExceptionMessage Value failed filtering, $allowNull is set to false + */ + public function filterAllowNullFail() + { + UnsignedInt::filter(null, false); + } + + /** + * @test + * @covers ::filter + * @expectedException \TraderInteractive\Exceptions\FilterException + * @expectedExceptionMessage 0 is less than 1 + */ + public function filterMinValueFail() + { + $this->assertSame(1, UnsignedInt::filter('0', false, 1)); + } + + /** + * @test + * @covers ::filter + * @expectedException \TraderInteractive\Exceptions\FilterException + * @expectedExceptionMessage 2 is greater than 1 + */ + public function filterMaxValueFail() + { + UnsignedInt::filter('2', false, 0, 1); + } +}