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.
Completed
Push — master ( 2d4913...1eb6a1 )
by De
02:14
created

Structure::reset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
c 0
b 0
f 0
rs 9.4285
cc 1
eloc 4
nc 1
nop 0
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 Sokil\Mongo\Validator;
15
use Sokil\Mongo\Document\InvalidDocumentException;
16
17
class Structure implements
18
    ArrayableInterface,
19
    \JsonSerializable
20
{
21
    /**
22
     * @deprecated use self::$schema to define initial data and getters or setters to get or set field's values.
23
     * @var array
24
     */
25
    protected $_data = array();
26
27
    /**
28
     * Document's data
29
     * @var array
30
     */
31
    private $data = array();
32
33
    /**
34
     * Initial document's data
35
     * @var array
36
     */
37
    protected $schema = array();
38
39
    /**
40
     *
41
     * @var array original data.
42
     */
43
    private $originalData = array();
44
45
    /**
46
     *
47
     * @var array modified fields.
48
     */
49
    private $modifiedFields = array();
50
51
    /**
52
     * Name of scenario, used for validating fields
53
     * @var string
54
     */
55
    private $scenario;
56
57
    /**
58
     *
59
     * @var array list of namespaces
60
     */
61
    private $validatorNamespaces = array(
62
        '\Sokil\Mongo\Validator',
63
    );
64
65
66
    /**
67
     * @var array validator errors
68
     */
69
    private $errors = array();
70
71
    /**
72
     * @var array manually added validator errors
73
     */
74
    private $triggeredErrors = array();
75
76
    /**
77
     * @param array|null $data data to initialise structure
78
     * @param bool $notModified define if data set as modified or not
79
     */
80
    public function __construct(
81
        array $data = null,
82
        $notModified = true
83
    ) {
84
        // 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...
85
        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...
86
            $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...
87
        }
88
89
        $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...
90
91
        // define initial data with schema
92
        $this->data = $this->schema;
93
        $this->originalData = $this->data;
94
95
        // execute before construct callable
96
        $this->beforeConstruct();
97
98
        // initialize with passed data
99
        if ($data) {
100
            if ($notModified) {
101
                // set as not modified
102
                $this->replace($data);
103
            } else {
104
                // set as modified
105
                $this->merge($data);
106
            }
107
        }
108
    }
109
110
    /**
111
     * Event handler, called before running constructor.
112
     * May be overridden in child classes
113
     */
114
    public function beforeConstruct()
115
    {
116
        // override this to add some functionality on structure instance initialization
117
    }
118
119
    /**
120
     * Cloning not allowed because cloning of object not clone related aggregates of this object, so
121
     * cloned object has links to original aggregates. This is difficult to handle.
122
     */
123
    final public function __clone()
124
    {
125
        throw new \RuntimeException('Cloning not allowed');
126
    }
127
128
    /**
129
     * IMPORTANT! Do not use this method
130
     *
131
     * This method allow set data of document in external code.
132
     * e.g. link data of document to GridFS file matadata.
133
     * Modification of document's data will modify external data too.
134
     * Note that also the opposite case also right - modification of external data will
135
     * modify document's data directly, so document may be in unconsisted state.
136
     *
137
     * @param array $data reference to data in external code
138
     * @return Structure
139
     */
140
    protected function setDataReference(array &$data)
141
    {
142
        $this->data = &$data;
143
        return $this;
144
    }
145
146
    public function __get($name)
147
    {
148
        // get first-level value
149
        return isset($this->data[$name]) ? $this->data[$name] : null;
150
    }
151
152
    public function get($selector)
153
    {
154
        if (false === strpos($selector, '.')) {
155
            return isset($this->data[$selector]) ? $this->data[$selector] : null;
156
        }
157
158
        $value = $this->data;
159
        foreach (explode('.', $selector) as $field) {
160
            if (!isset($value[$field])) {
161
                return null;
162
            }
163
164
            $value = $value[$field];
165
        }
166
167
        return $value;
168
    }
169
170
    /**
171
     * Get structure object from a document's value
172
     *
173
     * @param string $selector
174
     * @param string|callable $className string class name or closure, which accept data and return string class name
175
     * @return object representation of document with class, passed as argument
176
     * @throws \Sokil\Mongo\Exception
177
     */
178
    public function getObject($selector, $className = '\Sokil\Mongo\Structure')
179
    {
180
        $data = $this->get($selector);
181
        if (!$data) {
182
            return null;
183
        }
184
185
        // get class name from callable
186
        if (is_callable($className)) {
187
            $className = $className($data);
188
        }
189
190
        // prepare structure
191
        $structure =  new $className();
192
        if (!($structure instanceof Structure)) {
193
            throw new Exception('Wrong structure class specified');
194
        }
195
196
        return $structure->merge($data);
197
    }
198
199
    /**
200
     * Get list of structure objects from list of values in mongo document
201
     *
202
     * @param string $selector
203
     * @param string|callable $className Structure class name or closure, which accept data and return string class name of Structure
204
     * @return object representation of document with class, passed as argument
205
     * @throws \Sokil\Mongo\Exception
206
     */
207
    public function getObjectList($selector, $className = '\Sokil\Mongo\Structure')
208
    {
209
        $data = $this->get($selector);
210
        if (!$data || !is_array($data)) {
211
            return array();
212
        }
213
214
        // class name is string
215 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...
216
            $list = array_map(
217
                function ($dataItem) use ($className) {
218
                    $listItemStructure = new $className();
219
                    if (!($listItemStructure instanceof Structure)) {
220
                        throw new Exception('Wrong structure class specified');
221
                    }
222
                    $listItemStructure->mergeUnmodified($dataItem);
223
                    return $listItemStructure;
224
                },
225
                $data
226
            );
227
228
            return $list;
229
        }
230
231
        // class name id callable
232 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...
233
            return array_map(function ($dataItem) use ($className) {
234
                $classNameString = $className($dataItem);
235
                $listItemStructure = new $classNameString;
236
                if (!($listItemStructure instanceof Structure)) {
237
                    throw new Exception('Wrong structure class specified');
238
                }
239
240
                return $listItemStructure->merge($dataItem);
241
            }, $data);
242
        }
243
244
        throw new Exception('Wrong class name specified. Use string or closure');
245
    }
246
247
    /**
248
     * Handle setting params through public property
249
     *
250
     * @param string $name
251
     * @param mixed $value
252
     */
253
    public function __set($name, $value)
254
    {
255
        $this->set($name, $value);
256
    }
257
258
    /**
259
     * Store value to specified selector in local cache
260
     *
261
     * @param string $selector point-delimited field selector
262
     * @param mixed $value value
263
     * @return \Sokil\Mongo\Document
264
     * @throws Exception
265
     */
266
    public function set($selector, $value)
267
    {
268
        $value = self::prepareToStore($value);
269
270
        // modify
271
        $arraySelector = explode('.', $selector);
272
        $chunksNum = count($arraySelector);
273
274
        // optimize one-level selector search
275
        if (1 == $chunksNum) {
276
            // update only if new value different from current
277
            if (!isset($this->data[$selector]) || $this->data[$selector] !== $value) {
278
                // modify
279
                $this->data[$selector] = $value;
280
                // mark field as modified
281
                $this->modifiedFields[] = $selector;
282
            }
283
284
            return $this;
285
        }
286
287
        // selector is nested
288
        $section = &$this->data;
289
290
        for ($i = 0; $i < $chunksNum - 1; $i++) {
291
            $field = $arraySelector[$i];
292
293
            if (!isset($section[$field])) {
294
                $section[$field] = array();
295
            } elseif (!is_array($section[$field])) {
296
                throw new Exception('Assigning sub-document to scalar value not allowed');
297
            }
298
299
            $section = &$section[$field];
300
        }
301
302
        // update only if new value different from current
303
        if (!isset($section[$arraySelector[$chunksNum - 1]]) || $section[$arraySelector[$chunksNum - 1]] !== $value) {
304
            // modify
305
            $section[$arraySelector[$chunksNum - 1]] = $value;
306
            // mark field as modified
307
            $this->modifiedFields[] = $selector;
308
        }
309
310
        return $this;
311
    }
312
313
    public function has($selector)
314
    {
315
        $pointer = &$this->data;
316
317
        foreach (explode('.', $selector) as $field) {
318
            if (!array_key_exists($field, $pointer)) {
319
                return false;
320
            }
321
322
            $pointer = &$pointer[$field];
323
        }
324
325
        return true;
326
    }
327
328
    public function __isset($name)
329
    {
330
        return isset($this->data[$name]);
331
    }
332
333
    public static function prepareToStore($value)
334
    {
335
        // if array - try to prepare every value
336
        if (is_array($value)) {
337
            foreach ($value as $k => $v) {
338
                $value[$k] = self::prepareToStore($v);
339
            }
340
341
            return $value;
342
        }
343
344
        // if scalar - return it
345
        if (!is_object($value)) {
346
            return $value;
347
        }
348
349
        // if internal mongo types - pass it as is
350
        if (in_array(get_class($value), array('MongoId', 'MongoCode', 'MongoDate', 'MongoRegex', 'MongoBinData', 'MongoInt32', 'MongoInt64', 'MongoDBRef', 'MongoMinKey', 'MongoMaxKey', 'MongoTimestamp'))) {
351
            return $value;
352
        }
353
354
        // do not convert geo-json to array
355
        if ($value instanceof \GeoJson\Geometry\Geometry) {
356
            return $value->jsonSerialize();
357
        }
358
359
        // structure
360
        if ($value instanceof Structure) {
361
            // validate structure
362
            if (!$value->isValid()) {
363
                $exception = new InvalidDocumentException('Embedded document not valid');
364
                $exception->setDocument($value);
365
                throw $exception;
366
            }
367
368
            // get array from structure
369
            return $value->toArray();
370
        }
371
372
        // other objects convert to array
373
        return (array) $value;
374
    }
375
376
    public function unsetField($selector)
377
    {
378
        // modify
379
        $arraySelector = explode('.', $selector);
380
        $chunksNum = count($arraySelector);
381
382
        // optimize one-level selector search
383
        if (1 == $chunksNum) {
384
            // check if field exists
385
            if (isset($this->data[$selector])) {
386
                // unset field
387
                unset($this->data[$selector]);
388
                // mark field as modified
389
                $this->modifiedFields[] = $selector;
390
            }
391
392
            return $this;
393
        }
394
395
        // find section
396
        $section = &$this->data;
397
398
        for ($i = 0; $i < $chunksNum - 1; $i++) {
399
            $field = $arraySelector[$i];
400
401
            if (!isset($section[$field])) {
402
                return $this;
403
            }
404
405
            $section = &$section[$field];
406
        }
407
408
        // check if field exists
409
        if (isset($section[$arraySelector[$chunksNum - 1]])) {
410
            // unset field
411
            unset($section[$arraySelector[$chunksNum - 1]]);
412
            // mark field as modified
413
            $this->modifiedFields[] = $selector;
414
        }
415
416
        return $this;
417
    }
418
419
    /**
420
     * If field not exist - set value.
421
     * If field exists and is not array - convert to array and append
422
     * If field -s array - append
423
     *
424
     * @param string $selector
425
     * @param mixed $value
426
     * @return \Sokil\Mongo\Structure
427
     */
428
    public function append($selector, $value)
429
    {
430
        $oldValue = $this->get($selector);
431
        if ($oldValue) {
432
            if (!is_array($oldValue)) {
433
                $oldValue = (array) $oldValue;
434
            }
435
            $oldValue[] = $value;
436
            $value = $oldValue;
437
        }
438
439
        $this->set($selector, $value);
440
        return $this;
441
    }
442
443
    /**
444
     * If selector passed, return true if field is modified.
445
     * If selector omitted, return true if document is modified.
446
     *
447
     * @param string|null $selector
448
     * @return bool
449
     */
450
    public function isModified($selector = null)
451
    {
452
        if (empty($this->modifiedFields)) {
453
            return false;
454
        }
455
456
        if (empty($selector)) {
457
            return (bool) $this->modifiedFields;
458
        }
459
460
        foreach ($this->modifiedFields as $modifiedField) {
461
            if (preg_match('/^' . $selector . '($|.)/', $modifiedField)) {
462
                return true;
463
            }
464
        }
465
466
        return false;
467
    }
468
469
    public function getModifiedFields()
470
    {
471
        return $this->modifiedFields;
472
    }
473
474
    public function getOriginalData()
475
    {
476
        return $this->originalData;
477
    }
478
479
    public function toArray()
480
    {
481
        return $this->data;
482
    }
483
484
    public function jsonSerialize()
485
    {
486
        return $this->data;
487
    }
488
489
    /**
490
     * Recursive function to merge data for Structure::mergeUnmodified()
491
     *
492
     * @param array $target
493
     * @param array $source
494
     */
495
    private function mergeUnmodifiedPartial(array &$target, array $source)
496
    {
497
        foreach ($source as $key => $value) {
498
            if (is_array($value) && isset($target[$key])) {
499
                $this->mergeUnmodifiedPartial($target[$key], $value);
500
            } else {
501
                $target[$key] = $value;
502
            }
503
        }
504
    }
505
506
    /**
507
     * Merge array to current structure without setting modification mark
508
     *
509
     * @param array $data
510
     * @return \Sokil\Mongo\Structure
511
     */
512
    public function mergeUnmodified(array $data)
513
    {
514
        $this->mergeUnmodifiedPartial($this->data, $data);
515
        $this->mergeUnmodifiedPartial($this->originalData, $data);
516
517
        return $this;
518
    }
519
520
    /**
521
     * Check if array is sequential list
522
     * @param array $array
523
     */
524
    private function isEmbeddedDocument($array)
525
    {
526
        return is_array($array) && (array_values($array) !== $array);
527
    }
528
529
    /**
530
     * Recursive function to merge data for Structure::merge()
531
     *
532
     * @param array $document
533
     * @param array $updatedDocument
534
     * @param string $prefix
535
     */
536
    private function mergePartial(array &$document, array $updatedDocument, $prefix = null)
537
    {
538
        foreach ($updatedDocument as $key => $newValue) {
539
            // if original data is embedded document and value also - then merge
540
            if (is_array($newValue) && isset($document[$key]) && $this->isEmbeddedDocument($document[$key])) {
541
                $this->mergePartial($document[$key], $newValue, $prefix . $key . '.');
542
            } // in other cases just set new value
543
            else {
544
                $document[$key] = $newValue;
545
                $this->modifiedFields[] = $prefix . $key;
546
            }
547
        }
548
    }
549
550
    /**
551
     * Merge array to current structure with setting modification mark
552
     *
553
     * @param array $data
554
     * @return \Sokil\Mongo\Structure
555
     */
556
    public function merge(array $data)
557
    {
558
        $this->mergePartial($this->data, $data);
559
        return $this;
560
    }
561
562
    /**
563
     * Replace data of document with passed.
564
     * Document became unmodified
565
     *
566
     * @param array $data new document data
567
     */
568
    public function replace(array $data)
569
    {
570
        $this->data = $data;
571
        $this->apply();
572
573
        return $this;
574
    }
575
576
    /**
577
     * Replace modified fields with original
578
     * @return $this
579
     */
580
    public function reset()
581
    {
582
        $this->data = $this->originalData;
583
        $this->modifiedFields = array();
584
585
        return $this;
586
    }
587
588
    /**
589
     * Apply modified document fields as original
590
     *
591
     * @return $this
592
     */
593
    protected function apply()
594
    {
595
        $this->originalData = $this->data;
596
        $this->modifiedFields = array();
597
598
        return $this;
599
    }
600
601
    /**
602
     * Validation rules
603
     * @return array
604
     */
605
    public function rules()
606
    {
607
        return array();
608
    }
609
610
    public function setScenario($scenario)
611
    {
612
        $this->scenario = $scenario;
613
        return $this;
614
    }
615
616
    public function getScenario()
617
    {
618
        return $this->scenario;
619
    }
620
621
    public function setNoScenario()
622
    {
623
        $this->scenario = null;
624
        return $this;
625
    }
626
627
    public function isScenario($scenario)
628
    {
629
        return $scenario === $this->scenario;
630
    }
631
632
    public function hasErrors()
633
    {
634
        return (!empty($this->errors) || !empty($this->triggeredErrors));
635
    }
636
637
    /**
638
     * get list of validation errors
639
     *
640
     * Format: $errors['fieldName']['rule'] = 'message';
641
     *
642
     * @return array list of validation errors
643
     */
644
    public function getErrors()
645
    {
646
        return array_merge_recursive($this->errors, $this->triggeredErrors);
647
    }
648
649
    /**
650
     * Add validator error from validator classes and methods. This error
651
     * reset on every re-validation
652
     *
653
     * @param string $fieldName dot-notated field name
654
     * @param string $ruleName name of validation rule
655
     * @param string $message error message
656
     * @return \Sokil\Mongo\Document
657
     */
658
    public function addError($fieldName, $ruleName, $message)
659
    {
660
        $this->errors[$fieldName][$ruleName] = $message;
661
662
        return $this;
663
    }
664
665
    /**
666
     * Add errors
667
     *
668
     * @param array $errors
669
     * @return \Sokil\Mongo\Document
670
     */
671
    public function addErrors(array $errors)
672
    {
673
        $this->errors = array_merge_recursive($this->errors, $errors);
674
        return $this;
675
    }
676
677
    /**
678
     * Add custom error which not reset after validation
679
     *
680
     * @param string $fieldName
681
     * @param string $ruleName
682
     * @param string $message
683
     *
684
     * @return \Sokil\Mongo\Document
685
     */
686
    public function triggerError($fieldName, $ruleName, $message)
687
    {
688
        $this->triggeredErrors[$fieldName][$ruleName] = $message;
689
        return $this;
690
    }
691
692
    /**
693
     * Add custom errors
694
     *
695
     * @param array $errors
696
     * @return \Sokil\Mongo\Document
697
     */
698
    public function triggerErrors(array $errors)
699
    {
700
        $this->triggeredErrors = array_merge_recursive($this->triggeredErrors, $errors);
701
        return $this;
702
    }
703
704
    /**
705
     * Clear triggered and validation errors
706
     * @return $this
707
     */
708
    public function clearErrors()
709
    {
710
        $this->errors = array();
711
        $this->triggeredErrors = array();
712
        return $this;
713
    }
714
715
    /**
716
     * Remove custom errors
717
     *
718
     * @return \Sokil\Mongo\Document
719
     */
720
    public function clearTriggeredErrors()
721
    {
722
        $this->triggeredErrors = array();
723
        return $this;
724
    }
725
726
    /**
727
     * Add own namespace of validators
728
     *
729
     * @param string $namespace
730
     *
731
     * @return Structure
732
     */
733
    public function addValidatorNamespace($namespace)
734
    {
735
        $this->validatorNamespaces[] = rtrim($namespace, '\\');
736
        return $this;
737
    }
738
739
    private function getValidatorClassNameByRuleName($ruleName)
740
    {
741
        if (false !== strpos($ruleName, '_')) {
742
            $className = implode('', array_map('ucfirst', explode('_', strtolower($ruleName))));
743
        } else {
744
            $className = ucfirst(strtolower($ruleName));
745
        }
746
747
        foreach ($this->validatorNamespaces as $namespace) {
748
            $fullyQualifiedClassName = $namespace . '\\' . $className . 'Validator';
749
            if (class_exists($fullyQualifiedClassName)) {
750
                return $fullyQualifiedClassName;
751
            }
752
        }
753
754
        throw new Exception('Validator with name ' . $ruleName . ' not found');
755
    }
756
757
    /**
758
     * check if filled model params is valid
759
     * @return boolean
760
     */
761
    public function isValid()
762
    {
763
        $this->errors = array();
764
765
        foreach ($this->rules() as $rule) {
766
            $fields = array_map('trim', explode(',', $rule[0]));
767
            $ruleName = $rule[1];
768
            $params = array_slice($rule, 2);
769
770
            // check scenario
771 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...
772
                $onScenarios = explode(',', $rule['on']);
773
                if (!in_array($this->getScenario(), $onScenarios)) {
774
                    continue;
775
                }
776
            }
777
778 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...
779
                $exceptScenarios = explode(',', $rule['except']);
780
                if (in_array($this->getScenario(), $exceptScenarios)) {
781
                    continue;
782
                }
783
            }
784
785
            if (method_exists($this, $ruleName)) {
786
                // method
787
                foreach ($fields as $field) {
788
                    $this->{$ruleName}($field, $params);
789
                }
790
            } else {
791
                // validator class
792
                $validatorClassName = $this->getValidatorClassNameByRuleName($ruleName);
793
794
                /* @var $validator \Sokil\Mongo\Validator */
795
                $validator = new $validatorClassName;
796
                if (!$validator instanceof Validator) {
797
                    throw new Exception('Validator class must implement \Sokil\Mongo\Validator class');
798
                }
799
800
                $validator->validate($this, $fields, $params);
801
            }
802
        }
803
804
        return !$this->hasErrors();
805
    }
806
}
807