Passed
Push — master ( 157485...b7615a )
by Nikolaos
09:23
created

Filter::processArraySanitizers()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 42
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 18
c 1
b 0
f 0
dl 0
loc 42
rs 9.3554
cc 5
nc 5
nop 3
1
<?php
2
3
/**
4
 * This file is part of the Phalcon Framework.
5
 *
6
 * (c) Phalcon Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Phalcon;
15
16
use Phalcon\Filter\Exception;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Phalcon\Exception. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
17
use Phalcon\Filter\FilterInterface;
18
19
use function array_merge;
20
use function call_user_func_array;
21
use function is_array;
22
use function is_string;
23
use function trigger_error;
24
25
use const E_USER_NOTICE;
26
27
/**
28
 * Lazy loads, stores and exposes sanitizer objects
29
 *
30
 * @property array $mapper
31
 * @property array $services
32
 */
33
class Filter implements FilterInterface
34
{
35
    public const FILTER_ABSINT      = "absint";
36
    public const FILTER_ALNUM       = "alnum";
37
    public const FILTER_ALPHA       = "alpha";
38
    public const FILTER_BOOL        = "bool";
39
    public const FILTER_EMAIL       = "email";
40
    public const FILTER_FLOAT       = "float";
41
    public const FILTER_INT         = "int";
42
    public const FILTER_LOWER       = "lower";
43
    public const FILTER_LOWERFIRST  = "lowerFirst";
44
    public const FILTER_REGEX       = "regex";
45
    public const FILTER_REMOVE      = "remove";
46
    public const FILTER_REPLACE     = "replace";
47
    public const FILTER_SPECIAL     = "special";
48
    public const FILTER_SPECIALFULL = "specialFull";
49
    public const FILTER_STRING      = "string";
50
    public const FILTER_STRIPTAGS   = "striptags";
51
    public const FILTER_TRIM        = "trim";
52
    public const FILTER_UPPER       = "upper";
53
    public const FILTER_UPPERFIRST  = "upperFirst";
54
    public const FILTER_UPPERWORDS  = "upperWords";
55
    public const FILTER_URL         = "url";
56
57
    /**
58
     * @var array
59
     */
60
    protected $mapper = [];
61
62
    /**
63
     * @var array
64
     */
65
    protected $services = [];
66
67
    /**
68
     * Key value pairs with name as the key and a callable as the value for
69
     * the service object
70
     *
71
     * @param array $mapper
72
     */
73
    public function __construct(array $mapper = [])
74
    {
75
        $this->init($mapper);
76
    }
77
78
    /**
79
     * Get a service. If it is not in the mapper array, create a new object,
80
     * set it and then return it.
81
     *
82
     * @param string $name
83
     *
84
     * @return object
85
     * @throws Exception
86
     */
87
    public function get(string $name): object
88
    {
89
        if (!isset($this->mapper[$name])) {
90
            throw new Exception(
91
                "The service " . $name
92
                . " has not been found in the locator"
93
            );
94
        }
95
96
        if (!isset($this->services[$name])) {
97
            $definition = $this->mapper[$name];
98
            if (is_string($definition)) {
99
                $this->services[$name] = new $definition();
100
            } else {
101
                $this->services[$name] = $definition;
102
            }
103
        }
104
105
        return $this->services[$name];
106
    }
107
108
    /**
109
     * Checks if a service exists in the map array
110
     *
111
     * @param string $name
112
     *
113
     * @return bool
114
     */
115
    public function has(string $name): bool
116
    {
117
        return isset($this->mapper[$name]);
118
    }
119
120
    /**
121
     * Sanitizes a value with a specified single or set of sanitizers
122
     *
123
     * @param mixed        $value
124
     * @param array|string $sanitizers
125
     * @param bool         $noRecursive
126
     *
127
     * @return mixed
128
     * @throws Exception
129
     */
130
    public function sanitize($value, $sanitizers, bool $noRecursive = false)
131
    {
132
        /**
133
         * First we need to figure out whether this is one sanitizer (string) or
134
         * an array with different sanitizers in it.
135
         *
136
         * All is well if the sanitizer accepts only one parameter, but certain
137
         * sanitizers require more than one parameter. To figure this out we
138
         * need to of course call call_user_func_array() but with the correct
139
         * parameters.
140
         *
141
         * If the array is passed with just values then those are just the
142
         * sanitizer names i.e.
143
         *
144
         * $locator->sanitize( 'abcde', ['trim', 'upper'])
145
         *
146
         * If the sanitizer requires more than one parameter then we need to
147
         * inject those parameters in the sanitize also:
148
         *
149
         * $locator->sanitize(
150
         *         '  mary had a little lamb ',
151
         *         [
152
         *             'trim',
153
         *             'replace' => [' ', '-'],
154
         *             'remove'  => ['mary'],
155
         *         ]
156
         * );
157
         *
158
         * The above should produce "-had-a-little-lamb"
159
         */
160
161
        /**
162
         * Filter is an array
163
         */
164
        if (is_array($sanitizers)) {
165
            /**
166
             * Null value - return immediately
167
             */
168
            if (null === $value) {
169
                return null;
170
            }
171
172
            return $this->processArraySanitizers(
173
                $sanitizers,
174
                $value,
175
                $noRecursive
176
            );
177
        }
178
179
        /**
180
         * Apply a single sanitizer to the values. Check if the values are an
181
         * array
182
         */
183
        if (is_array($value) && !$noRecursive) {
184
            return $this->processArrayValues($value, $sanitizers);
185
        }
186
187
        /**
188
         * One value one sanitizer
189
         */
190
        return $this->sanitizer($value, $sanitizers);
191
    }
192
193
    /**
194
     * Set a new service to the mapper array
195
     *
196
     * @param string          $name
197
     * @param callable|string $service
198
     */
199
    public function set(string $name, $service): void
200
    {
201
        $this->mapper[$name] = $service;
202
203
        unset($this->services[$name]);
204
    }
205
206
    /**
207
     * Loads the objects in the internal mapper array
208
     *
209
     * @param array $mapper
210
     */
211
    protected function init(array $mapper): void
212
    {
213
        foreach ($mapper as $name => $service) {
214
            $this->set($name, $service);
215
        }
216
    }
217
218
    /**
219
     * Processes the array values with the relevant sanitizers
220
     *
221
     * @param array  $values
222
     * @param string $sanitizerName
223
     * @param array  $sanitizerParams
224
     *
225
     * @return array
226
     * @throws Exception
227
     */
228
    private function processArrayValues(
229
        array $values,
230
        string $sanitizerName,
231
        array $sanitizerParams = []
232
    ): array {
233
        $arrayValue = [];
234
235
        foreach ($values as $itemKey => $itemValue) {
236
            $arrayValue[$itemKey] = $this->sanitizer(
237
                $itemValue,
238
                $sanitizerName,
239
                $sanitizerParams
240
            );
241
        }
242
243
        return $arrayValue;
244
    }
245
246
    /**
247
     * @param array $sanitizers
248
     * @param mixed $value
249
     * @param bool  $noRecursive
250
     *
251
     * @return string|array
252
     * @throws Exception
253
     */
254
    private function processArraySanitizers(
255
        array $sanitizers,
256
        $value,
257
        bool $noRecursive = false
258
    ) {
259
        /**
260
         * `value` is something. Loop through the sanitizers
261
         */
262
        foreach ($sanitizers as $sanitizerKey => $sanitizer) {
263
            /**
264
             * If `sanitizer` is an array, that means that the sanitizerKey
265
             * is the name of the sanitizer.
266
             */
267
            if (is_array($sanitizer)) {
268
                $sanitizerName   = $sanitizerKey;
269
                $sanitizerParams = $sanitizer;
270
            } else {
271
                $sanitizerName   = $sanitizer;
272
                $sanitizerParams = [];
273
            }
274
275
            /**
276
             * Check if the value is an array of elements. If `noRecursive`
277
             * has been defined it is a straight up; otherwise recursion is
278
             * required
279
             */
280
            if (is_array($value) && !$noRecursive) {
281
                $value = $this->processArrayValues(
282
                    $value,
283
                    $sanitizerName,
284
                    $sanitizerParams
285
                );
286
            } else {
287
                $value = $this->sanitizer(
288
                    $value,
289
                    $sanitizerName,
290
                    $sanitizerParams
291
                );
292
            }
293
        }
294
295
        return $value;
296
    }
297
298
    /**
299
     * Internal sanitize wrapper for recursion
300
     *
301
     * @param mixed  $value
302
     * @param string $sanitizerName
303
     * @param array  $sanitizerParams
304
     *
305
     * @return mixed
306
     * @throws Exception
307
     */
308
    private function sanitizer(
309
        $value,
310
        string $sanitizerName,
311
        array $sanitizerParams = []
312
    ) {
313
        if (!$this->has($sanitizerName)) {
314
            if (!empty($sanitizerName)) {
315
                trigger_error(
316
                    "Sanitizer '" . $sanitizerName . "' is not registered",
317
                    E_USER_NOTICE
318
                );
319
            }
320
321
            return $value;
322
        }
323
324
        $sanitizerObject = $this->get($sanitizerName);
325
        $params          = array_merge([$value], $sanitizerParams);
326
327
        return call_user_func_array($sanitizerObject, $params);
0 ignored issues
show
Bug introduced by
$sanitizerObject of type object is incompatible with the type callable expected by parameter $function of call_user_func_array(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

327
        return call_user_func_array(/** @scrutinizer ignore-type */ $sanitizerObject, $params);
Loading history...
328
    }
329
}
330