diff --git a/.coveralls.yml b/.coveralls.yml deleted file mode 100644 index 4eecff5..0000000 --- a/.coveralls.yml +++ /dev/null @@ -1,3 +0,0 @@ -service_name: travis-ci -coverage_clover: clover.xml -json_path: coveralls-upload.json diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 54655f5..481d73a 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] + php-versions: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index 3d2d9e8..0000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,31 +0,0 @@ -filter: - excluded_paths: - - 'vendor/*' -before_commands: - - 'composer install' -build: - nodes: - analysis: - tests: - override: - - php-scrutinizer-run - tests: - override: - - phpcs-run --standard=PSR2 -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(< C).new.exists' - - 'issues.label("coding-style").new.exists' - - 'issues.severity(>= MAJOR).new.exists' - - 'project.metric("scrutinizer.quality", < 6.8)' diff --git a/README.md b/README.md index 47ad102..359fb27 100644 --- a/README.md +++ b/README.md @@ -330,6 +330,52 @@ $specification = [ ]; ``` +## requires + +#### Summary + +Defines any input field(s) which are required by a separate given field. Used when one field requires one or more additional fields to be operate properly. + +#### Types + +* string||string[] + +#### Default + +There is no default value for this option. + +#### Constant + +```php +TraderInteractive\FilterOptions::REQUIRES +``` + +#### Examples + +```php +$specification = [ + 'duration' => [['uint']], + 'startDate' => [ + TraderInteractive\FilterOptions::REQUIRES => 'duration', + ['date'], + ], +]; +``` + +```php +$specification = [ + 'id' => [['string']], + 'typeId' => [ + TraderInteractive\FilterOptions::REQUIRES => ['typeCode'], + ['uint'], + ], + 'typeCode' => [ + TraderInteractive\FilterOptions::REQUIRES => ['typeId'], + ['string'], + ], +]; +``` + ## uses #### Summary diff --git a/composer.json b/composer.json index d029fb9..049a928 100644 --- a/composer.json +++ b/composer.json @@ -40,8 +40,7 @@ }, "require-dev": { "phpunit/phpunit": "^9.0", - "squizlabs/php_codesniffer": "^3.2", - "symfony/yaml": "^3.4" + "squizlabs/php_codesniffer": "^3.2" }, "autoload": { "psr-4": { "TraderInteractive\\": "src/" } diff --git a/src/FilterOptions.php b/src/FilterOptions.php index 17ad992..b6161ad 100644 --- a/src/FilterOptions.php +++ b/src/FilterOptions.php @@ -24,6 +24,11 @@ final class FilterOptions */ const IS_REQUIRED = 'required'; + /** + * @var string + */ + const REQUIRES = 'requires'; + /** * @var string */ diff --git a/src/Filterer.php b/src/Filterer.php index e9ded47..229f857 100644 --- a/src/Filterer.php +++ b/src/Filterer.php @@ -161,6 +161,7 @@ public function execute(array $input) : FilterResponse $filteredInput = []; $errors = []; $conflicts = []; + $requires = []; foreach ($inputToFilter as $field => $input) { $filters = $this->specification[$field]; self::assertFiltersIsAnArray($filters, $field); @@ -170,6 +171,7 @@ public function execute(array $input) : FilterResponse unset($filters[FilterOptions::IS_REQUIRED]);//doesn't matter if required since we have this one unset($filters[FilterOptions::DEFAULT_VALUE]);//doesn't matter if there is a default since we have a value $conflicts = self::extractConflicts($filters, $field, $conflicts); + $requires = self::extractRequires($filters, $field, $requires); foreach ($filters as $filter) { self::assertFilterIsArray($filter, $field); @@ -218,6 +220,7 @@ public function execute(array $input) : FilterResponse $errors = self::handleAllowUnknowns($this->allowUnknowns, $leftOverInput, $errors); $errors = self::handleConflicts($filteredInput, $conflicts, $errors); + $errors = self::handleRequires($filteredInput, $requires, $errors); return new FilterResponse($filteredInput, $errors, $leftOverInput); } @@ -249,6 +252,41 @@ private static function extractConflicts(array &$filters, string $field, array $ return $conflicts; } + private static function extractRequires(array &$filters, string $field, array $requires): array + { + $required = $filters[FilterOptions::REQUIRES] ?? null; + unset($filters[FilterOptions::REQUIRES]); + if ($required === null) { + return $requires; + } + + if (!is_array($required)) { + $required = [$required]; + } + + $requires[$field] = $required; + + return $requires; + } + + private static function handleRequires(array $inputToFilter, array $requires, array $errors) + { + foreach (array_keys($inputToFilter) as $field) { + if (!array_key_exists($field, $requires)) { + continue; + } + + + foreach ($requires[$field] as $requiredField) { + if (!array_key_exists($requiredField, $inputToFilter)) { + $errors[] = "Field '{$field}' requires field '{$requiredField}' but it is not present."; + } + } + } + + return $errors; + } + private static function handleConflicts(array $inputToFilter, array $conflicts, array $errors) { foreach (array_keys($inputToFilter) as $field) { diff --git a/tests/FiltererTest.php b/tests/FiltererTest.php index 07280a2..fa2c43a 100644 --- a/tests/FiltererTest.php +++ b/tests/FiltererTest.php @@ -265,6 +265,70 @@ public function provideValidFilterData() : array 'options' => [], 'result' => [true, ['field' => 'a string with newlines and extra spaces'], null, []], ], + 'requires single' => [ + 'spec' => [ + 'fieldOne' => [FilterOptions::REQUIRES => 'fieldThree', ['string']], + 'fieldTwo' => [['string']], + 'fieldThree' => [['string']], + ], + 'input' => [ + 'fieldOne' => 'abc', + 'fieldTwo' => '123', + ], + 'options' => [], + 'result' => [ + false, + null, + "Field 'fieldOne' requires field 'fieldThree' but it is not present.", + [], + ], + ], + 'requires multiple' => [ + 'spec' => [ + 'fieldOne' => [FilterOptions::REQUIRES => ['fieldThree', 'fieldFour'], ['string']], + 'fieldTwo' => [['string']], + 'fieldThree' => [['string']], + 'fieldFour' => [['uint']], + ], + 'input' => [ + 'fieldOne' => 'abc', + 'fieldTwo' => '123', + ], + 'options' => [], + 'result' => [ + false, + null, + "Field 'fieldOne' requires field 'fieldThree' but it is not present.\n" + . "Field 'fieldOne' requires field 'fieldFour' but it is not present.", + [], + ], + ], + 'requires multiple all present' => [ + 'spec' => [ + 'fieldOne' => [FilterOptions::REQUIRES => ['fieldThree', 'fieldFour'], ['string']], + 'fieldTwo' => [['string']], + 'fieldThree' => [['string']], + 'fieldFour' => [['uint']], + ], + 'input' => [ + 'fieldOne' => 'abc', + 'fieldTwo' => '123', + 'fieldThree' => 'foo', + 'fieldFour' => '789', + ], + 'options' => [], + 'result' => [ + true, + [ + 'fieldOne' => 'abc', + 'fieldTwo' => '123', + 'fieldThree' => 'foo', + 'fieldFour' => 789, + ], + null, + [], + ], + ], 'conflicts with single' => [ 'spec' => [ 'fieldOne' => [FilterOptions::CONFLICTS_WITH => 'fieldThree', ['string']],