Completed
Push — master ( a4f1b8...dd0c4a )
by grégoire
02:01
created

ParameterJuicer::launchExceptionWhenFail()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 2
nop 1
1
<?php
2
/*
3
 * This file is part of Chanmix51’s ParameterJuicer package.
4
 *
5
 * (c) 2017 Grégoire HUBERT <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace Chanmix51\ParameterJuicer;
11
12
use Chanmix51\ParameterJuicer\ValidationException;
13
use Chanmix51\ParameterJuicer\ParameterJuicerInterface;
14
15
/**
16
 * ParameterJuicer
17
 *
18
 * @package   ParameterJuicer
19
 * @copyright 2017 Grégoire HUBERT
20
 * @author    Grégoire HUBERT <[email protected]>
21
 * @license   X11 {@link http://opensource.org/licenses/mit-license.php}
22
 */
23
class ParameterJuicer implements ParameterJuicerInterface
24
{
25
    const STRATEGY_IGNORE_EXTRA_VALUES = 0;
26
    const STRATEGY_REFUSE_EXTRA_VALUES = 1;
27
    const STRATEGY_ACCEPT_EXTRA_VALUES = 2;
28
29
    /** @var  array     list of validators, must be callables */
30
    protected $validators = [];
31
32
    /** @var  array     list of cleaners, must be callables */
33
    protected $cleaners = [];
34
35
    /** @var  array     list of fields, this gives an information if the field
36
                        is mandatory or optional. */
37
    protected $fields = [];
38
39
    /**
40
     * getName
41
     *
42
     * @see     ParameterJuicerInterface
43
     */
44
    public function getName(): string
45
    {
46
        return 'anonymous';
47
    }
48
49
    /**
50
     * addField
51
     *
52
     * Declare a new field with no validators nor cleaner. It can be declared
53
     * if the field is optional or mandatory.
54
     * If the field already exists, it is overriden.
55
     */
56
    public function addField(string $name, bool $is_mandatory = true): self
57
    {
58
        $this->fields[$name] = $is_mandatory;
59
60
        return $this;
61
    }
62
63
    /**
64
     * addFields
65
     *
66
     * Declare several fields at once.Existing fields are overriden.
67
     */
68
    public function addFields(array $fields, $are_mandatory = true): self
69
    {
70
        array_merge($this->fields, array_fill_keys($fields, $are_mandatory));
71
72
        return $this;
73
    }
74
75
    /**
76
     * removeField
77
     *
78
     * Remove an existing field with all validators or cleaners associated to
79
     * it if any. It throws an exception if the field does not exist.
80
     *
81
     * @throws \InvalidArgumentException
82
     */
83
    public function removeField(string $name): self
84
    {
85
        $this->checkFieldExists($name);
86
        unset($this->fields[$name]);
87
88
        if (isset($this->validators[$name])) {
89
            unset($this->validators[$name]);
90
        }
91
92
        if (isset($this->cleaners[$name])) {
93
            unset($this->cleaners[$name]);
94
        }
95
96
        return $this;
97
    }
98
99
    /**
100
     * addValidator
101
     *
102
     * Add a new validator associated to a key. If the field is not already
103
     * declared, it is created.
104
     *
105
     * @throws \InvalidArgumentException
106
     */
107
    public function addValidator(string $name, callable $validator): self
108
    {
109
        $this
110
            ->checkFieldExists($name)
111
            ->validators[$name][] = $validator
112
            ;
113
114
        return $this;
115
    }
116
117
    /**
118
     * addCleaner
119
     *
120
     * Add a new cleaner associated to a key.
121
     *
122
     * @throws \InvalidArgumentException
123
     */
124
    public function addCleaner(string $name, callable $cleaner): self
125
    {
126
        $this
127
            ->checkFieldExists($name)
128
            ->cleaners[$name][] = $cleaner
129
            ;
130
131
        return $this;
132
    }
133
134
    /**
135
     * addJuicer
136
     *
137
     * Add a juicer to clean a validate a subset of data.
138
     *
139
     * @throws \InvalidArgumentException
140
     */
141
    public function addJuicer(
142
        string $name,
143
        ParameterJuicerInterface $juicer,
144
        int $strategy = self::STRATEGY_IGNORE_EXTRA_VALUES
0 ignored issues
show
Unused Code introduced by
The parameter $strategy is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
145
    ): self
146
    {
147
        return $this
148
            ->addCleaner($name, [$juicer, 'clean'])
149
            ->addValidator($name, [$juicer, 'validate'])
150
            ;
151
    }
152
153
    /**
154
     * squash
155
     *
156
     * Clean & validate the given data according to the definition.
157
     *
158
     * @see     ParameterJuicerInterface
159
     */
160
    public function squash(
161
        array   $values,
162
        int     $strategy = self::STRATEGY_IGNORE_EXTRA_VALUES
163
    ): array
164
    {
165
        return $this->validate(
166
            $this->getName(),
167
            $this->clean($values),
168
            $strategy
169
        );
170
    }
171
172
    /**
173
     * validate
174
     *
175
     * Trigger validation on values.
176
     *
177
     * @see     ParameterJuicerInterface
178
     */
179
    public function validate(
180
        string $name,
181
        array $values,
182
        int $strategy = self::STRATEGY_IGNORE_EXTRA_VALUES
183
    ): array
184
    {
185
        $exception = new ValidationException;
186
187
        if ($strategy <> self::STRATEGY_ACCEPT_EXTRA_VALUES) {
188
            $values = $this->applyStrategy($exception, $values, $strategy);
189
        }
190
191
        foreach ($this->fields as $field => $is_mandatory) {
192
            $is_set = isset($values[$field]);
193
194
            if ($is_mandatory && !$is_set) {
195
                $exception->addMessage(sprintf("Missing field '%s' is mandatory.", $field));
196
197
                continue;
198
            }
199
200
            if ($is_set && isset($this->validators[$field])) {
201
                $this->launchValidatorsFor($field, $values[$field], $strategy, $exception);
202
            }
203
        }
204
205
        $this->launchExceptionWhenFail($exception);
206
207
        return $values;
208
    }
209
210
    /**
211
     * clean
212
     *
213
     * Clean and return values.
214
     *
215
     * @see     ParameterJuicerInterface
216
     */
217
    public function clean(array $values): array
218
    {
219
        foreach ($this->cleaners as $field_name => $cleaners) {
220
            if (isset($values[$field_name])) {
221
                foreach ($cleaners as $cleaner) {
222
                    $values[$field_name] =
223
                        call_user_func($cleaner, $values[$field_name]);
224
                }
225
            }
226
        }
227
228
        return $values;
229
    }
230
231
    /**
232
     * applyStrategy
233
     *
234
     * Apply extra values strategies.
235
     *
236
     * @throws RuntimeException if no valid stratgies were provided.
237
     */
238
    private function applyStrategy(ValidationException $exception, array $values, int $strategy): array
239
    {
240
        $diff_keys = array_keys(
241
            array_diff_key(
242
                $values,
243
                $this->fields
244
            )
245
        );
246
247
        if (count($diff_keys) === 0 || $strategy === self::STRATEGY_ACCEPT_EXTRA_VALUES) {
248
            return $values;
249
        }
250
251
        if ($strategy === self::STRATEGY_REFUSE_EXTRA_VALUES) {
252
            foreach ($diff_keys as $key) {
253
                $exception->addMessage(
254
                    sprintf(
255
                        "Extra field '%s' is present with STRATEGY_REFUSE_EXTRA_VALUES.",
256
                        $key
257
                    )
258
                );
259
            }
260
261
            return $values;
262
        }
263
264
        if ($strategy === self::STRATEGY_IGNORE_EXTRA_VALUES) {
265
            return array_diff_key($values, array_flip($diff_keys));
266
        }
267
268
        throw new \RuntimeException(
269
            sprintf(
270
                "Unknown strategy %d, available strategies are [STRATEGY_IGNORE_EXTRA_VALUES, STRATEGY_ACCEPT_EXTRA_VALUES, STRATEGY_REFUSE_EXTRA_VALUES].",
271
                $strategy
272
            )
273
        );
274
    }
275
276
    /**
277
     * checkFieldExists
278
     *
279
     * Throw an exception if the field does not exist.
280
     *
281
     * @throws  \InvalidArgumentException
282
     */
283
    private function checkFieldExists(string $name): self
284
    {
285
        if (!isset($this->fields[$name])) {
286
            throw new \InvalidArgumentException(
287
                sprintf(
288
                    "Field '%s' is not declared, fields are {%s}.",
289
                    $name,
290
                    join(', ', array_keys($this->fields))
291
                )
292
            );
293
        }
294
295
        return $this;
296
    }
297
298
    /**
299
     * launchExceptionWhenFail
300
     *
301
     * Check if the validation exception has messages. If yes, the exception is
302
     * thrown.
303
     */
304
    private function launchExceptionWhenFail(ValidationException $exception): self
305
    {
306
        if ($exception->hasMessages()) {
307
            $exception->addMessage(
308
                sprintf(
309
                    "Validation of set '%s' failed.",
310
                    $this->getName()
311
                )
312
            );
313
314
            throw $exception;
315
        }
316
317
        return $this;
318
    }
319
320
    private function launchValidatorsFor(string $field, $value, int $strategy, ValidationException $exception): self
321
    {
322
        foreach ($this->validators[$field] as $validator) {
323
            try {
324
                if (call_user_func($validator, $field, $value, $strategy) === false) {
325
                    throw new \RuntimeException(
326
                        sprintf("One of the validators for the field '%s' has a PHP error.", $field)
327
                    );
328
                }
329
            } catch (ValidationException $e) {
330
                $exception->addMessage($e->getMessage());
331
            }
332
        }
333
334
        return $this;
335
    }
336
}
337
338