GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Structure::getValidatorClassNameByRuleName()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
c 0
b 0
f 0
rs 9.2
cc 4
eloc 10
nc 6
nop 1
1
<?php
2
3
/**
4
 * This file is part of the PHPMongo package.
5
 *
6
 * (c) Dmytro Sokil <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sokil\Mongo;
13
14
use GeoJson\Geometry\Geometry;
15
use Sokil\Mongo\Document\InvalidDocumentException;
16
use Sokil\Mongo\Type\TypeChecker;
17
18
class Structure implements
19
    ArrayableInterface,
20
    \JsonSerializable
21
{
22
    /**
23
     * @deprecated use self::$schema to define initial data and getters or setters to get or set field's values.
24
     * @var array
25
     */
26
    protected $_data = array();
27
28
    /**
29
     * Document's data
30
     * @var array
31
     */
32
    private $data = array();
33
34
    /**
35
     * Initial document's data
36
     * @var array
37
     */
38
    protected $schema = array();
39
40
    /**
41
     *
42
     * @var array original data.
43
     */
44
    private $originalData = array();
45
46
    /**
47
     *
48
     * @var array modified fields.
49
     */
50
    private $modifiedFields = array();
51
52
    /**
53
     * Name of scenario, used for validating fields
54
     * @var string
55
     */
56
    private $scenario;
57
58
    /**
59
     *
60
     * @var array list of namespaces
61
     */
62
    private $validatorNamespaces = array(
63
        '\Sokil\Mongo\Validator',
64
    );
65
66
67
    /**
68
     * @var array validator errors
69
     */
70
    private $errors = array();
71
72
    /**
73
     * @var array manually added validator errors
74
     */
75
    private $triggeredErrors = array();
76
77
    /**
78
     * @param array|null $data data to initialise structure
79
     * @param bool $notModified define if data set as modified or not
80
     */
81
    public function __construct(
82
        array $data = null,
83
        $notModified = true
84
    ) {
85
        // self::$data and self::$schema instead of deprecated self::$_data
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
86
        if (!empty($this->_data)) {
0 ignored issues
show
Deprecated Code introduced by
The property Sokil\Mongo\Structure::$_data has been deprecated with message: use self::$schema to define initial data and getters or setters to get or set field's values.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
87
            $this->schema = $this->_data;
0 ignored issues
show
Deprecated Code introduced by
The property Sokil\Mongo\Structure::$_data has been deprecated with message: use self::$schema to define initial data and getters or setters to get or set field's values.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
88
        }
89
90
        $this->_data = &$this->data;
0 ignored issues
show
Deprecated Code introduced by
The property Sokil\Mongo\Structure::$_data has been deprecated with message: use self::$schema to define initial data and getters or setters to get or set field's values.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
91
92
        // define initial data with schema
93
        $this->data = $this->schema;
94
        $this->originalData = $this->data;
95
96
        // execute before construct callable
97
        $this->beforeConstruct();
98
99
        // initialize with passed data
100
        if ($data) {
101
            if ($notModified) {
102
                // set as not modified
103
                $this->replace($data);
104
            } else {
105
                // set as modified
106
                $this->merge($data);
107
            }
108
        }
109
    }
110
111
    /**
112
     * Event handler, called before running constructor.
113
     * May be overridden in child classes
114
     */
115
    public function beforeConstruct()
116
    {
117
        // override this to add some functionality on structure instance initialization
118
    }
119
120
    /**
121
     * Cloning not allowed because cloning of object not clone related aggregates of this object, so
122
     * cloned object has links to original aggregates. This is difficult to handle.
123
     */
124
    final public function __clone()
125
    {
126
        throw new \RuntimeException('Cloning not allowed');
127
    }
128
129
    /**
130
     * IMPORTANT! Do not use this method
131
     *
132
     * This method allow set data of document in external code.
133
     * e.g. link data of document to GridFS file matadata.
134
     * Modification of document's data will modify external data too.
135
     * Note that also the opposite case also right - modification of external data will
136
     * modify document's data directly, so document may be in unconsisted state.
137
     *
138
     * @param array $data reference to data in external code
139
     * @return Structure
140
     */
141
    protected function setDataReference(array &$data)
142
    {
143
        $this->data = &$data;
144
        return $this;
145
    }
146
147
    public function __get($name)
148
    {
149
        // get first-level value
150
        return isset($this->data[$name]) ? $this->data[$name] : null;
151
    }
152
153
    /**
154
     * @param $selector
155
     * @return mixed
156
     */
157
    public function get($selector)
158
    {
159
        if (false === strpos($selector, '.')) {
160
            return isset($this->data[$selector]) ? $this->data[$selector] : null;
161
        }
162
163
        $value = $this->data;
164
        foreach (explode('.', $selector) as $field) {
165
            if (!isset($value[$field])) {
166
                return null;
167
            }
168
169
            $value = $value[$field];
170
        }
171
172
        return $value;
173
    }
174
175
    /**
176
     * Get structure object from a document's value
177
     *
178
     * @param string $selector
179
     * @param string|callable $className string class name or closure, which accept data and return string class name
180
     * @return object representation of document with class, passed as argument
181
     * @throws \Sokil\Mongo\Exception
182
     */
183
    public function getObject($selector, $className = '\Sokil\Mongo\Structure')
184
    {
185
        $data = $this->get($selector);
186
        if (!$data) {
187
            return null;
188
        }
189
190
        // get class name from callable
191
        if (is_callable($className)) {
192
            $className = $className($data);
193
        }
194
195
        // prepare structure
196
        $structure =  new $className();
197
        if (!($structure instanceof Structure)) {
198
            throw new Exception('Wrong structure class specified');
199
        }
200
201
        return $structure->merge($data);
202
    }
203
204
    /**
205
     * Get list of structure objects from list of values in mongo document
206
     *
207
     * @param string $selector
208
     * @param string|callable $className Structure class name or closure, which accept data and return string class name of Structure
209
     *
210
     * @return object|array representation of document with class, passed as argument
211
     *
212
     * @throws \Sokil\Mongo\Exception
213
     */
214
    public function getObjectList($selector, $className = '\Sokil\Mongo\Structure')
215
    {
216
        $data = $this->get($selector);
217
        if (!$data || !is_array($data)) {
218
            return array();
219
        }
220
221
        // class name is string
222 View Code Duplication
        if (is_string($className)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
223
            $list = array_map(
224
                function ($dataItem) use ($className) {
225
                    $listItemStructure = new $className();
226
                    if (!($listItemStructure instanceof Structure)) {
227
                        throw new Exception('Wrong structure class specified');
228
                    }
229
                    $listItemStructure->mergeUnmodified($dataItem);
230
                    return $listItemStructure;
231
                },
232
                $data
233
            );
234
235
            return $list;
236
        }
237
238
        // class name id callable
239 View Code Duplication
        if (is_callable($className)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
240
            return array_map(function ($dataItem) use ($className) {
241
                $classNameString = $className($dataItem);
242
                $listItemStructure = new $classNameString;
243
                if (!($listItemStructure instanceof Structure)) {
244
                    throw new Exception('Wrong structure class specified');
245
                }
246
247
                return $listItemStructure->merge($dataItem);
248
            }, $data);
249
        }
250
251
        throw new Exception('Wrong class name specified. Use string or closure');
252
    }
253
254
    /**
255
     * Handle setting params through public property
256
     *
257
     * @param string $name
258
     * @param mixed $value
259
     */
260
    public function __set($name, $value)
261
    {
262
        $this->set($name, $value);
263
    }
264
265
    /**
266
     * Store value to specified selector in local cache
267
     *
268
     * @param string $selector point-delimited field selector
269
     * @param mixed $value value
270
     *
271
     * @return Structure
272
     *
273
     * @throws Exception
274
     */
275
    public function set($selector, $value)
276
    {
277
        $value = self::prepareToStore($value);
278
279
        // modify
280
        $arraySelector = explode('.', $selector);
281
        $chunksNum = count($arraySelector);
282
283
        // optimize one-level selector search
284
        if (1 == $chunksNum) {
285
            // update only if new value different from current
286
            if (!isset($this->data[$selector]) || $this->data[$selector] !== $value) {
287
                // modify
288
                $this->data[$selector] = $value;
289
                // mark field as modified
290
                $this->modifiedFields[] = $selector;
291
            }
292
293
            return $this;
294
        }
295
296
        // selector is nested
297
        $section = &$this->data;
298
299
        for ($i = 0; $i < $chunksNum - 1; $i++) {
300
            $field = $arraySelector[$i];
301
302
            if (!isset($section[$field])) {
303
                $section[$field] = array();
304
            } elseif (!is_array($section[$field])) {
305
                throw new Exception('Assigning sub-document to scalar value not allowed');
306
            }
307
308
            $section = &$section[$field];
309
        }
310
311
        // update only if new value different from current
312
        if (!isset($section[$arraySelector[$chunksNum - 1]]) || $section[$arraySelector[$chunksNum - 1]] !== $value) {
313
            // modify
314
            $section[$arraySelector[$chunksNum - 1]] = $value;
315
            // mark field as modified
316
            $this->modifiedFields[] = $selector;
317
        }
318
319
        return $this;
320
    }
321
322
    /**
323
     * Check if structure has field identified by selector
324
     *
325
     * @param string $selector
326
     *
327
     * @return bool
328
     */
329
    public function has($selector)
330
    {
331
        $pointer = &$this->data;
332
333
        foreach (explode('.', $selector) as $field) {
334
            if (!array_key_exists($field, $pointer)) {
335
                return false;
336
            }
337
338
            $pointer = &$pointer[$field];
339
        }
340
341
        return true;
342
    }
343
344
    /**
345
     * @param string $name
346
     *
347
     * @return bool
348
     */
349
    public function __isset($name)
350
    {
351
        return isset($this->data[$name]);
352
    }
353
354
    /**
355
     * Normalize variable to be able to store in database
356
     *
357
     * @param mixed $value
358
     *
359
     * @return array
360
     *
361
     * @throws InvalidDocumentException
362
     */
363
    public static function prepareToStore($value)
364
    {
365
        // if array - try to prepare every value
366
        if (is_array($value)) {
367
            foreach ($value as $k => $v) {
368
                $value[$k] = self::prepareToStore($v);
369
            }
370
371
            return $value;
372
        }
373
374
        // if scalar - return it
375
        if (!is_object($value)) {
376
            return $value;
377
        }
378
379
        // if internal mongo types - pass it as is
380
        if (TypeChecker::isInternalType($value)) {
381
            return $value;
382
        }
383
384
        // do not convert geo-json to array
385
        if ($value instanceof Geometry) {
386
            return $value->jsonSerialize();
387
        }
388
389
        // structure
390
        if ($value instanceof Structure) {
391
            // validate structure
392
            if (!$value->isValid()) {
393
                $exception = new InvalidDocumentException('Embedded document not valid');
394
                $exception->setDocument($value);
395
                throw $exception;
396
            }
397
398
            // get array from structure
399
            return $value->toArray();
400
        }
401
402
        // other objects convert to array
403
        return (array) $value;
404
    }
405
406
    public function unsetField($selector)
407
    {
408
        // modify
409
        $arraySelector = explode('.', $selector);
410
        $chunksNum = count($arraySelector);
411
412
        // optimize one-level selector search
413
        if (1 == $chunksNum) {
414
            // check if field exists
415
            if (isset($this->data[$selector])) {
416
                // unset field
417
                unset($this->data[$selector]);
418
                // mark field as modified
419
                $this->modifiedFields[] = $selector;
420
            }
421
422
            return $this;
423
        }
424
425
        // find section
426
        $section = &$this->data;
427
428
        for ($i = 0; $i < $chunksNum - 1; $i++) {
429
            $field = $arraySelector[$i];
430
431
            if (!isset($section[$field])) {
432
                return $this;
433
            }
434
435
            $section = &$section[$field];
436
        }
437
438
        // check if field exists
439
        if (isset($section[$arraySelector[$chunksNum - 1]])) {
440
            // unset field
441
            unset($section[$arraySelector[$chunksNum - 1]]);
442
            // mark field as modified
443
            $this->modifiedFields[] = $selector;
444
        }
445
446
        return $this;
447
    }
448
449
    /**
450
     * If field not exist - set value.
451
     * If field exists and is not array - convert to array and append
452
     * If field -s array - append
453
     *
454
     * @param string $selector
455
     * @param mixed $value
456
     * @return \Sokil\Mongo\Structure
457
     */
458
    public function append($selector, $value)
459
    {
460
        $oldValue = $this->get($selector);
461
        if ($oldValue) {
462
            if (!is_array($oldValue)) {
463
                $oldValue = (array) $oldValue;
464
            }
465
            $oldValue[] = $value;
466
            $value = $oldValue;
467
        }
468
469
        $this->set($selector, $value);
470
        return $this;
471
    }
472
473
    /**
474
     * If selector passed, return true if field is modified.
475
     * If selector omitted, return true if document is modified.
476
     *
477
     * @param string|null $selector
478
     * @return bool
479
     */
480
    public function isModified($selector = null)
481
    {
482
        if (empty($this->modifiedFields)) {
483
            return false;
484
        }
485
486
        if (empty($selector)) {
487
            return (bool) $this->modifiedFields;
488
        }
489
490
        foreach ($this->modifiedFields as $modifiedField) {
491
            if (preg_match('/^' . $selector . '($|.)/', $modifiedField)) {
492
                return true;
493
            }
494
        }
495
496
        return false;
497
    }
498
499
    /**
500
     * @return array
501
     */
502
    public function getModifiedFields()
503
    {
504
        return $this->modifiedFields;
505
    }
506
507
    /**
508
     * @return array
509
     */
510
    public function getOriginalData()
511
    {
512
        return $this->originalData;
513
    }
514
515
    public function toArray()
516
    {
517
        return $this->data;
518
    }
519
520
    public function jsonSerialize()
521
    {
522
        return $this->data;
523
    }
524
525
    /**
526
     * Recursive function to merge data for Structure::mergeUnmodified()
527
     *
528
     * @param array $target
529
     * @param array $source
530
     */
531
    private function mergeUnmodifiedPartial(array &$target, array $source)
532
    {
533
        foreach ($source as $key => $value) {
534
            if (is_array($value) && isset($target[$key])) {
535
                $this->mergeUnmodifiedPartial($target[$key], $value);
536
            } else {
537
                $target[$key] = $value;
538
            }
539
        }
540
    }
541
542
    /**
543
     * Merge array to current structure without setting modification mark
544
     *
545
     * @param array $data
546
     * @return \Sokil\Mongo\Structure
547
     */
548
    public function mergeUnmodified(array $data)
549
    {
550
        $this->mergeUnmodifiedPartial($this->data, $data);
551
        $this->mergeUnmodifiedPartial($this->originalData, $data);
552
553
        return $this;
554
    }
555
556
    /**
557
     * Check if array is sequential list
558
     * @param array $array
559
     */
560
    private function isEmbeddedDocument($array)
561
    {
562
        return is_array($array) && (array_values($array) !== $array);
563
    }
564
565
    /**
566
     * Recursive function to merge data for Structure::merge()
567
     *
568
     * @param array $document
569
     * @param array $updatedDocument
570
     * @param string $prefix
571
     */
572
    private function mergePartial(array &$document, array $updatedDocument, $prefix = null)
573
    {
574
        foreach ($updatedDocument as $key => $newValue) {
575
            // if original data is embedded document and value also - then merge
576
            if (is_array($newValue) && isset($document[$key]) && $this->isEmbeddedDocument($document[$key])) {
577
                $this->mergePartial($document[$key], $newValue, $prefix . $key . '.');
578
            } // in other cases just set new value
579
            else {
580
                $document[$key] = $newValue;
581
                $this->modifiedFields[] = $prefix . $key;
582
            }
583
        }
584
    }
585
586
    /**
587
     * Merge array to current structure with setting modification mark
588
     *
589
     * @param array $data
590
     * @return \Sokil\Mongo\Structure
591
     */
592
    public function merge(array $data)
593
    {
594
        $this->mergePartial($this->data, $data);
595
        return $this;
596
    }
597
598
    /**
599
     * Replace data of document with passed.
600
     * Document became unmodified
601
     *
602
     * @param array $data new document data
603
     */
604
    public function replace(array $data)
605
    {
606
        $this->data = $data;
607
        $this->apply();
608
609
        return $this;
610
    }
611
612
    /**
613
     * Replace modified fields with original
614
     * @return $this
615
     */
616
    public function reset()
617
    {
618
        $this->data = $this->originalData;
619
        $this->modifiedFields = array();
620
621
        return $this;
622
    }
623
624
    /**
625
     * Apply modified document fields as original
626
     *
627
     * @return $this
628
     */
629
    protected function apply()
630
    {
631
        $this->originalData = $this->data;
632
        $this->modifiedFields = array();
633
634
        return $this;
635
    }
636
637
    /**
638
     * Validation rules
639
     * @return array
640
     */
641
    public function rules()
642
    {
643
        return array();
644
    }
645
646
    public function setScenario($scenario)
647
    {
648
        $this->scenario = $scenario;
649
        return $this;
650
    }
651
652
    public function getScenario()
653
    {
654
        return $this->scenario;
655
    }
656
657
    public function setNoScenario()
658
    {
659
        $this->scenario = null;
660
        return $this;
661
    }
662
663
    public function isScenario($scenario)
664
    {
665
        return $scenario === $this->scenario;
666
    }
667
668
    public function hasErrors()
669
    {
670
        return (!empty($this->errors) || !empty($this->triggeredErrors));
671
    }
672
673
    /**
674
     * get list of validation errors
675
     *
676
     * Format: $errors['fieldName']['rule'] = 'message';
677
     *
678
     * @return array list of validation errors
679
     */
680
    public function getErrors()
681
    {
682
        return array_merge_recursive($this->errors, $this->triggeredErrors);
683
    }
684
685
    /**
686
     * Add validator error from validator classes and methods. This error
687
     * reset on every re-validation
688
     *
689
     * @param string $fieldName dot-notated field name
690
     * @param string $ruleName name of validation rule
691
     * @param string $message error message
692
     *
693
     * @return Structure
694
     */
695
    public function addError($fieldName, $ruleName, $message)
696
    {
697
        $this->errors[$fieldName][$ruleName] = $message;
698
699
        return $this;
700
    }
701
702
    /**
703
     * Add errors
704
     *
705
     * @param array $errors
706
     *
707
     * @return Structure
708
     */
709
    public function addErrors(array $errors)
710
    {
711
        $this->errors = array_merge_recursive($this->errors, $errors);
712
713
        return $this;
714
    }
715
716
    /**
717
     * Add custom error which not reset after validation
718
     *
719
     * @param string $fieldName
720
     * @param string $ruleName
721
     * @param string $message
722
     *
723
     * @return \Sokil\Mongo\Document
724
     */
725
    public function triggerError($fieldName, $ruleName, $message)
726
    {
727
        $this->triggeredErrors[$fieldName][$ruleName] = $message;
728
        return $this;
729
    }
730
731
    /**
732
     * Add custom errors
733
     *
734
     * @param array $errors
735
     * @return \Sokil\Mongo\Document
736
     */
737
    public function triggerErrors(array $errors)
738
    {
739
        $this->triggeredErrors = array_merge_recursive($this->triggeredErrors, $errors);
740
        return $this;
741
    }
742
743
    /**
744
     * Clear triggered and validation errors
745
     * @return $this
746
     */
747
    public function clearErrors()
748
    {
749
        $this->errors = array();
750
        $this->triggeredErrors = array();
751
        return $this;
752
    }
753
754
    /**
755
     * Remove custom errors
756
     *
757
     * @return \Sokil\Mongo\Document
758
     */
759
    public function clearTriggeredErrors()
760
    {
761
        $this->triggeredErrors = array();
762
        return $this;
763
    }
764
765
    /**
766
     * Add own namespace of validators
767
     *
768
     * @param string $namespace
769
     *
770
     * @return Structure
771
     */
772
    public function addValidatorNamespace($namespace)
773
    {
774
        $this->validatorNamespaces[] = rtrim($namespace, '\\');
775
        return $this;
776
    }
777
778
    private function getValidatorClassNameByRuleName($ruleName)
779
    {
780
        if (false !== strpos($ruleName, '_')) {
781
            $className = implode('', array_map('ucfirst', explode('_', strtolower($ruleName))));
782
        } else {
783
            $className = ucfirst(strtolower($ruleName));
784
        }
785
786
        foreach ($this->validatorNamespaces as $namespace) {
787
            $fullyQualifiedClassName = $namespace . '\\' . $className . 'Validator';
788
            if (class_exists($fullyQualifiedClassName)) {
789
                return $fullyQualifiedClassName;
790
            }
791
        }
792
793
        throw new Exception('Validator with name ' . $ruleName . ' not found');
794
    }
795
796
    /**
797
     * Check if filled model params is valid
798
     *
799
     * @return boolean
800
     *
801
     * @throws Exception
802
     */
803
    public function isValid()
804
    {
805
        $this->errors = array();
806
807
        foreach ($this->rules() as $rule) {
808
            $fields = array_map('trim', explode(',', $rule[0]));
809
            $ruleName = $rule[1];
810
            $params = array_slice($rule, 2);
811
812
            // check scenario
813 View Code Duplication
            if (!empty($rule['on'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
814
                $onScenarios = explode(',', $rule['on']);
815
                if (!in_array($this->getScenario(), $onScenarios)) {
816
                    continue;
817
                }
818
            }
819
820 View Code Duplication
            if (!empty($rule['except'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
821
                $exceptScenarios = explode(',', $rule['except']);
822
                if (in_array($this->getScenario(), $exceptScenarios)) {
823
                    continue;
824
                }
825
            }
826
827
            if (method_exists($this, $ruleName)) {
828
                // method
829
                foreach ($fields as $field) {
830
                    $this->{$ruleName}($field, $params);
831
                }
832
            } else {
833
                // validator class
834
                $validatorClassName = $this->getValidatorClassNameByRuleName($ruleName);
835
836
                /* @var $validator \Sokil\Mongo\Validator */
837
                $validator = new $validatorClassName;
838
                if (!$validator instanceof Validator) {
839
                    throw new Exception('Validator class must implement \Sokil\Mongo\Validator class');
840
                }
841
842
                $validator->validate($this, $fields, $params);
843
            }
844
        }
845
846
        return !$this->hasErrors();
847
    }
848
}
849