Completed
Push — master ( fb506f...5cd876 )
by Davide
10s
created

Validation::validate()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 16
cts 16
cp 1
rs 9.2408
c 0
b 0
f 0
cc 5
nc 5
nop 3
crap 5
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
                $this->validate($params, $validator, $actualKeys);
128 9
            } else {
129
                try {
130 24
                    $validator->assert($param);
131 24
                } catch (NestedValidationException $exception) {
132 14
                    if ($this->translator) {
133 2
                        $exception->setParam('translator', $this->translator);
134 2
                    }
135 14
                    $this->errors[implode('.', $actualKeys)] = $exception->getMessages();
136
                }
137
            }
138
139
            //Remove the key added in this foreach
140 24
            array_pop($actualKeys);
141 25
        }
142 25
    }
143
144
    /**
145
     * Get the nested parameter value.
146
     *
147
     * @param array $params An array that represents the values of the parameters.
148
     * @param array $keys   An array that represents the tree of keys to use.
149
     *
150
     * @return mixed The nested parameter value by the given params and tree of keys.
151
     */
152 24
    private function getNestedParam($params = [], $keys = [])
153
    {
154 24
        if (empty($keys)) {
155 22
            return $params;
156
        } else {
157 24
            $firstKey = array_shift($keys);
158 24
            if ($this->isArrayLike($params) && array_key_exists($firstKey, $params)) {
159 22
                $params = (array) $params;
160 22
                $paramValue = $params[$firstKey];
161
162 22
                return $this->getNestedParam($paramValue, $keys);
163 14
            } else {
164 3
                return;
165
            }
166
        }
167
    }
168
169
    /**
170
     * Check if the given $params is an array like variable.
171
     *
172
     * @param array $params The variable to check.
173
     *
174
     * @return boolean Returns true if the given $params parameter is array like.
175
     */
176 24
    private function isArrayLike($params)
177
    {
178 24
        return is_array($params) || $params instanceof \SimpleXMLElement;
179
    }
180
181
    /**
182
     * Check if there are any errors.
183
     *
184
     * @return bool
185
     */
186 25
    public function hasErrors()
187
    {
188 25
        return !empty($this->errors);
189
    }
190
191
    /**
192
     * Get errors.
193
     *
194
     * @return array The errors array.
195
     */
196 25
    public function getErrors()
197
    {
198 25
        return $this->errors;
199
    }
200
201
    /**
202
     * Get validators.
203
     *
204
     * @return array The validators array.
205
     */
206 25
    public function getValidators()
207
    {
208 25
        return $this->validators;
209
    }
210
211
    /**
212
     * Set validators.
213
     *
214
     * @param array $validators The validators array.
215
     */
216 1
    public function setValidators($validators)
217
    {
218 1
        $this->validators = $validators;
219 1
    }
220
221
    /**
222
     * Get translator.
223
     *
224
     * @return callable The translator.
225
     */
226 25
    public function getTranslator()
227
    {
228 25
        return $this->translator;
229
    }
230
231
    /**
232
     * Set translator.
233
     *
234
     * @param callable $translator The translator.
235
     */
236 1
    public function setTranslator($translator)
237
    {
238 1
        $this->translator = $translator;
239 1
    }
240
}
241