Completed
Push — master ( 458234...a0327e )
by Adrian
01:39
created

AbstractRule::__construct()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 7
cts 7
cp 1
rs 9.9666
c 0
b 0
f 0
cc 4
nc 3
nop 1
crap 4
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 153
    protected $optionsIndexMap = [];
70
71 153
    public function __construct($options = [])
72 152
    {
73 59
        $options = RuleHelper::normalizeOptions($options, $this->optionsIndexMap);
74 59
        if (is_array($options) && ! empty($options)) {
75 59
            foreach ($options as $k => $v) {
76 59
                $this->setOption($k, $v);
77 152
            }
78
        }
79
    }
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
    public function getUniqueId(): string
88
    {
89
        return get_called_class() . '|' . json_encode(ksort($this->options));
90
    }
91 153
92
    /**
93 153
     * Set an option for the validator.
94 107
     *
95
     * The options are also be passed to the error message.
96
     *
97 60
     * @param string $name
98 49
     * @param mixed $value
99
     *
100
     * @return AbstractRule
101 12
     */
102 12
    public function setOption($name, $value)
103 11
    {
104 11
        $this->options[$name] = $value;
105 7
106 11
        return $this;
107 5
    }
108 5
109 1
    /**
110
     * Get an option for the validator.
111 11
     *
112
     * @param string $name
113 12
     *
114 1
     * @return mixed
115
     */
116
    public function getOption($name)
117 11
    {
118
        if (isset($this->options[$name])) {
119
            return $this->options[$name];
120
        } else {
121
            return null;
122
        }
123
    }
124
125
    /**
126
     * The context of the validator can be used when the validator depends on other values
127 5
     * 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 5
     * to confirm the email address
130
     *
131 5
     * @param array|object $context
132
     *
133
     * @return AbstractRule
134
     *@throws \InvalidArgumentException
135
     */
136
    public function setContext($context = null)
137
    {
138
        if ($context === null) {
139
            return $this;
140
        }
141 6
        if (is_array($context)) {
142
            $context = new ArrayWrapper($context);
143 6
        }
144 6
        if (! is_object($context) || ! $context instanceof WrapperInterface) {
145
            throw new \InvalidArgumentException(
146 6
                'Validator context must be either an array or an instance
147 2
                of ' . WrapperInterface::class
148
            );
149 6
        }
150 2
        $this->context = $context;
151
152
        return $this;
153 6
    }
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
    public function setMessageTemplate($messageTemplate)
163
    {
164 1
        $this->messageTemplate = $messageTemplate;
165
166 1
        return $this;
167
    }
168
169
    /**
170
     * Retrieves the error message template (either the global one or the custom message)
171
     *
172
     * @return string
173 1
     */
174 1
    public function getMessageTemplate(): string
175 1
    {
176 1
        if ($this->messageTemplate) {
177
            return $this->messageTemplate;
178
        }
179
        if (isset($this->options['label'])) {
180
            return constant(get_class($this) . '::LABELED_MESSAGE');
181
        }
182
183 1
        return constant(get_class($this) . '::MESSAGE');
184 1
    }
185
186 1
    /**
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 49
    /**
197
     * Sets the error message prototype that will be used when returning the error message
198 49
     * 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
    public function setErrorMessagePrototype(ErrorMessage $errorMessagePrototype)
207
    {
208 21
        $this->errorMessagePrototype = $errorMessagePrototype;
209
210 21
        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
    public function getErrorMessagePrototype()
220
    {
221
        if (! $this->errorMessagePrototype) {
222
            $this->errorMessagePrototype = new ErrorMessage();
223 100
        }
224
225 100
        return $this->errorMessagePrototype;
226
    }
227 100
228
    /**
229
     * Retrieve the error message if validation failed
230
     *
231
     * @return NULL|ErrorMessage
232
     */
233
    public function getMessage()
234
    {
235
        if ($this->success) {
236
            return null;
237 1
        }
238
        $message = $this->getPotentialMessage();
239 1
        $message->setVariables([
240 1
            'value' => $this->value
241
        ]);
242 1
243
        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
    public function getPotentialMessage()
253
    {
254
        $message = clone $this->getErrorMessagePrototype();
255
        $message->setTemplate($this->getMessageTemplate());
256
        $message->setVariables($this->options);
257 36
258
        return $message;
259 36
    }
260 5
261
    /**
262 31
     * Method for determining the path to a related item.
263 3
     * Eg: for `lines[5][price]` the related item `lines[*][quantity]`
264 3
     * has the value identifier as `lines[5][quantity]`
265 31
     *
266 1
     * @param $valueIdentifier
267
     * @param $relatedItem
268
     *
269 1
     * @return string|null
270
     */
271 30
    protected function getRelatedValueIdentifier($valueIdentifier, $relatedItem)
272
    {
273 30
        // in case we don't have a related path
274
        if (strpos($relatedItem, '*') === false) {
275
            return $relatedItem;
276
        }
277
278
        // lines[*][quantity] is converted to ['lines', '*', 'quantity']
279
        $relatedItemParts = explode('[', str_replace(']', '', $relatedItem));
280
        // lines[5][price] is ['lines', '5', 'price']
281
        $valueIdentifierParts = explode('[', str_replace(']', '', $valueIdentifier));
282
283 22
        if (count($relatedItemParts) !== count($valueIdentifierParts)) {
284
            return $relatedItem;
285 22
        }
286
287 22
        // the result should be ['lines', '5', 'quantity']
288
        $relatedValueIdentifierParts = [];
289
        foreach ($relatedItemParts as $index => $part) {
290
            if ($part === '*' && isset($valueIdentifierParts[$index])) {
291
                $relatedValueIdentifierParts[] = $valueIdentifierParts[$index];
292
            } else {
293
                $relatedValueIdentifierParts[] = $part;
294
            }
295 27
        }
296
297 27
        $relatedValueIdentifier = implode('][', $relatedValueIdentifierParts) . ']';
298 21
        $relatedValueIdentifier = str_replace(
299
            $relatedValueIdentifierParts[0] . ']',
300 8
            $relatedValueIdentifierParts[0],
301 1
            $relatedValueIdentifier
302
        );
303
304 7
        return $relatedValueIdentifier;
305
    }
306
}
307