Completed
Pull Request — master (#40)
by Davide
01:33
created

Validation::setTranslator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
namespace DavidePastore\Slim\Validation;
4
5
use Respect\Validation\Exceptions\NestedValidationException;
6
use Respect\Validation\Validator;
7
8
/**
9
 * Validation for Slim.
10
 */
11
class Validation
12
{
13
    /**
14
     * Validators.
15
     *
16
     * @var array
17
     */
18
    protected $validators = [];
19
20
    /**
21
     * Options.
22
     *
23
     * @var array
24
     */
25
    protected $options = [
26
    ];
27
28
    /**
29
     * The translator to use fro the exception message.
30
     *
31
     * @var callable
32
     */
33
    protected $translator = null;
34
35
    /**
36
     * Errors from the validation.
37
     *
38
     * @var array
39
     */
40
    protected $errors = [];
41
42
    /**
43
     * The 'errors' attribute name.
44
     *
45
     * @var string
46
     */
47
    protected $errors_name = 'errors';
48
49
    /**
50
     * The 'has_error' attribute name.
51
     *
52
     * @var string
53
     */
54
    protected $has_errors_name = 'has_errors';
55
56
    /**
57
     * The 'validators' attribute name.
58
     *
59
     * @var string
60
     */
61
    protected $validators_name = 'validators';
62
63
    /**
64
     * The 'translator' attribute name.
65
     *
66
     * @var string
67
     */
68
    protected $translator_name = 'translator';
69
70
    /**
71
     * Is the validators an instance of Validator?
72
     *
73
     * @var boolean
74
     */
75
    protected $isValidator = false;
76 25
77
    /**
78
     * Create new Validator service provider.
79 25
     *
80 24
     * @param null|array|ArrayAccess $validators
81 25
     * @param null|callable          $translator
82 1
     * @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...
83 1
     */
84 25
    public function __construct($validators = null, $translator = null, $options = [])
85 25
    {
86 25
        // Set the validators
87
        if (is_array($validators) || $validators instanceof \ArrayAccess) {
88
            $this->validators = $validators;
89
        } elseif ($validators instanceof Validator) {
90
            $this->validators = $validators;
91
            $this->isValidator = true;
92
        } elseif (is_null($validators)) {
93
            $this->validators = [];
94
        }
95
        $this->translator = $translator;
96
        $this->options = array_merge($this->options, $options);
97 25
    }
98
99 25
    /**
100 25
     * Validation middleware invokable class.
101 25
     *
102 25
     * @param \Psr\Http\Message\ServerRequestInterface $request  PSR7 request
103
     * @param \Psr\Http\Message\ResponseInterface      $response PSR7 response
104 25
     * @param callable                                 $next     Next middleware
105 25
     *
106 25
     * @return \Psr\Http\Message\ResponseInterface
107 25
     */
108
    public function __invoke($request, $response, $next)
109 25
    {
110
        $this->errors = [];
111
        $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...
112
        $params = array_merge((array) $request->getAttribute('routeInfo')[2], $params);
113
        if ($this->isValidator) {
114
            $this->validateParam($params, $this->validators);
0 ignored issues
show
Documentation introduced by
$params is of type array, but the function expects a object<DavidePastore\Slim\Validation\any>.

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...
Documentation introduced by
$this->validators is of type array, but the function expects a object<DavidePastore\Slim\Validation\any>.

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