Test Failed
Pull Request — master (#171)
by
unknown
01:53
created

Parameter::expandFilterArgs()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 0
cts 0
cp 0
rs 9.7666
c 0
b 0
f 0
cc 3
nc 3
nop 3
crap 12
1
<?php
2
namespace GuzzleHttp\Command\Guzzle;
3
4
use GuzzleHttp\Command\ToArrayInterface;
5
6
/**
7
 * API parameter object used with service descriptions
8
 */
9
class Parameter implements ToArrayInterface
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
48
    private $originalData;
49
50
    /** @var string $name */
51
    private $name;
52
53
    /** @var string $description */
54
    private $description;
55
56
    /** @var string|array $type */
57
    private $type;
58
59
    /** @var bool $required*/
60
    private $required;
61
62
    /** @var array|null $enum */
63
    private $enum;
64
65
    /** @var string $pattern */
66
    private $pattern;
67
68
    /** @var int $minimum*/
69
    private $minimum;
70
71
    /** @var int $maximum */
72
    private $maximum;
73
74
    /** @var int $minLength */
75
    private $minLength;
76
77
    /** @var int $maxLength */
78
    private $maxLength;
79
80
    /** @var int $minItems */
81
    private $minItems;
82
83
    /** @var int $maxItems */
84
    private $maxItems;
85
86
    /** @var mixed $default */
87
    private $default;
88
89
    /** @var bool $static */
90
    private $static;
91
92
    /** @var array $filters */
93
    private $filters;
94
95
    /** @var string $location */
96
    private $location;
97
98
    /** @var string $sentAs */
99
    private $sentAs;
100
101
    /** @var array $data */
102
    private $data;
103
104
    /** @var array $properties */
105
    private $properties = [];
106
107
    /** @var array|bool|Parameter $additionalProperties */
108
    private $additionalProperties;
109
110
    /** @var array|Parameter $items */
111
    private $items;
112
113
    /** @var string $format */
114
    private $format;
115
116
    private $propertiesCache = null;
117
118
    /** @var Description */
119
    private $serviceDescription;
120
121
    /**
122
     * Create a new Parameter using an associative array of data.
123
     *
124
     * The array can contain the following information:
125
     *
126
     * - name: (string) Unique name of the parameter
127
     *
128
     * - type: (string|array) Type of variable (string, number, integer,
129
     *   boolean, object, array, numeric, null, any). Types are used for
130
     *   validation and determining the structure of a parameter. You can use a
131
     *   union type by providing an array of simple types. If one of the union
132
     *   types matches the provided value, then the value is valid.
133
     *
134
     * - required: (bool) Whether or not the parameter is required
135
     *
136
     * - default: (mixed) Default value to use if no value is supplied
137
     *
138
     * - static: (bool) Set to true to specify that the parameter value cannot
139
     *   be changed from the default.
140
     *
141
     * - description: (string) Documentation of the parameter
142
     *
143
     * - location: (string) The location of a request used to apply a parameter.
144
     *   Custom locations can be registered with a command, but the defaults
145
     *   are uri, query, header, body, json, xml, formParam, multipart.
146
     *
147
     * - sentAs: (string) Specifies how the data being modeled is sent over the
148
     *   wire. For example, you may wish to include certain headers in a
149
     *   response model that have a normalized casing of FooBar, but the actual
150
     *   header is x-foo-bar. In this case, sentAs would be set to x-foo-bar.
151
     *
152
     * - filters: (array) Array of static method names to run a parameter
153
     *   value through. Each value in the array must be a string containing the
154
     *   full class path to a static method or an array of complex filter
155
     *   information. You can specify static methods of classes using the full
156
     *   namespace class name followed by '::' (e.g. Foo\Bar::baz). Some
157
     *   filters require arguments in order to properly filter a value.
158
     *
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
174
     *
175
     * - additionalProperties: (array) This attribute defines a schema for all
176
     *   properties that are not explicitly defined in an object type
177
     *   definition. If specified, the value MUST be a schema or a boolean. If
178 34
     *   false is provided, no additional properties are allowed beyond the
179
     *   properties defined in the schema. The default value is an empty schema
180 34
     *   which allows any value for additional properties.
181
     *
182 34
     * - items: This attribute defines the allowed items in an instance array,
183 7
     *   and MUST be a schema or an array of schemas. The default value is an
184 7
     *   empty schema which allows any value for items in the instance array.
185 1
     *   When this attribute value is a schema and the instance value is an
186
     *   array, then all the items in the array MUST be valid according to the
187 6
     *   schema.
188 1
     *
189 1
     * - pattern: When the type is a string, you can specify the regex pattern
190 1
     *   that a value must match
191 1
     *
192
     * - enum: When the type is a string, you can specify a list of acceptable
193
     *   values.
194 1
     *
195 6
     * - minItems: (int) Minimum number of items allowed in an array
196
     *
197
     * - maxItems: (int) Maximum number of items allowed in an array
198
     *
199 1
     * - minLength: (int) Minimum length of a string
200 1
     *
201 1
     * - maxLength: (int) Maximum length of a string
202 1
     *
203 6
     * - minimum: (int) Minimum value of an integer
204
     *
205
     * - maximum: (int) Maximum value of an integer
206 33
     *
207 31
     * - data: (array) Any additional custom data to use when serializing,
208 33
     *   validating, etc
209
     *
210 33
     * - format: (string) Format used to coax a value into the correct format
211 33
     *   when serializing or unserializing. You may specify either an array of
212
     *   filters OR a format, but not both. Supported values: date-time, date,
213 33
     *   time, timestamp, date-time-http, and boolean-string.
214 10
     *
215 9
     * - $ref: (string) String referencing a service description model. The
216
     *   parameter is replaced by the schema contained in the model.
217 32
     *
218 2
     * @param array $data    Array of data as seen in service descriptions
219 2
     * @param array $options Options used when creating the parameter. You can
220 32
     *     specify a Guzzle service description in the 'description' key.
221
     *
222
     * @throws \InvalidArgumentException
223
     */
224
    public function __construct(array $data = [], array $options = [])
225
    {
226
        $this->originalData = $data;
227 7
228
        if (isset($options['description'])) {
229 7
            $this->serviceDescription = $options['description'];
230
            if (!($this->serviceDescription instanceof DescriptionInterface)) {
231
                throw new \InvalidArgumentException('description must be a Description');
232
            }
233
            if (isset($data['$ref'])) {
234
                if ($model = $this->serviceDescription->getModel($data['$ref'])) {
235
                    $name = isset($data['name']) ? $data['name'] : null;
236
                    $data = $model->toArray() + $data;
237
                    if ($name) {
238
                        $data['name'] = $name;
239 4
                    }
240
                }
241 4
            } elseif (isset($data['extends'])) {
242 2
                // If this parameter extends from another parameter then start
243
                // with the actual data union in the parent's data (e.g. actual
244
                // supersedes parent)
245 2
                if ($extends = $this->serviceDescription->getModel($data['extends'])) {
246
                    $data += $extends->toArray();
247
                }
248
            }
249
        }
250
251
        // Pull configuration data into the parameter
252
        foreach ($data as $key => $value) {
253
            $this->{$key} = $value;
254
        }
255
256
        $this->required = (bool) $this->required;
257
        $this->data = (array) $this->data;
258 8
259
        if ($this->filters) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->filters of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
260
            $this->setFilters((array) $this->filters);
261 8
        }
262 5
263 1
        if ($this->type == 'object' && $this->additionalProperties === null) {
264 1
            $this->additionalProperties = true;
265
        }
266 4
    }
267
268
    /**
269
     * Convert the object to an array
270 3
     *
271 1
     * @return array
272 1
     */
273
    public function toArray()
274
    {
275 3
        return $this->originalData;
276 2
    }
277 2
278
    /**
279 1
     * Get the default or static value of the command based on a value
280 1
     *
281 1
     * @param string $value Value that is currently set
282 1
     *
283 1
     * @return mixed Returns the value, a static value if one is present, or a default value
284 1
     */
285 1
    public function getValue($value)
286 1
    {
287 1
        if ($this->static || ($this->default !== null && $value === null)) {
288 1
            return $this->default;
289 1
        }
290 1
291 1
        return $value;
292
    }
293 2
294 2
    /**
295
     * Run a value through the filters OR format attribute associated with the
296 3
     * parameter.
297
     *
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.
303
     *
304 1
     * @return mixed Returns the filtered value
305
     * @throws \RuntimeException when trying to format when no service
306 1
     *     description is available.
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 1
                sprintf(
315
                    '$stage must be one of [%s], but was given "%s"',
316 1
                    implode(', ', self::FILTER_STAGES),
317 1
                    $stage
318
                )
319
            );
320
        }
321
322
        // Formats are applied exclusively and supercede filters
323
        if ($this->format) {
324
            if (!$this->serviceDescription) {
325 1
                throw new \RuntimeException('No service description was set so '
326
                    . 'the value cannot be formatted.');
327 1
            }
328
            return $this->serviceDescription->format($this->format, $value);
329
        }
330
331
        // Convert Boolean values
332
        if ($this->type == 'boolean' && !is_bool($value)) {
333
            $value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
334
        }
335 2
336
        // Apply filters to the value
337 2
        if ($this->filters) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->filters of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
338
            $value = $this->invokeCustomFilters($value, $stage);
339
        }
340
341
        return $value;
342
    }
343
344
    /**
345 1
     * Get the name of the parameter
346
     *
347 1
     * @return string
348
     */
349
    public function getName()
350
    {
351
        return $this->name;
352
    }
353
354
    /**
355 1
     * Set the name of the parameter
356
     *
357 1
     * @param string $name Name to set
358
     */
359
    public function setName($name)
360
    {
361
        $this->name = $name;
362
    }
363
364
    /**
365 1
     * Get the key of the parameter, where sentAs will supersede name if it is
366
     * set.
367 1
     *
368
     * @return string
369
     */
370
    public function getWireName()
371
    {
372
        return $this->sentAs ?: $this->name;
373
    }
374
375 1
    /**
376
     * Get the type(s) of the parameter
377 1
     *
378
     * @return string|array
379
     */
380
    public function getType()
381
    {
382
        return $this->type;
383
    }
384
385 1
    /**
386
     * Get if the parameter is required
387 1
     *
388
     * @return bool
389
     */
390
    public function isRequired()
391
    {
392
        return $this->required;
393
    }
394
395 1
    /**
396
     * Get the default value of the parameter
397 1
     *
398
     * @return string|null
399
     */
400
    public function getDefault()
401
    {
402
        return $this->default;
403
    }
404
405 1
    /**
406
     * Get the description of the parameter
407 1
     *
408
     * @return string|null
409
     */
410
    public function getDescription()
411
    {
412
        return $this->description;
413
    }
414
415 1
    /**
416
     * Get the minimum acceptable value for an integer
417 1
     *
418
     * @return int|null
419
     */
420
    public function getMinimum()
421
    {
422
        return $this->minimum;
423
    }
424
425 1
    /**
426
     * Get the maximum acceptable value for an integer
427 1
     *
428
     * @return int|null
429
     */
430
    public function getMaximum()
431
    {
432
        return $this->maximum;
433
    }
434
435 2
    /**
436
     * Get the minimum allowed length of a string value
437 2
     *
438
     * @return int
439
     */
440
    public function getMinLength()
441
    {
442
        return $this->minLength;
443
    }
444
445
    /**
446 1
     * Get the maximum allowed length of a string value
447
     *
448 1
     * @return int|null
449
     */
450
    public function getMaxLength()
451
    {
452
        return $this->maxLength;
453
    }
454
455
    /**
456
     * Get the maximum allowed number of items in an array value
457
     *
458
     * @return int|null
459
     */
460 1
    public function getMaxItems()
461
    {
462 1
        return $this->maxItems;
463 1
    }
464 1
465 1
    /**
466 1
     * Get the minimum allowed number of items in an array value
467 1
     *
468
     * @return int
469
     */
470 1
    public function getMinItems()
471
    {
472
        return $this->minItems;
473
    }
474
475
    /**
476
     * Get the location of the parameter
477
     *
478 1
     * @return string|null
479
     */
480 1
    public function getLocation()
481
    {
482
        return $this->location;
483
    }
484
485
    /**
486
     * Get the sentAs attribute of the parameter that used with locations to
487
     * sentAs an attribute when it is being applied to a location.
488 2
     *
489
     * @return string|null
490 2
     */
491
    public function getSentAs()
492
    {
493
        return $this->sentAs;
494
    }
495
496
    /**
497
     * Retrieve a known property from the parameter by name or a data property
498 1
     * by name. When no specific name value is passed, all data properties
499
     * will be returned.
500 1
     *
501 1
     * @param string|null $name Specify a particular property name to retrieve
502 1
     *
503 1
     * @return array|mixed|null
504 1
     */
505 1
    public function getData($name = null)
506
    {
507 1
        if (!$name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
508
            return $this->data;
509
        } elseif (isset($this->data[$name])) {
510
            return $this->data[$name];
511
        } elseif (isset($this->{$name})) {
512
            return $this->{$name};
513
        }
514
515
        return null;
516
    }
517 1
518
    /**
519 1
     * Get whether or not the default value can be changed
520 1
     *
521
     * @return bool
522
     */
523 1
    public function isStatic()
524 1
    {
525 1
        return $this->static;
526 1
    }
527 1
528 1
    /**
529 1
     * Get an array of filters used by the parameter
530
     *
531 1
     * @return array
532
     */
533
    public function getFilters()
534
    {
535
        return $this->filters ?: [];
536
    }
537
538
    /**
539 1
     * Get the properties of the parameter
540
     *
541 1
     * @return Parameter[]
542 1
     */
543 1
    public function getProperties()
544 1
    {
545 1
        if (!$this->propertiesCache) {
546 1
            $this->propertiesCache = [];
547
            foreach (array_keys($this->properties) as $name) {
548 1
                $this->propertiesCache[$name] = $this->getProperty($name);
549
            }
550
        }
551
552
        return $this->propertiesCache;
553
    }
554
555
    /**
556 1
     * Get a specific property from the parameter
557
     *
558 1
     * @param string $name Name of the property to retrieve
559 1
     *
560 1
     * @return null|Parameter
561 1
     */
562 1
    public function getProperty($name)
563 1
    {
564
        if (!isset($this->properties[$name])) {
565 1
            return null;
566
        }
567
568
        if (!($this->properties[$name] instanceof self)) {
569
            $this->properties[$name]['name'] = $name;
570
            $this->properties[$name] = new static(
571
                $this->properties[$name],
572
                ['description' => $this->serviceDescription]
573 1
            );
574
        }
575 1
576
        return $this->properties[$name];
577
    }
578
579
    /**
580
     * Get the additionalProperties value of the parameter
581
     *
582
     * @return bool|Parameter|null
583 1
     */
584
    public function getAdditionalProperties()
585 1
    {
586
        if (is_array($this->additionalProperties)) {
587
            $this->additionalProperties = new static(
588
                $this->additionalProperties,
589
                ['description' => $this->serviceDescription]
590
            );
591
        }
592
593 4
        return $this->additionalProperties;
594
    }
595 4
596
    /**
597
     * Get the item data of the parameter
598
     *
599
     * @return Parameter
600
     */
601
    public function getItems()
602
    {
603
        if (is_array($this->items)) {
604
            $this->items = new static(
605 10
                $this->items,
606
                ['description' => $this->serviceDescription]
607 10
            );
608 10
        }
609 10
610 9
        return $this->items;
611
    }
612 9
613
    /**
614
     * Get the enum of strings that are valid for the parameter
615
     *
616
     * @return array|null
617
     */
618
    public function getEnum()
619
    {
620
        return $this->enum;
621
    }
622
623 10
    /**
624
     * Get the regex pattern that must match a value when the value is a string
625 10
     *
626 2
     * @return string
627 1
     */
628
    public function getPattern()
629 1
    {
630
        return $this->pattern;
631 1
    }
632
633 9
    /**
634 9
     * Get the format attribute of the schema
635 9
     *
636 7
     * @return string
637
     */
638
    public function getFormat()
639 9
    {
640
        return $this->format;
641
    }
642
643
    /**
644
     * Set the array of filters used by the parameter
645
     *
646
     * @param array $filters Array of functions to use as filters
647
     *
648 3
     * @return self
649
     */
650 3
    private function setFilters(array $filters)
651 1
    {
652
        $this->filters = [];
653 2
        foreach ($filters as $filter) {
654
            $this->addFilter($filter);
655
        }
656
657
        return $this;
658
    }
659
660
    /**
661
     * Add a filter to the parameter
662
     *
663
     * @param string|array $filter Method to filter the value through
664
     *
665
     * @return self
666
     * @throws \InvalidArgumentException
667
     */
668
    private function addFilter($filter)
669
    {
670
        if (is_array($filter)) {
671
            if (!isset($filter['method'])) {
672
                throw new \InvalidArgumentException(
673
                    'A [method] value must be specified for each complex filter'
674
                );
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
            }
687
        }
688
689
        if (!$this->filters) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->filters of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
690
            $this->filters = [$filter];
691
        } else {
692
            $this->filters[] = $filter;
693
        }
694
695
        return $this;
696
    }
697
698
    /**
699
     * Check if a parameter has a specific variable and if it set.
700
     *
701
     * @param string $var
702
     * @return bool
703
     */
704
    public function has($var)
705
    {
706
        if (!is_string($var)) {
707
            throw new \InvalidArgumentException('Expected a string. Got: ' . (is_object($var) ? get_class($var) : gettype($var)));
708
        }
709
        return isset($this->{$var}) && !empty($this->{$var});
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) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
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
    }
834
}
835