Completed
Pull Request — master (#41)
by Davide
03:41
created

Validation::validate()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 33
ccs 17
cts 17
cp 1
rs 8.4586
c 0
b 0
f 0
cc 7
nc 6
nop 3
crap 7
1
<?php
2
3
namespace DavidePastore\Slim\Validation;
4
5
use Respect\Validation\Exceptions\NestedValidationException;
6
7
/**
8
 * Validation for Slim.
9
 */
10
class Validation
11
{
12
    /**
13
     * Validators.
14
     *
15
     * @var array
16
     */
17
    protected $validators = [];
18
19
    /**
20
     * Options.
21
     *
22
     * @var array
23
     */
24
    protected $options = [
25
    ];
26
27
    /**
28
     * The translator to use fro the exception message.
29
     *
30
     * @var callable
31
     */
32
    protected $translator = null;
33
34
    /**
35
     * Errors from the validation.
36
     *
37
     * @var array
38
     */
39
    protected $errors = [];
40
41
    /**
42
     * The 'errors' attribute name.
43
     *
44
     * @var string
45
     */
46
    protected $errors_name = 'errors';
47
48
    /**
49
     * The 'has_error' attribute name.
50
     *
51
     * @var string
52
     */
53
    protected $has_errors_name = 'has_errors';
54
55
    /**
56
     * The 'validators' attribute name.
57
     *
58
     * @var string
59
     */
60
    protected $validators_name = 'validators';
61
62
    /**
63
     * The 'translator' attribute name.
64
     *
65
     * @var string
66
     */
67
    protected $translator_name = 'translator';
68
69
    /**
70
     * Create new Validator service provider.
71
     *
72
     * @param null|array|ArrayAccess $validators
73
     * @param null|callable          $translator
74
     * @param []|array               $options
0 ignored issues
show
Documentation introduced by
The doc-type []|array could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
75
     */
76 25
    public function __construct($validators = null, $translator = null, $options = [])
77
    {
78
        // Set the validators
79 25
        if (is_array($validators) || $validators instanceof \ArrayAccess) {
80 24
            $this->validators = $validators;
81 25
        } elseif (is_null($validators)) {
82 1
            $this->validators = [];
83 1
        }
84 25
        $this->translator = $translator;
85 25
        $this->options = array_merge($this->options, $options);
86 25
    }
87
88
    /**
89
     * Validation middleware invokable class.
90
     *
91
     * @param \Psr\Http\Message\ServerRequestInterface $request  PSR7 request
92
     * @param \Psr\Http\Message\ResponseInterface      $response PSR7 response
93
     * @param callable                                 $next     Next middleware
94
     *
95
     * @return \Psr\Http\Message\ResponseInterface
96
     */
97 25
    public function __invoke($request, $response, $next)
98
    {
99 25
        $this->errors = [];
100 25
        $params = $request->getParams();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Http\Message\ServerRequestInterface as the method getParams() does only exist in the following implementations of said interface: Slim\Http\Request.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
101 25
        $params = array_merge((array) $request->getAttribute('routeInfo')[2], $params);
102 25
        $this->validate($params, $this->validators);
103
104 25
        $request = $request->withAttribute($this->errors_name, $this->getErrors());
105 25
        $request = $request->withAttribute($this->has_errors_name, $this->hasErrors());
106 25
        $request = $request->withAttribute($this->validators_name, $this->getValidators());
107 25
        $request = $request->withAttribute($this->translator_name, $this->getTranslator());
108
109 25
        return $next($request, $response);
110
    }
111
112
    /**
113
     * Validate the parameters by the given params, validators and actual keys.
114
     * This method populates the $errors attribute.
115
     *
116
     * @param array $params     The array of parameters.
117
     * @param array $validators The array of validators.
118
     * @param array $actualKeys An array that will save all the keys of the tree to retrieve the correct value.
119
     */
120 25
    private function validate($params = [], $validators = [], $actualKeys = [])
121
    {
122
        //Validate every parameters in the validators array
123 25
        foreach ($validators as $key => $validator) {
124 24
            $actualKeys[] = $key;
125 24
            $param = $this->getNestedParam($params, $actualKeys);
126 24
            if (is_array($validator)) {
127 9
                if ($key === "*") {
128 9
                    $arrayKeys = array_keys($params[$actualKeys[0]]);
129
                    $innerActualKeys = array_splice($actualKeys, 0, -1);
130 24
                    foreach ($arrayKeys as $arrayKey) {
131 24
                        $innerActualKeys[] = $arrayKey;
132 14
                        $this->validate($params, $validator, $innerActualKeys);
133 2
                        array_pop($innerActualKeys);
134 2
                    }
135 14
                } else {
136
                    $this->validate($params, $validator, $actualKeys);
137
                }
138
            } else {
139
                try {
140 24
                    $validator->assert($param);
141 25
                } catch (NestedValidationException $exception) {
142 25
                    if ($this->translator) {
143
                        $exception->setParam('translator', $this->translator);
144
                    }
145
                    $this->errors[implode('.', $actualKeys)] = $exception->getMessages();
146
                }
147
            }
148
149
            //Remove the key added in this foreach
150
            array_pop($actualKeys);
151
        }
152 24
    }
153
154 24
    /**
155 22
     * Get the nested parameter value.
156
     *
157 24
     * @param array $params An array that represents the values of the parameters.
158 24
     * @param array $keys   An array that represents the tree of keys to use.
159 22
     *
160 22
     * @return mixed The nested parameter value by the given params and tree of keys.
161
     */
162 22
    private function getNestedParam($params = [], $keys = [])
163 14
    {
164 3
        if (empty($keys)) {
165
            return $params;
166
        } else {
167
            $firstKey = array_shift($keys);
168
            if ($this->isArrayLike($params) && array_key_exists($firstKey, $params)) {
169
                $params = (array) $params;
170
                $paramValue = $params[$firstKey];
171
172
                return $this->getNestedParam($paramValue, $keys);
173
            } else {
174
                return;
175
            }
176 24
        }
177
    }
178 24
179
    /**
180
     * Check if the given $params is an array like variable.
181
     *
182
     * @param array $params The variable to check.
183
     *
184
     * @return boolean Returns true if the given $params parameter is array like.
185
     */
186 25
    private function isArrayLike($params)
187
    {
188 25
        return is_array($params) || $params instanceof \SimpleXMLElement;
189
    }
190
191
    /**
192
     * Check if there are any errors.
193
     *
194
     * @return bool
195
     */
196 25
    public function hasErrors()
197
    {
198 25
        return !empty($this->errors);
199
    }
200
201
    /**
202
     * Get errors.
203
     *
204
     * @return array The errors array.
205
     */
206 25
    public function getErrors()
207
    {
208 25
        return $this->errors;
209
    }
210
211
    /**
212
     * Get validators.
213
     *
214
     * @return array The validators array.
215
     */
216 1
    public function getValidators()
217
    {
218 1
        return $this->validators;
219 1
    }
220
221
    /**
222
     * Set validators.
223
     *
224
     * @param array $validators The validators array.
225
     */
226 25
    public function setValidators($validators)
227
    {
228 25
        $this->validators = $validators;
229
    }
230
231
    /**
232
     * Get translator.
233
     *
234
     * @return callable The translator.
235
     */
236 1
    public function getTranslator()
237
    {
238 1
        return $this->translator;
239 1
    }
240
241
    /**
242
     * Set translator.
243
     *
244
     * @param callable $translator The translator.
245
     */
246
    public function setTranslator($translator)
247
    {
248
        $this->translator = $translator;
249
    }
250
}
251