Completed
Branch feature/pre-split (d91fae)
by Anton
05:19
created

ValidatorTrait::intiaiteValidator()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 10
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 22
rs 8.9197
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
namespace Spiral\Models\Traits;
9
10
use Spiral\Core\Exceptions\SugarException;
11
use Spiral\Core\FactoryInterface;
12
use Spiral\Models\AccessorInterface;
13
use Spiral\Models\InvalidatesInterface;
14
use Spiral\Translator\Traits\TranslatorTrait;
15
use Spiral\Translator\Translator;
16
use Spiral\Validation\Events\ValidatorEvent;
17
use Spiral\Validation\Exceptions\ValidationException;
18
use Spiral\Validation\ValidatorInterface;
19
use Symfony\Component\EventDispatcher\Event;
20
21
/**
22
 * Provides ability to validate entity and all nested entities.
23
 */
24
trait ValidatorTrait
25
{
26
    use TranslatorTrait;
27
28
    /**
29
     * @var ValidatorInterface
30
     */
31
    private $validator = null;
32
33
    /**
34
     * Indication that entity been validated.
35
     *
36
     * @var bool
37
     */
38
    private $validated = true;
39
40
    /**
41
     * @var array
42
     */
43
    private $errors = [];
44
45
    /**
46
     * Attach custom validator to model.
47
     *
48
     * @param ValidatorInterface $validator
49
     */
50
    public function setValidator(ValidatorInterface $validator)
51
    {
52
        $this->validator = $validator;
53
        $this->validated = false;
54
    }
55
56
    /**
57
     * Get associated entity validator or create one (if needed), method can return null
58
     *
59
     * @return ValidatorInterface|null
60
     */
61
    public function getValidator()
62
    {
63
        if (empty($this->validator)) {
64
            $this->createValidator();
65
        }
66
67
        return $this->validator;
68
    }
69
70
    /**
71
     * Check if context data is valid.
72
     *
73
     * @return bool
74
     */
75
    public function isValid()
76
    {
77
        $this->validate(false);
78
79
        return empty($this->errors);
80
    }
81
82
    /**
83
     * Check if context data has errors.
84
     *
85
     * @return bool
86
     */
87
    public function hasErrors()
88
    {
89
        return !$this->isValid();
90
    }
91
92
    /**
93
     * List of errors associated with parent field, every field should have only one error assigned.
94
     *
95
     * @param bool $reset Re-validate object.
96
     *
97
     * @return array
98
     */
99
    public function getErrors($reset = false)
100
    {
101
        $this->validate($reset);
102
103
        $errors = [];
104
        foreach ($this->errors as $field => $error) {
105
            if (is_string($error) && Translator::isMessage($error)) {
106
                //We will localize only messages embraced with [[ and ]]
107
                $error = $this->say($error);
108
            }
109
110
            $errors[$field] = $error;
111
        }
112
113
        return $errors;
114
    }
115
116
    /**
117
     * Attach error to data field. Internal method to be used in validations. To be used ONLY in
118
     * overloaded validate method.
119
     *
120
     * @see validate()
121
     *
122
     * @param string $field
123
     *
124
     * @param string $message
125
     */
126
    protected function setError($field, $message)
127
    {
128
        $this->errors[$field] = $message;
129
    }
130
131
    /**
132
     * Check if desired field caused some validation error. To be used ONLY in overloaded validate
133
     * method.
134
     *
135
     * @see validate()
136
     *
137
     * @param string $field
138
     *
139
     * @return bool
140
     */
141
    protected function hasError($field)
142
    {
143
        return !empty($this->errors[$field]);
144
    }
145
146
    /**
147
     * @return bool
148
     */
149
    public function isValidated()
150
    {
151
        return $this->validated;
152
    }
153
154
    /**
155
     * Entity must re-validate data.
156
     *
157
     * @param bool $cascade Do not invalidate nested models (if such presented)
158
     *
159
     * @return $this
160
     */
161
    public function invalidate($cascade = false)
162
    {
163
        $this->validated = false;
164
165
        if ($cascade) {
166
            return $this;
167
        }
168
169
        //Invalidating all compositions
170
        foreach ($this->getFields(false) as $value) {
171
            //Let's force composition construction
172
            if ($value instanceof InvalidatesInterface) {
173
                $value->invalidate($cascade);
174
            }
175
        }
176
177
        return $this;
178
    }
179
180
    /**
181
     * @return array
182
     */
183
    public function __debugInfo()
184
    {
185
        return [
186
            'fields' => $this->getFields(),
187
            'errors' => $this->getErrors(),
188
        ];
189
    }
190
191
    /**
192
     * Validate entity fields
193
     *
194
     * @param bool $reset
195
     *
196
     * @return bool
197
     *
198
     * @throws ValidationException
199
     *
200
     * @event validate($validator)
201
     */
202
    protected function validate($reset = false)
203
    {
204
        $validator = $this->getValidator();
205
        if (empty($validator)) {
206
            $this->validated = true;
207
        }
208
209
        if ($this->validated && !$reset) {
210
            //Nothing to do
211
            return empty($this->errors);
212
        }
213
214
        //Refreshing validation fields
215
        $validator->setData($this->getFields(false));
216
217
        $this->dispatch('validate', new ValidatorEvent($validator));
0 ignored issues
show
Bug introduced by
It seems like $validator defined by $this->getValidator() on line 204 can be null; however, Spiral\Validation\Events...torEvent::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
218
219
        //Validation performed
220
        $this->validated = true;
221
222
        //Collecting errors if any
223
        return empty($this->errors = $validator->getErrors());
224
    }
225
226
    /**
227
     * @param bool $filter
228
     * @return array|AccessorInterface[]
229
     */
230
    abstract public function getFields($filter = true);
231
232
    /**
233
     * Method can return null if no validator is required.
234
     *
235
     * @return ValidatorInterface|null
236
     */
237
    abstract protected function createValidator();
238
239
    /**
240
     * Dispatch event. If no dispatched associated even will be returned without dispatching.
241
     *
242
     * @param string     $name  Event name.
243
     * @param Event|null $event Event class if any.
244
     *
245
     * @return Event
246
     */
247
    abstract protected function dispatch($name, Event $event = null);
248
249
    /**
250
     * Create validator using shared container.
251
     *
252
     * @param array $rules Non empty rules will initiate validator.
253
     *
254
     * @return ValidatorInterface
255
     *
256
     * @throws SugarException
257
     */
258
    private function intiaiteValidator(array $rules = [])
259
    {
260
        $container = $this->container();
261
262
        if (empty($container) || !$container->has(ValidatorInterface::class)) {
263
            //We can't create default validation without any rule, this is not secure
264
            throw new SugarException(
265
                'Unable to create Validator, no global container set or binding is missing'
266
            );
267
        }
268
269
        //We need factory to create validator
270
        if ($container instanceof FactoryInterface) {
271
            //Shortcut
272
            $factory = $container;
273
        } else {
274
            $factory = $container->get(FactoryInterface::class);
275
        }
276
277
        //Receiving instance of validator from container (todo: improve?)
278
        return $factory->make(ValidatorInterface::class, compact('rules'));
279
    }
280
}