8
8
*/
9
9
class Parameter implements ToArrayInterface
10
10
{
11
+ /**
12
+ * The name of the filter stage that happens before a parameter is
13
+ * validated, for filtering raw data (e.g. clean-up before validation).
14
+ */
15
+ const FILTER_STAGE_BEFORE_VALIDATION = 'before_validation ' ;
16
+
17
+ /**
18
+ * The name of the filter stage that happens immediately after a parameter
19
+ * has been validated but before it is evaluated by location handlers to be
20
+ * written out on the wire.
21
+ */
22
+ const FILTER_STAGE_AFTER_VALIDATION = 'after_validation ' ;
23
+
24
+ /**
25
+ * The name of the filter stage that happens right before a validated value
26
+ * is being written out "on the wire" (e.g. for adjusting the structure or
27
+ * format of the data before sending it to the server).
28
+ */
29
+ const FILTER_STAGE_REQUEST_WIRE = 'request_wire ' ;
30
+
31
+ /**
32
+ * The name of the filter stage that happens right after a value has been
33
+ * read out of a response "on the wire" (e.g. for adjusting the structure or
34
+ * format of the data after receiving it back from the server).
35
+ */
36
+ const FILTER_STAGE_RESPONSE_WIRE = 'response_wire ' ;
37
+
38
+ /**
39
+ * A list of all allowed filter stages.
40
+ */
41
+ const FILTER_STAGES = [
42
+ self ::FILTER_STAGE_BEFORE_VALIDATION ,
43
+ self ::FILTER_STAGE_AFTER_VALIDATION ,
44
+ self ::FILTER_STAGE_REQUEST_WIRE ,
45
+ self ::FILTER_STAGE_RESPONSE_WIRE
46
+ ];
47
+
11
48
private $ originalData ;
12
49
13
50
/** @var string $name */
@@ -117,14 +154,23 @@ class Parameter implements ToArrayInterface
117
154
* full class path to a static method or an array of complex filter
118
155
* information. You can specify static methods of classes using the full
119
156
* namespace class name followed by '::' (e.g. Foo\Bar::baz). Some
120
- * filters require arguments in order to properly filter a value. For
121
- * complex filters, use a hash containing a 'method' key pointing to a
122
- * static method, and an 'args' key containing an array of positional
123
- * arguments to pass to the method. Arguments can contain keywords that
124
- * are replaced when filtering a value: '@value' is replaced with the
125
- * value being validated, '@api' is replaced with the Parameter object.
157
+ * filters require arguments in order to properly filter a value.
126
158
*
127
- * - properties: When the type is an object, you can specify nested parameters
159
+ * For complex filters, use a hash containing a 'method' key pointing to a
160
+ * static method, an 'args' key containing an array of positional
161
+ * arguments to pass to the method, and an optional 'stage' key. Arguments
162
+ * can contain keywords that are replaced when filtering a value: '@value'
163
+ * is replaced with the value being validated, '@api' is replaced with the
164
+ * Parameter object, and '@stage' is replaced with the current filter
165
+ * stage (if any was provided).
166
+ *
167
+ * The optional 'stage' key can be provided to control when the filter is
168
+ * invoked. The key can indicate that a filter should only be invoked
169
+ * 'before_validation', 'after_validation', when being written out to the
170
+ * 'request_wire' or being read from the 'response_wire'.
171
+ *
172
+ * - properties: When the type is an object, you can specify nested
173
+ * parameters
128
174
*
129
175
* - additionalProperties: (array) This attribute defines a schema for all
130
176
* properties that are not explicitly defined in an object type
@@ -250,14 +296,30 @@ public function getValue($value)
250
296
* parameter.
251
297
*
252
298
* @param mixed $value Value to filter
299
+ * @param string $stage An optional specifier of what filter stage to
300
+ * invoke. If null, then all filters are invoked no matter what stage
301
+ * they apply to. Otherwise, only filters for the specified stage are
302
+ * invoked.
253
303
*
254
304
* @return mixed Returns the filtered value
255
305
* @throws \RuntimeException when trying to format when no service
256
306
* description is available.
257
- */
258
- public function filter ($ value )
259
- {
260
- // Formats are applied exclusively and supersed filters
307
+ * @throws \InvalidArgumentException if an invalid validation stage is
308
+ * provided.
309
+ */
310
+ public function filter ($ value , $ stage = null )
311
+ {
312
+ if (($ stage !== null ) && !in_array ($ stage , self ::FILTER_STAGES )) {
313
+ throw new \InvalidArgumentException (
314
+ sprintf (
315
+ '$stage must be one of [%s], but was given "%s" ' ,
316
+ implode (', ' , self ::FILTER_STAGES ),
317
+ $ stage
318
+ )
319
+ );
320
+ }
321
+
322
+ // Formats are applied exclusively and supercede filters
261
323
if ($ this ->format ) {
262
324
if (!$ this ->serviceDescription ) {
263
325
throw new \RuntimeException ('No service description was set so '
@@ -273,24 +335,7 @@ public function filter($value)
273
335
274
336
// Apply filters to the value
275
337
if ($ this ->filters ) {
276
- foreach ($ this ->filters as $ filter ) {
277
- if (is_array ($ filter )) {
278
- // Convert complex filters that hold value place holders
279
- foreach ($ filter ['args ' ] as &$ data ) {
280
- if ($ data == '@value ' ) {
281
- $ data = $ value ;
282
- } elseif ($ data == '@api ' ) {
283
- $ data = $ this ;
284
- }
285
- }
286
- $ value = call_user_func_array (
287
- $ filter ['method ' ],
288
- $ filter ['args ' ]
289
- );
290
- } else {
291
- $ value = call_user_func ($ filter , $ value );
292
- }
293
- }
338
+ $ value = $ this ->invokeCustomFilters ($ value , $ stage );
294
339
}
295
340
296
341
return $ value ;
@@ -628,6 +673,17 @@ private function addFilter($filter)
628
673
'A [method] value must be specified for each complex filter '
629
674
);
630
675
}
676
+
677
+ if (isset ($ filter ['stage ' ])
678
+ && !in_array ($ filter ['stage ' ], self ::FILTER_STAGES )) {
679
+ throw new \InvalidArgumentException (
680
+ sprintf (
681
+ '[stage] value must be one of [%s], but was given "%s" ' ,
682
+ implode (', ' , self ::FILTER_STAGES ),
683
+ $ filter ['stage ' ]
684
+ )
685
+ );
686
+ }
631
687
}
632
688
633
689
if (!$ this ->filters ) {
@@ -652,4 +708,127 @@ public function has($var)
652
708
}
653
709
return isset ($ this ->{$ var }) && !empty ($ this ->{$ var });
654
710
}
711
+
712
+ /**
713
+ * Filters the given data using filter methods specified in the config.
714
+ *
715
+ * If $stage is provided, only filters that apply to the provided filter
716
+ * stage will be invoked. To preserve legacy behavior, filters that do not
717
+ * specify a stage are implicitly invoked only in the pre-validation stage.
718
+ *
719
+ * @param mixed $value The value to filter.
720
+ * @param string $stage An optional specifier of what filter stage to
721
+ * invoke. If null, then all filters are invoked no matter what stage
722
+ * they apply to. Otherwise, only filters for the specified stage are
723
+ * invoked.
724
+ *
725
+ * @return mixed The filtered value.
726
+ */
727
+ private function invokeCustomFilters ($ value , $ stage ) {
728
+ $ filteredValue = $ value ;
729
+
730
+ foreach ($ this ->filters as $ filter ) {
731
+ if (is_array ($ filter )) {
732
+ $ filteredValue =
733
+ $ this ->invokeComplexFilter ($ filter , $ value , $ stage );
734
+ } else {
735
+ $ filteredValue =
736
+ $ this ->invokeSimpleFilter ($ filter , $ value , $ stage );
737
+ }
738
+ }
739
+
740
+ return $ filteredValue ;
741
+ }
742
+
743
+ /**
744
+ * Invokes a filter that uses value substitution and/or should only be
745
+ * invoked for a particular filter stage.
746
+ *
747
+ * If $stage is provided, and the filter specifies a stage, it is not
748
+ * invoked unless $stage matches the stage the filter indicates it applies
749
+ * to. If the filter is not invoked, $value is returned exactly as it was
750
+ * provided to this method.
751
+ *
752
+ * To preserve legacy behavior, if the filter does not specify a stage, it
753
+ * is implicitly invoked only in the pre-validation stage.
754
+ *
755
+ * @param array $filter Information about the filter to invoke.
756
+ * @param mixed $value The value to filter.
757
+ * @param string $stage An optional specifier of what filter stage to
758
+ * invoke. If null, then the filter is invoked no matter what stage it
759
+ * indicates it applies to. Otherwise, the filter is only invoked if it
760
+ * matches the specified stage.
761
+ *
762
+ * @return mixed The filtered value.
763
+ */
764
+ private function invokeComplexFilter (array $ filter , $ value , $ stage ) {
765
+ if (isset ($ filter ['stage ' ])) {
766
+ $ filterStage = $ filter ['stage ' ];
767
+ } else {
768
+ $ filterStage = self ::FILTER_STAGE_AFTER_VALIDATION ;
769
+ }
770
+
771
+ if (($ stage === null ) || ($ filterStage == $ stage )) {
772
+ // Convert complex filters that hold value place holders
773
+ $ filterArgs =
774
+ $ this ->expandFilterArgs ($ filter ['args ' ], $ value , $ stage );
775
+
776
+ $ filteredValue =
777
+ call_user_func_array ($ filter ['method ' ], $ filterArgs );
778
+ } else {
779
+ $ filteredValue = $ value ;
780
+ }
781
+
782
+ return $ filteredValue ;
783
+ }
784
+
785
+ /**
786
+ * Replaces any placeholders in filter arguments with values from the
787
+ * current context.
788
+ *
789
+ * @param array $filterArgs The array of arguments to pass to the filter
790
+ * function. Some of the elements of this array are expected to be
791
+ * placeholders that will be replaced by this function.
792
+ *
793
+ * @return array The array of arguments, with all placeholders replaced.
794
+ */
795
+ function expandFilterArgs (array $ filterArgs , $ value , $ stage ) {
796
+ $ replacements = [
797
+ '@value ' => $ value ,
798
+ '@api ' => $ this ,
799
+ '@stage ' => $ stage ,
800
+ ];
801
+
802
+ foreach ($ filterArgs as &$ argValue ) {
803
+ if (isset ($ replacements [$ argValue ])) {
804
+ $ argValue = $ replacements [$ argValue ];
805
+ }
806
+ }
807
+
808
+ return $ filterArgs ;
809
+ }
810
+
811
+ /**
812
+ * Invokes a filter only provides a function or method name to invoke,
813
+ * without additional parameters.
814
+ *
815
+ * If $stage is provided, the filter is not invoked unless we are in the
816
+ * pre-validation stage, to preserve legacy behavior.
817
+ *
818
+ * @param array $filter Information about the filter to invoke.
819
+ * @param mixed $value The value to filter.
820
+ * @param string $stage An optional specifier of what filter stage to
821
+ * invoke. If null, then the filter is invoked no matter what.
822
+ * Otherwise, the filter is only invoked if the value is
823
+ * FILTER_STAGE_AFTER_VALIDATION.
824
+ *
825
+ * @return mixed The filtered value.
826
+ */
827
+ private function invokeSimpleFilter ($ filter , $ value , $ stage ) {
828
+ if ($ stage === self ::FILTER_STAGE_AFTER_VALIDATION ) {
829
+ return $ value ;
830
+ } else {
831
+ return call_user_func ($ filter , $ value );
832
+ }
833
+ }
655
834
}
0 commit comments