@@ -46,9 +46,9 @@ final class Filterer implements FiltererInterface
4646 * @var array
4747 */
4848 const DEFAULT_OPTIONS = [
49- ' allowUnknowns ' => false ,
50- ' defaultRequired ' => false ,
51- ' responseType ' => self ::RESPONSE_TYPE_ARRAY ,
49+ FiltererOptions:: ALLOW_UNKNOWNS => false ,
50+ FiltererOptions:: DEFAULT_REQUIRED => false ,
51+ FiltererOptions:: RESPONSE_TYPE => self ::RESPONSE_TYPE_ARRAY ,
5252 ];
5353
5454 /**
@@ -123,12 +123,15 @@ public function execute(array $input) : FilterResponse
123123 $ leftOverInput = array_diff_key ($ input , $ this ->specification );
124124
125125 $ errors = [];
126+ $ conflicts = [];
126127 foreach ($ inputToFilter as $ field => $ input ) {
127128 $ filters = $ this ->specification [$ field ];
128129 self ::assertFiltersIsAnArray ($ filters , $ field );
129130 $ customError = self ::validateCustomError ($ filters , $ field );
130- unset($ filters ['required ' ]);//doesn't matter if required since we have this one
131- unset($ filters ['default ' ]);//doesn't matter if there is a default since we have a value
131+ unset($ filters [FilterOptions::IS_REQUIRED ]);//doesn't matter if required since we have this one
132+ unset($ filters [FilterOptions::DEFAULT_VALUE ]);//doesn't matter if there is a default since we have a value
133+ $ conflicts = self ::extractConflicts ($ filters , $ field , $ conflicts );
134+
132135 foreach ($ filters as $ filter ) {
133136 self ::assertFilterIsNotArray ($ filter , $ field );
134137
@@ -156,15 +159,16 @@ public function execute(array $input) : FilterResponse
156159 foreach ($ leftOverSpec as $ field => $ filters ) {
157160 self ::assertFiltersIsAnArray ($ filters , $ field );
158161 $ required = self ::getRequired ($ filters , $ this ->defaultRequired , $ field );
159- if (array_key_exists (' default ' , $ filters )) {
160- $ inputToFilter [$ field ] = $ filters [' default ' ];
162+ if (array_key_exists (FilterOptions:: DEFAULT_VALUE , $ filters )) {
163+ $ inputToFilter [$ field ] = $ filters [FilterOptions:: DEFAULT_VALUE ];
161164 continue ;
162165 }
163166
164167 $ errors = self ::handleRequiredFields ($ required , $ field , $ errors );
165168 }
166169
167170 $ errors = self ::handleAllowUnknowns ($ this ->allowUnknowns , $ leftOverInput , $ errors );
171+ $ errors = self ::handleConflicts ($ inputToFilter , $ conflicts , $ errors );
168172
169173 return new FilterResponse ($ inputToFilter , $ errors , $ leftOverInput );
170174 }
@@ -179,6 +183,40 @@ public function getAliases() : array
179183 return $ this ->filterAliases ?? self ::$ registeredFilterAliases ;
180184 }
181185
186+ private static function extractConflicts (array &$ filters , string $ field , array $ conflicts ) : array
187+ {
188+ $ conflictsWith = $ filters [FilterOptions::CONFLICTS_WITH ] ?? null ;
189+ unset($ filters [FilterOptions::CONFLICTS_WITH ]);
190+ if ($ conflictsWith === null ) {
191+ return $ conflicts ;
192+ }
193+
194+ if (!is_array ($ conflictsWith )) {
195+ $ conflictsWith = [$ conflictsWith ];
196+ }
197+
198+ $ conflicts [$ field ] = $ conflictsWith ;
199+
200+ return $ conflicts ;
201+ }
202+
203+ private static function handleConflicts (array $ inputToFilter , array $ conflicts , array $ errors )
204+ {
205+ foreach (array_keys ($ inputToFilter ) as $ field ) {
206+ if (!array_key_exists ($ field , $ conflicts )) {
207+ continue ;
208+ }
209+
210+ foreach ($ conflicts [$ field ] as $ conflictsWith ) {
211+ if (array_key_exists ($ conflictsWith , $ inputToFilter )) {
212+ $ errors [] = "Field ' {$ field }' cannot be given if field ' {$ conflictsWith }' is present. " ;
213+ }
214+ }
215+ }
216+
217+ return $ errors ;
218+ }
219+
182220 /**
183221 * @return array
184222 *
@@ -219,8 +257,8 @@ public function withSpecification(array $specification) : FiltererInterface
219257 private function getOptions () : array
220258 {
221259 return [
222- ' defaultRequired ' => $ this ->defaultRequired ,
223- ' allowUnknowns ' => $ this ->allowUnknowns ,
260+ FiltererOptions:: DEFAULT_REQUIRED => $ this ->defaultRequired ,
261+ FiltererOptions:: ALLOW_UNKNOWNS => $ this ->allowUnknowns ,
224262 ];
225263 }
226264
@@ -296,7 +334,7 @@ private function getOptions() : array
296334 public static function filter (array $ specification , array $ input , array $ options = [])
297335 {
298336 $ options += self ::DEFAULT_OPTIONS ;
299- $ responseType = $ options [' responseType ' ];
337+ $ responseType = $ options [FiltererOptions:: RESPONSE_TYPE ];
300338
301339 $ filterer = new Filterer ($ specification , $ options );
302340 $ filterResponse = $ filterer ->execute ($ input );
@@ -484,9 +522,11 @@ private static function handleRequiredFields(bool $required, string $field, arra
484522
485523 private static function getRequired ($ filters , $ defaultRequired , $ field ) : bool
486524 {
487- $ required = isset ( $ filters [' required ' ]) ? $ filters [ ' required ' ] : $ defaultRequired ;
525+ $ required = $ filters [FilterOptions:: IS_REQUIRED ] ?? $ defaultRequired ;
488526 if ($ required !== false && $ required !== true ) {
489- throw new InvalidArgumentException ("'required' for field ' {$ field }' was not a bool " );
527+ throw new InvalidArgumentException (
528+ sprintf ("'%s' for field '%s' was not a bool " , FilterOptions::IS_REQUIRED , $ field )
529+ );
490530 }
491531
492532 return $ required ;
@@ -508,11 +548,8 @@ private static function handleCustomError(
508548 ) : array {
509549 $ error = $ customError ;
510550 if ($ error === null ) {
511- $ error = sprintf (
512- "Field '%s' with value '{value}' failed filtering, message '%s' " ,
513- $ field ,
514- $ e ->getMessage ()
515- );
551+ $ errorFormat = "Field '%s' with value '{value}' failed filtering, message '%s' " ;
552+ $ error = sprintf ($ errorFormat , $ field , $ e ->getMessage ());
516553 }
517554
518555 $ errors [$ field ] = str_replace ('{value} ' , trim (var_export ($ value , true ), "' " ), $ error );
@@ -547,33 +584,37 @@ private static function assertFilterIsNotArray($filter, string $field)
547584 private static function validateCustomError (array &$ filters , string $ field )
548585 {
549586 $ customError = null ;
550- if (array_key_exists (' error ' , $ filters )) {
551- $ customError = $ filters [' error ' ];
587+ if (array_key_exists (FilterOptions:: CUSTOM_ERROR , $ filters )) {
588+ $ customError = $ filters [FilterOptions:: CUSTOM_ERROR ];
552589 if (!is_string ($ customError ) || trim ($ customError ) === '' ) {
553- throw new InvalidArgumentException ("error for field ' {$ field }' was not a non-empty string " );
590+ throw new InvalidArgumentException (
591+ sprintf ("%s for field '%s' was not a non-empty string " , FilterOptions::CUSTOM_ERROR , $ field )
592+ );
554593 }
555594
556- unset($ filters [' error ' ]);//unset so its not used as a filter
595+ unset($ filters [FilterOptions:: CUSTOM_ERROR ]);//unset so its not used as a filter
557596 }
558597
559598 return $ customError ;
560599 }
561600
562601 private static function getAllowUnknowns (array $ options ) : bool
563602 {
564- $ allowUnknowns = $ options [' allowUnknowns ' ];
603+ $ allowUnknowns = $ options [FiltererOptions:: ALLOW_UNKNOWNS ];
565604 if ($ allowUnknowns !== false && $ allowUnknowns !== true ) {
566- throw new InvalidArgumentException (" 'allowUnknowns ' option was not a bool " );
605+ throw new InvalidArgumentException (sprintf ( " '%s ' option was not a bool ", FiltererOptions:: ALLOW_UNKNOWNS ) );
567606 }
568607
569608 return $ allowUnknowns ;
570609 }
571610
572611 private static function getDefaultRequired (array $ options ) : bool
573612 {
574- $ defaultRequired = $ options [' defaultRequired ' ];
613+ $ defaultRequired = $ options [FiltererOptions:: DEFAULT_REQUIRED ];
575614 if ($ defaultRequired !== false && $ defaultRequired !== true ) {
576- throw new InvalidArgumentException ("'defaultRequired' option was not a bool " );
615+ throw new InvalidArgumentException (
616+ sprintf ("'%s' option was not a bool " , FiltererOptions::DEFAULT_REQUIRED )
617+ );
577618 }
578619
579620 return $ defaultRequired ;
@@ -602,6 +643,6 @@ private static function generateFilterResponse(string $responseType, FilterRespo
602643 ];
603644 }
604645
605- throw new InvalidArgumentException (" 'responseType ' was not a recognized value " );
646+ throw new InvalidArgumentException (sprintf ( " '%s ' was not a recognized value ", FiltererOptions:: RESPONSE_TYPE ) );
606647 }
607648}
0 commit comments