Completed
Push — master ( d45ca1...554243 )
by Adrian
01:42
created

AbstractRule::getMessage()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 7
cts 7
cp 1
rs 9.8666
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2
1
<?php
2
declare(strict_types=1);
3
namespace Sirius\Validation\Rule;
4
5
use Sirius\Validation\DataWrapper\ArrayWrapper;
6
use Sirius\Validation\DataWrapper\WrapperInterface;
7
use Sirius\Validation\ErrorMessage;
8
use Sirius\Validation\Util\RuleHelper;
9
10
abstract class AbstractRule
11
{
12
    // default error message when there is no LABEL attached
13
    const MESSAGE = 'Value is not valid';
14
15
    // default error message when there is a LABEL attached
16
    const LABELED_MESSAGE = '{label} is not valid';
17
18
    /**
19
     * The validation context
20
     * This is the data set that the data being validated belongs to
21
     * @var WrapperInterface
22
     */
23
    protected $context;
24
25
    /**
26
     * Options for the validator.
27
     * Also passed to the error message for customization.
28
     *
29
     * @var array
30
     */
31
    protected $options = [];
32
33
    /**
34
     * Custom error message template for the validator instance
35
     * If you don't agree with the default messages that were provided
36
     *
37
     * @var string
38
     */
39
    protected $messageTemplate;
40
41
    /**
42
     * Result of the last validation
43
     *
44
     * @var boolean
45
     */
46
    protected $success = false;
47
48
    /**
49
     * Last value validated with the validator.
50
     * Stored in order to be passed to the errorMessage so that you get error
51
     * messages like '"abc" is not a valid email'
52
     *
53
     * @var mixed
54
     */
55
    protected $value;
56
57
    /**
58
     * The error message prototype that will be used to generate the error message
59
     *
60
     * @var ErrorMessage
61
     */
62
    protected $errorMessagePrototype;
63
64
    /**
65
     * Options map in case the options are passed as list instead of associative array
66
     *
67
     * @var array
68
     */
69
    protected $optionsIndexMap = [];
70
71 166
    public function __construct($options = [])
72
    {
73 166
        $options = RuleHelper::normalizeOptions($options, $this->optionsIndexMap);
74 165
        if (is_array($options) && ! empty($options)) {
75 66
            foreach ($options as $k => $v) {
76 66
                $this->setOption($k, $v);
77
            }
78
        }
79 165
    }
80
81
    /**
82
     * Generates a unique string to identify the validator.
83
     * It is used to compare 2 validators so you don't add the same rule twice in a validator object
84
     *
85
     * @return string
86
     */
87 23
    public function getUniqueId(): string
88
    {
89 23
        return get_called_class() . '|' . json_encode(ksort($this->options));
90
    }
91
92
    /**
93
     * Set an option for the validator.
94
     *
95
     * The options are also be passed to the error message.
96
     *
97
     * @param string $name
98
     * @param mixed $value
99
     *
100
     * @return AbstractRule
101
     */
102 109
    public function setOption($name, $value)
103
    {
104 109
        $this->options[$name] = $value;
105
106 109
        return $this;
107
    }
108
109
    /**
110
     * Get an option for the validator.
111
     *
112
     * @param string $name
113
     *
114
     * @return mixed
115
     */
116 6
    public function getOption($name)
117
    {
118 6
        if (isset($this->options[$name])) {
119 6
            return $this->options[$name];
120
        } else {
121 2
            return null;
122
        }
123
    }
124
125
    /**
126
     * The context of the validator can be used when the validator depends on other values
127
     * that are not known at the moment the validator is constructed
128
     * For example, when you need to validate an email field matches another email field,
129
     * to confirm the email address
130
     *
131
     * @param array|object $context
132
     *
133
     * @return AbstractRule
134
     *@throws \InvalidArgumentException
135
     */
136 40
    public function setContext($context = null)
137
    {
138 40
        if ($context === null) {
139 6
            return $this;
140
        }
141 34
        if (is_array($context)) {
142 3
            $context = new ArrayWrapper($context);
143
        }
144 34
        if (! is_object($context) || ! $context instanceof WrapperInterface) {
145 1
            throw new \InvalidArgumentException(
146
                'Validator context must be either an array or an instance
147 1
                of ' . WrapperInterface::class
148
            );
149
        }
150 33
        $this->context = $context;
151
152 33
        return $this;
153
    }
154
155
    /**
156
     * Custom message for this validator to used instead of the the default one
157
     *
158
     * @param string $messageTemplate
159
     *
160
     * @return AbstractRule
161
     */
162 23
    public function setMessageTemplate($messageTemplate)
163
    {
164 23
        $this->messageTemplate = $messageTemplate;
165
166 23
        return $this;
167
    }
168
169
    /**
170
     * Retrieves the error message template (either the global one or the custom message)
171
     *
172
     * @return string
173
     */
174 28
    public function getMessageTemplate(): string
175
    {
176 28
        if ($this->messageTemplate) {
177 21
            return $this->messageTemplate;
178
        }
179 9
        if (isset($this->options['label'])) {
180 1
            return constant(get_class($this) . '::LABELED_MESSAGE');
181
        }
182
183 8
        return constant(get_class($this) . '::MESSAGE');
184
    }
185
186
    /**
187
     * Validates a value
188
     *
189
     * @param mixed $value
190
     * @param null|mixed $valueIdentifier
191
     *
192
     * @return mixed
193
     */
194
    abstract public function validate($value, string $valueIdentifier = null);
195
196
    /**
197
     * Sets the error message prototype that will be used when returning the error message
198
     * when validation fails.
199
     * This option can be used when you need translation
200
     *
201
     * @param ErrorMessage $errorMessagePrototype
202
     *
203
     * @return AbstractRule
204
     * @throws \InvalidArgumentException
205
     */
206 23
    public function setErrorMessagePrototype(ErrorMessage $errorMessagePrototype)
207
    {
208 23
        $this->errorMessagePrototype = $errorMessagePrototype;
209
210 23
        return $this;
211
    }
212
213
    /**
214
     * Returns the error message prototype.
215
     * It constructs one if there isn't one.
216
     *
217
     * @return ErrorMessage
218
     */
219 29
    public function getErrorMessagePrototype()
220
    {
221 29
        if (! $this->errorMessagePrototype) {
222 9
            $this->errorMessagePrototype = new ErrorMessage();
223
        }
224
225 29
        return $this->errorMessagePrototype;
226
    }
227
228
    /**
229
     * Retrieve the error message if validation failed
230
     *
231
     * @return NULL|ErrorMessage
232
     */
233 24
    public function getMessage()
234
    {
235 24
        if ($this->success) {
236 1
            return null;
237
        }
238 23
        $message = $this->getPotentialMessage();
239 23
        $message->setVariables([
240 23
            'value' => $this->value
241
        ]);
242
243 23
        return $message;
244
    }
245
246
    /**
247
     * Retrieve the potential error message.
248
     * Example: when you do client-side validation you need to access the "potential error message" to be displayed
249
     *
250
     * @return ErrorMessage
251
     */
252 28
    public function getPotentialMessage()
253
    {
254 28
        $message = clone $this->getErrorMessagePrototype();
255 28
        $message->setTemplate($this->getMessageTemplate());
256 28
        $message->setVariables($this->options);
257
258 28
        return $message;
259
    }
260
261
    /**
262
     * Method for determining the path to a related item.
263
     * Eg: for `lines[5][price]` the related item `lines[*][quantity]`
264
     * has the value identifier as `lines[5][quantity]`
265
     *
266
     * @param $valueIdentifier
267
     * @param $relatedItem
268
     *
269
     * @return string|null
270
     */
271 12
    protected function getRelatedValueIdentifier($valueIdentifier, $relatedItem)
272
    {
273
        // in case we don't have a related path
274 12
        if (strpos($relatedItem, '*') === false) {
275 10
            return $relatedItem;
276
        }
277
278
        // lines[*][quantity] is converted to ['lines', '*', 'quantity']
279 3
        $relatedItemParts = explode('[', str_replace(']', '', $relatedItem));
280
        // lines[5][price] is ['lines', '5', 'price']
281 3
        $valueIdentifierParts = explode('[', str_replace(']', '', $valueIdentifier));
282
283 3
        if (count($relatedItemParts) !== count($valueIdentifierParts)) {
284
            return $relatedItem;
285
        }
286
287
        // the result should be ['lines', '5', 'quantity']
288 3
        $relatedValueIdentifierParts = [];
289 3
        foreach ($relatedItemParts as $index => $part) {
290 3
            if ($part === '*' && isset($valueIdentifierParts[$index])) {
291 3
                $relatedValueIdentifierParts[] = $valueIdentifierParts[$index];
292
            } else {
293 3
                $relatedValueIdentifierParts[] = $part;
294
            }
295
        }
296
297 3
        $relatedValueIdentifier = implode('][', $relatedValueIdentifierParts) . ']';
298 3
        $relatedValueIdentifier = str_replace(
299 3
            $relatedValueIdentifierParts[0] . ']',
300 3
            $relatedValueIdentifierParts[0],
301
            $relatedValueIdentifier
302
        );
303
304 3
        return $relatedValueIdentifier;
305
    }
306
}
307