Completed
Push — 3.x ( 664d3e...80a626 )
by Hari
11s
created

SubjectFilter   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 366
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 96.2%

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 3
dl 0
loc 366
ccs 76
cts 79
cp 0.962
rs 10
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
A init() 0 4 1
A __invoke() 0 4 1
A assert() 0 18 2
A validate() 0 4 1
A sanitize() 0 4 1
A subfilter() 0 5 1
A addSpec() 0 6 1
A useFieldMessage() 0 4 1
A apply() 0 14 3
A applyToArray() 0 7 1
A applyToObject() 0 12 3
A applySpec() 0 17 4
A failed() 0 14 3
A getFailures() 0 4 1
1
<?php
2
/**
3
 *
4
 * This file is part of Aura for PHP.
5
 *
6
 * @license http://opensource.org/licenses/bsd-license.php BSD
7
 *
8
 */
9
namespace Aura\Filter;
10
11
use Aura\Filter\Exception;
12
use Aura\Filter\Failure\FailureCollection;
13
use Aura\Filter\Spec\SanitizeSpec;
14
use Aura\Filter\Spec\ValidateSpec;
15
use Aura\Filter\Spec\SubSpecFactory;
16
use Aura\Filter\Spec\SubSpec;
17
use InvalidArgumentException;
18
19
/**
20
 *
21
 * A filter for an entire "subject" (i.e., an array or object).
22
 *
23
 * @package Aura.Filter
24
 *
25
 */
26
class SubjectFilter
27
{
28
    /**
29
     *
30
     * An array of specifications for the filter subject.
31
     *
32
     * @var array
33
     *
34
     */
35
    protected $specs = array();
36
37
    /**
38
     *
39
     * Skip these fields on the filter subject.
40
     *
41
     * @var array
42
     *
43
     */
44
    protected $skip = array();
45
46
    /**
47
     *
48
     * A collection of failure objects.
49
     *
50
     * @var FailureCollection
51
     *
52
     */
53
    protected $failures;
54
55
    /**
56
     *
57
     * Use these field-specific messages when a subject field fails.
58
     *
59
     * @var array
60
     *
61
     */
62
    protected $field_messages = array();
63
64
    /**
65
     *
66
     * A prototype ValidateSpect.
67
     *
68
     * @var ValidateSpec
69
     *
70
     */
71
    protected $validate_spec;
72
73
    /**
74
     *
75
     * A prototype SanitizeSpect.
76
     *
77
     * @var SanitizeSpec
78
     *
79
     */
80
    protected $sanitize_spec;
81
82
83
    /**
84
     * Factory for Sub subject specifications
85
     *
86
     * @var SubSpecFactory
87
     *
88
     * @access protected
89
     */
90
    protected $sub_spec_factory;
91
92
    /**
93
     *
94
     * A prototype FailureCollection.
95
     *
96
     * @var FailureCollection
97
     *
98
     */
99
    protected $proto_failures;
100
101
    /**
102
     *
103
     * Constructor.
104
     *
105
     * @param ValidateSpec $validate_spec A prototype ValidateSpec.
106
     *
107
     * @param ValidateSpec $sanitize_spec A prototype SanitizeSpec.
108
     *
109
     * @param SubSpecFactory $sub_spec_factory A factory for SubSpec
110
     *
111
     * @param FailureCollection $failures A prototype FailureCollection.
112
     *
113
     * @return self
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
114
     *
115
     */
116 10
    public function __construct(
117
        ValidateSpec $validate_spec,
118
        SanitizeSpec $sanitize_spec,
119
        SubSpecFactory $sub_spec_factory,
120
        FailureCollection $failures
121
    ) {
122 10
        $this->validate_spec = $validate_spec;
123 10
        $this->sanitize_spec = $sanitize_spec;
124 10
        $this->sub_spec_factory = $sub_spec_factory;
125 10
        $this->proto_failures = $failures;
126 10
        $this->init();
127 10
    }
128
129
    /**
130
     *
131
     * Initialization logic for this filter.
132
     *
133
     * @return null
134
     *
135
     */
136 10
    protected function init()
137
    {
138
        // do nothing
139 10
    }
140
141
    /**
142
     *
143
     * Asserts that the subject passes the filter.
144
     *
145
     * @param array|object $subject The subject to be filtered.
146
     *
147
     * @return null
148
     *
149
     * @throws Exception\FilterFailed when the assertion fails.
150
     *
151
     */
152 2
    public function __invoke(&$subject)
153
    {
154 2
        return $this->assert($subject);
155
    }
156
157
    /**
158
     *
159
     * Asserts that the subject passes the filter.
160
     *
161
     * @param array|object $subject The subject to be filtered.
162
     *
163
     * @return null
164
     *
165
     * @throws Exception\FilterFailed when the assertion fails.
166
     *
167
     */
168 2
    public function assert(&$subject)
169
    {
170 2
        if ($this->apply($subject)) {
171 2
            return;
172 1
        }
173
174 1
        $class = get_class($this);
175
        $message = PHP_EOL
176 1
                 . "  Filter: {$class}" . PHP_EOL
177 1
                 . "  Fields:" . PHP_EOL
178 1
                 . $this->failures->getMessagesAsString('    ');
179
180 1
        $e = new Exception\FilterFailed($message);
181 1
        $e->setFilterClass($class);
182 1
        $e->setFailures($this->failures);
183 1
        $e->setSubject($subject);
184 1
        throw $e;
185
    }
186
187
    /**
188
     *
189
     * Adds a "validate" specification for a subject field.
190
     *
191
     * @param string $field The subject field name.
192
     *
193
     * @return ValidateSpec
194
     *
195
     */
196 7
    public function validate($field)
197
    {
198 7
        return $this->addSpec(clone $this->validate_spec, $field);
0 ignored issues
show
Documentation introduced by
clone $this->validate_spec is of type object<Aura\Filter\Spec\ValidateSpec>, but the function expects a object<Aura\Filter\Spec>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
199
    }
200
201
    /**
202
     *
203
     * Adds a "sanitize" specification for a subject field.
204
     *
205
     * @param string $field The subject field name.
206
     *
207
     * @return SanitizeSpec
208
     *
209
     */
210 3
    public function sanitize($field)
211
    {
212 3
        return $this->addSpec(clone $this->sanitize_spec, $field);
0 ignored issues
show
Documentation introduced by
clone $this->sanitize_spec is of type object<Aura\Filter\Spec\SanitizeSpec>, but the function expects a object<Aura\Filter\Spec>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
213
    }
214
215
    /**
216
     *
217
     * Adds a "subfilter" specification for a subject field.
218
     *
219
     * @param string $field The subject field name.
220
     *
221
     * @return SubSpec
222
     *
223
     */
224
    public function subfilter($field, $class = 'Aura\Filter\SubjectFilter')
225
    {
226
        $spec = $this->sub_spec_factory->newSubSpec($class);
227
        return $this->addSpec($spec, $field);
0 ignored issues
show
Documentation introduced by
$spec is of type object<Aura\Filter\Spec\SubSpec>, but the function expects a object<Aura\Filter\Spec>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
228
    }
229
230
    /**
231
     *
232
     * Adds a specification for a subject field.
233
     *
234
     * @param Spec $spec The specification object.
235
     *
236
     * @param string $field The subject field name.
237
     *
238
     * @return Spec
239
     *
240
     */
241 9
    protected function addSpec($spec, $field)
242
    {
243 9
        $this->specs[] = $spec;
244 9
        $spec->field($field);
245 9
        return $spec;
246
    }
247
248
    /**
249
     *
250
     * Specifies a custom message to use when a subject field fails.
251
     *
252
     * @param string $field The subject field name.
253
     *
254
     * @param string $message The failure message to use.
255
     *
256
     * @return null
257
     *
258
     */
259 1
    public function useFieldMessage($field, $message)
260
    {
261 1
        $this->field_messages[$field] = $message;
262 1
    }
263
264
    /**
265
     *
266
     * Applies the filter to a subject.
267
     *
268
     * @param array|object $subject The subject to be filtered.
269
     *
270
     * @return bool True on success, false on failure.
271
     *
272
     */
273 10
    public function apply(&$subject)
274
    {
275 10
        if (is_array($subject)) {
276 3
            return $this->applyToArray($subject);
277
        }
278
279 7
        if (! is_object($subject)) {
280 1
            $type = gettype($subject);
281 1
            $message = "Apply the filter to an array or object, not a {$type}.";
282 1
            throw new InvalidArgumentException($message);
283
        }
284
285 6
        return $this->applyToObject($subject);
286
    }
287
288
    /**
289
     *
290
     * Applies the rule specifications to an array.
291
     *
292
     * @param array $array The filter subject.
293
     *
294
     * @return bool True if all rules passed, false if not.
295
     *
296
     */
297 3
    protected function applyToArray(&$array)
298
    {
299 3
        $object = (object) $array;
300 3
        $result = $this->applyToObject($object);
301 3
        $array = (array) $object;
302 3
        return $result;
303
    }
304
305
    /**
306
     *
307
     * Applies the rule specifications to an object.
308
     *
309
     * @param object $object The filter subject.
310
     *
311
     * @return bool True if all rules passed, false if not.
312
     *
313
     */
314 9
    protected function applyToObject($object)
315
    {
316 9
        $this->skip = array();
317 9
        $this->failures = clone $this->proto_failures;
318 9
        foreach ($this->specs as $spec) {
319 9
            $continue = $this->applySpec($spec, $object);
320 9
            if (! $continue) {
321 1
                break;
322
            }
323 9
        }
324 9
        return $this->failures->isEmpty();
325
    }
326
327
    /**
328
     *
329
     * Apply a rule specification to the subject.
330
     *
331
     * @param Spec $spec The rule specification.
332
     *
333
     * @param object $subject The filter subject.
334
     *
335
     * @return bool True to continue, false to stop.
336
     *
337
     */
338 9
    protected function applySpec($spec, $subject)
339
    {
340 9
        if (isset($this->skip[$spec->getField()])) {
341 2
            return true;
342
        }
343
344 9
        if (call_user_func($spec, $subject)) {
345 4
            return true;
346
        }
347
348 7
        $this->failed($spec);
349 7
        if ($spec->isStopRule()) {
350 1
            return false;
351
        }
352
353 7
        return true;
354
    }
355
356
    /**
357
     *
358
     * Adds a failure.
359
     *
360
     * @param Spec $spec The failed rule specification.
361
     *
362
     * @return Failure
363
     *
364
     */
365 7
    protected function failed($spec)
366
    {
367 7
        $field = $spec->getField();
368
369 7
        if ($spec->isHardRule()) {
370 5
            $this->skip[$field] = true;
371 5
        }
372
373 7
        if (isset($this->field_messages[$field])) {
374 1
            return $this->failures->set($field, $this->field_messages[$field]);
375
        }
376
377 7
        return $this->failures->add($field, $spec->getMessage(), $spec->getArgs());
378
    }
379
380
    /**
381
     *
382
     * Returns the failures.
383
     *
384
     * @return FailureCollection
385
     *
386
     */
387 6
    public function getFailures()
388
    {
389 6
        return $this->failures;
390
    }
391
}
392