Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
71 / 71
100.00% covered (success)
100.00%
9 / 9
CRAP
100.00% covered (success)
100.00%
1 / 1
Gate
100.00% covered (success)
100.00%
71 / 71
100.00% covered (success)
100.00%
9 / 9
42
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 accepts
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addFields
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 accept
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addField
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 assert
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
10
 setData
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getData
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
9
 get
n/a
0 / 0
n/a
0 / 0
1
 __get
n/a
0 / 0
n/a
0 / 0
1
 validate
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 getErrors
n/a
0 / 0
n/a
0 / 0
1
1<?php
2
3namespace Verja;
4
5use Verja\Exception\InvalidValue;
6
7class Gate
8{
9    /** @var Field[] */
10    protected $fields = [];
11
12    /** @var array */
13    protected $rawData = [];
14
15    /** @var string */
16    protected $filteredDataHash;
17
18    /** @var array */
19    protected $filteredData = [];
20
21    /** @var array */
22    protected $errors = [];
23
24    public function __construct(array $data = null)
25    {
26        if ($data) {
27            $this->setData($data);
28        }
29    }
30
31    /**
32     * Alias for addFields
33     *
34     * @param array $fields
35     * @return $this
36     * @see Gate::addFields()
37     */
38    public function accepts(array $fields)
39    {
40        return $this->addFields($fields);
41    }
42
43    /**
44     * Add an array of fields
45     *
46     * Definitions are defined as in addField.
47     *
48     * Accepts fields without filter and validator definitions as values. So the following examples are equal:
49     *
50     * `[ $key => [] ]`
51     *
52     * `[ $key => null ]`
53     *
54     * `[ $key ]`
55     *
56     * @param array $fields
57     * @return $this
58     * @see Gate::addField() how to pass definitions
59     */
60    public function addFields(array $fields)
61    {
62        foreach ($fields as $key => $field) {
63            if (is_int($key)) {
64                $this->addField($field); // field is the key in numeric arrays
65            } else {
66                $this->addField($key, $field);
67            }
68        }
69        return $this;
70    }
71
72    /**
73     * Alias for addField
74     *
75     * @param string $key
76     * @param mixed  $field
77     * @return $this
78     * @see Gate::addField()
79     */
80    public function accept($key, $field = null)
81    {
82        return $this->addField($key, $field);
83    }
84
85    /**
86     * Add an accepted field to this gate
87     *
88     * The definition can be a single validator or filter as string or object, an array of validators and filters
89     * as string or object or an instance of Field.
90     *
91     * The following examples are equal:
92     *
93     * `(new Field)->addValidator('strLen:2:5')`
94     *
95     * `'strLen:2:5'`
96     *
97     * `['strLen:2:5']`
98     *
99     * `new Field(['strLen:2:5'])`
100     *
101     * @param string $key   The key in the data array
102     * @param mixed  $field Definition of the field
103     * @return $this
104     */
105    public function addField($key, $field = null)
106    {
107        if ($field instanceof Field) {
108            $this->fields[$key] = $field;
109        } else {
110            $definitions = [];
111            if (is_array($field)) {
112                $definitions = $field;
113            } elseif (is_string($field) || $field instanceof ValidatorInterface || $field instanceof FilterInterface) {
114                $definitions = [$field];
115            }
116            $this->fields[$key] = new Field($definitions);
117        }
118
119        return $this;
120    }
121
122    /**
123     * Quick assertion with filters and validators
124     *
125     * Asserts that $value in $context can be filtered and validated by $field.
126     *
127     * If not an InvalidValue exception is thrown.
128     *
129     * Field can be defined as for addField.
130     *
131     * @param mixed $field
132     * @param mixed $value
133     * @param array $context
134     * @return mixed
135     * @throws InvalidValue
136     * @see Gate::addField()
137     */
138    public static function assert($field, $value, array $context = [])
139    {
140        if (!$field instanceof Field) {
141            $definitions = [];
142            if (is_array($field)) {
143                $definitions = $field;
144            } elseif (is_string($field) ||
145                      $field instanceof ValidatorInterface ||
146                      $field instanceof FilterInterface ||
147                      is_callable($field)
148            ) {
149                $definitions = [$field];
150            }
151            $field = new Field($definitions);
152        }
153
154        $filtered = $field->filter($value, $context);
155        if (!$field->validate($filtered, $context)) {
156            $errors = $field->getErrors();
157            if (count($errors) === 1) {
158                throw new InvalidValue(sprintf('Assertion failed: %s', $errors[0]->message), ...$errors);
159            } elseif (count($errors) > 1) {
160                // Ignoring coverage because of error in coverage analysis
161                // @codeCoverageIgnoreStart
162                throw new InvalidValue(sprintf(
163                    'Assertion failed: %s',
164                    implode('; ', array_map(function (Error $error) {
165                        return $error->message;
166                    }, $errors))
167                ), ...$errors);
168                // @codeCoverageIgnoreEnd
169            } else {
170                throw new InvalidValue(sprintf(
171                    'Failed asserting that %s is valid (unknown error)',
172                    json_encode($value)
173                ));
174            }
175        }
176
177        return $filtered;
178    }
179
180    /**
181     * Set the data that should be covered (the context)
182     *
183     * @param array $data
184     * @return $this
185     */
186    public function setData(array $data)
187    {
188        $this->rawData = $data;
189        return $this;
190    }
191
192    /**
193     * Get all data or the value for $key
194     *
195     * @param string $key
196     * @param bool   $validate
197     * @return array|mixed
198     * @throws InvalidValue When value is invalid
199     */
200    public function getData(string $key = null, $validate = true)
201    {
202        if ($key !== null) {
203            if (!isset($this->fields[$key])) {
204                return null;
205            }
206            $fields = [$key => $this->fields[$key]];
207        } else {
208            $fields = $this->fields;
209        }
210
211        $result  = [];
212        foreach ($fields as $k => $field) {
213            $filtered = $field->filter($this->rawData[$k] ?? null, $this->rawData);
214
215            if ($validate && !$field->validate($this->rawData[$k] ?? null, $this->rawData)) {
216                if ($field->isRequired()) {
217                    $errors = $field->getErrors();
218                    if (count($errors) > 0) {
219                        throw new InvalidValue(sprintf('Invalid %s: %s', $k, $errors[0]->message), ...$errors);
220                    }
221                    throw new InvalidValue(sprintf('The value %s is not valid for %s', json_encode($filtered), $k));
222                } else {
223                    $filtered = null;
224                }
225            }
226
227            if ($key !== null) {
228                return $filtered;
229            }
230
231            $result[$k] = $filtered;
232        }
233
234        return $result;
235    }
236
237    /**
238     * Alias for getData
239     *
240     * @param string $key
241     * @return mixed
242     * @see                Gate::getData()
243     * @codeCoverageIgnore trivial
244     * @throws InvalidValue
245     */
246    public function get(string $key = null)
247    {
248        return $this->getData($key);
249    }
250
251    /**
252     * Alias for getData
253     *
254     * @param string $key
255     * @return mixed
256     * @see                Gate::getData()
257     * @codeCoverageIgnore trivial
258     * @throws InvalidValue
259     */
260    public function __get(string $key)
261    {
262        return $this->getData($key);
263    }
264
265    /**
266     * Validate $data or previously stored data
267     *
268     * @param array $data
269     * @return bool
270     */
271    public function validate(array $data = null)
272    {
273        if ($data) {
274            $this->setData($data);
275        }
276
277        $valid = true;
278        $this->errors = [];
279        foreach ($this->fields as $key => $field) {
280            if (empty($this->rawData[$key]) && !$field->isRequired()) {
281                continue;
282            }
283
284            if (!$field->validate($this->rawData[$key] ?? null, $this->rawData)) {
285                $valid = false;
286                $this->errors[$key] = $field->getErrors();
287            }
288        }
289        return $valid;
290    }
291
292    /**
293     * Get all reported errors
294     *
295     * @return array
296     * @codeCoverageIgnore trivial
297     */
298    public function getErrors()
299    {
300        return $this->errors;
301    }
302}