Completed
Push — master ( 3b34b0...1f6e23 )
by Adrian
01:20
created

RuleFactory::construcRuleByNameAndOptions()   B

Complexity

Conditions 8
Paths 20

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 8.013

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 16
cts 17
cp 0.9412
rs 8.1795
c 0
b 0
f 0
cc 8
nc 20
nop 2
crap 8.013
1
<?php
2
declare(strict_types=1);
3
4
namespace Sirius\Validation;
5
6
use Sirius\Validation\Rule\AbstractRule;
7
use Sirius\Validation\Rule\Callback as CallbackRule;
8
use Sirius\Validation\Rule\Matching;
9
10
class RuleFactory
11
{
12
    /**
13
     * Validator map allows for flexibility when creating a validation rule
14
     * You can use 'required' instead of 'required' for the name of the rule
15
     * or 'minLength'/'minlength' instead of 'MinLength'
16
     *
17
     * @var array
18
     */
19
    protected $validatorsMap = [];
20
21
    /**
22
     * @var array
23
     */
24
    protected $errorMessages = [];
25
26
    /**
27
     * @var array
28
     */
29
    protected $labeledErrorMessages = [];
30
31
    /**
32
     * Constructor
33
     */
34 29
    public function __construct()
35
    {
36 29
        $this->registerDefaultRules();
37 29
    }
38
39
    /**
40
     * Set up the default rules that come with the library
41
     */
42 29
    protected function registerDefaultRules()
43
    {
44
        $rulesClasses = [
45 29
            'Alpha',
46
            'AlphaNumeric',
47
            'AlphaNumHyphen',
48
            'ArrayLength',
49
            'ArrayMaxLength',
50
            'ArrayMinLength',
51
            'Between',
52
            'Callback',
53
            'Date',
54
            'DateTime',
55
            'Email',
56
            'EmailDomain',
57
            'Equal',
58
            'FullName',
59
            'GreaterThan',
60
            'InList',
61
            'Integer',
62
            'IpAddress',
63
            'Length',
64
            'LessThan',
65
            'MaxLength',
66
            'MinLength',
67
            'NotEqual',
68
            'NotInList',
69
            'NotMatch',
70
            'NotRegex',
71
            'Number',
72
            'Regex',
73
            'Required',
74
            'RequiredWhen',
75
            'RequiredWith',
76
            'RequiredWithout',
77
            'Time',
78
            'Url',
79
            'Website',
80
            'File\Extension',
81
            'File\Image',
82
            'File\ImageHeight',
83
            'File\ImageRatio',
84
            'File\ImageWidth',
85
            'File\Size',
86
            'Upload\Required',
87
            'Upload\Extension',
88
            'Upload\Image',
89
            'Upload\ImageHeight',
90
            'Upload\ImageRatio',
91
            'Upload\ImageWidth',
92
            'Upload\Size',
93
        ];
94 29
        foreach ($rulesClasses as $class) {
95 29
            $fullClassName       = '\\' . __NAMESPACE__ . '\Rule\\' . $class;
96 29
            $name                = strtolower(str_replace('\\', '', $class));
97 29
            $errorMessage        = constant($fullClassName . '::MESSAGE');
98 29
            $labeledErrorMessage = constant($fullClassName . '::LABELED_MESSAGE');
99 29
            $this->register($name, $fullClassName, $errorMessage, $labeledErrorMessage);
100
        }
101 29
        $this->register('match', Matching::class, Matching::MESSAGE, Matching::LABELED_MESSAGE);
102 29
    }
103
104
105
    /**
106
     * Register a class to be used when creating validation rules
107
     *
108
     * @param string $name
109
     * @param string $class
110
     *
111
     * @return $this
112
     */
113 29
    public function register($name, $class, $errorMessage = '', $labeledErrorMessage = '')
114
    {
115 29
        if (is_subclass_of($class, '\Sirius\Validation\Rule\AbstractRule')) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of returns inconsistent results on some PHP versions for interfaces; you could instead use ReflectionClass::implementsInterface.
Loading history...
116 29
            $this->validatorsMap[$name] = $class;
117
        }
118 29
        if ($errorMessage) {
119 29
            $this->errorMessages[$name] = $errorMessage;
120
        }
121 29
        if ($labeledErrorMessage) {
122 29
            $this->labeledErrorMessages[$name] = $labeledErrorMessage;
123
        }
124
125 29
        return $this;
126
    }
127
128
    /**
129
     * Factory method to construct a validator based on options that are used most of the times
130
     *
131
     * @param string|callable $name
132
     *            name of a validator class or a callable object/function
133
     * @param string|array $options
134
     *            validator options (an array, JSON string or QUERY string)
135
     * @param string $messageTemplate
136
     *            error message template
137
     * @param string $label
138
     *            label of the form input field or model attribute
139
     *
140
     * @throws \InvalidArgumentException
141
     * @return AbstractRule
142
     */
143 26
    public function createRule($name, $options = null, $messageTemplate = null, $label = null):AbstractRule
144
    {
145 26
        $validator = $this->construcRuleByNameAndOptions($name, $options);
146
147
        // no message template, try to get it from the registry
148 24
        if (!$messageTemplate) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $messageTemplate of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
149 21
            $messageTemplate = $this->getSuggestedMessageTemplate($name, !!$label);
0 ignored issues
show
Documentation introduced by
$name is of type callable, but the function expects a string.

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...
150
        }
151
152 24
        if (is_string($messageTemplate) && $messageTemplate !== '') {
153 21
            $validator->setMessageTemplate($messageTemplate);
154
        }
155 24
        if (is_string($label) && $label !== '') {
156 8
            $validator->setOption('label', $label);
157
        }
158
159 24
        return $validator;
160
    }
161
162
    /**
163
     * Set default error message for a rule
164
     *
165
     * @param string $rule
166
     * @param string|null $messageWithoutLabel
167
     * @param string|null $messageWithLabel
168
     *
169
     * @return $this
170
     */
171
    public function setMessages($rule, $messageWithoutLabel = null, $messageWithLabel = null)
172
    {
173
        if ($messageWithoutLabel) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $messageWithoutLabel of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
174
            $this->errorMessages[$rule] = $messageWithoutLabel;
175
        }
176
        if ($messageWithLabel) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $messageWithLabel of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
177
            $this->labeledErrorMessages[$rule] = $messageWithLabel;
178
        }
179
180
        return $this;
181
    }
182
183
    /**
184
     * Get the error message saved in the registry for a rule, where the message
185
     * is with or without a the label
186
     *
187
     * @param string $name name of the rule
188
     * @param bool $withLabel
189
     *
190
     * @return string|NULL
191
     */
192 21
    protected function getSuggestedMessageTemplate($name, $withLabel)
193
    {
194 21
        $noLabelMessage = is_string($name) && isset($this->errorMessages[$name]) ? $this->errorMessages[$name] : null;
195 21
        if ($withLabel) {
196 3
            return is_string($name) && isset($this->labeledErrorMessages[$name]) ?
197 3
                $this->labeledErrorMessages[$name] :
198 3
                $noLabelMessage;
199
        }
200
201 20
        return $noLabelMessage;
202
    }
203
204
    /**
205
     * @param $name
206
     * @param $options
207
     *
208
     * @return CallbackRule
209
     */
210 26
    protected function construcRuleByNameAndOptions($name, $options)
211
    {
212 26
        if (is_callable($name)) {
213 1
            $validator = new CallbackRule([
214 1
                'callback'  => $name,
215 1
                'arguments' => $options
216
            ]);
217 25
        } elseif (is_string($name)) {
218 25
            $name = trim($name);
219
            // use the validator map
220 25
            if (isset($this->validatorsMap[strtolower($name)])) {
221 24
                $name = $this->validatorsMap[strtolower($name)];
222
            }
223
            // try if the validator is the name of a class in the package
224 25
            if (class_exists('\Sirius\Validation\Rule\\' . $name, false)) {
225
                $name = '\Sirius\Validation\Rule\\' . $name;
226
            }
227
            // at this point we should have a class that can be instanciated
228 25
            if (class_exists($name) && is_subclass_of($name, '\Sirius\Validation\Rule\AbstractRule')) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of returns inconsistent results on some PHP versions for interfaces; you could instead use ReflectionClass::implementsInterface.
Loading history...
229 24
                $validator = new $name($options);
230
            }
231
        }
232
233 25
        if (!isset($validator)) {
234 1
            throw new \InvalidArgumentException(
235 1
                sprintf('Impossible to determine the validator based on the name: %s', (string) $name)
236
            );
237
        }
238
239 24
        return $validator;
240
    }
241
}
242