Completed
Push — feature/EVO-6307-record-origin... ( 797eb1...d3f12f )
by Narcotic
13:27
created

RecordOriginConstraint   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 161
Duplicated Lines 6.21 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 13.33%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 17
c 1
b 0
f 1
lcom 1
cbo 3
dl 10
loc 161
ccs 10
cts 75
cp 0.1333
rs 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
C checkRecordOrigin() 10 79 11
B addProperties() 0 25 5

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * Schema constraint that validates the rules of recordOrigin (and possible exceptions)
4
 */
5
6
namespace Graviton\SchemaBundle\Constraint;
7
8
use Graviton\JsonSchemaBundle\Validator\Constraint\Event\ConstraintEventSchema;
9
use Symfony\Component\PropertyAccess\PropertyAccess;
10
11
/**
12
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
13
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
14
 * @link     http://swisscom.ch
15
 */
16
class RecordOriginConstraint
17
{
18
19
    /**
20
     * @var string
21
     */
22
    private $recordOriginField;
23
24
    /**
25
     * @var array
26
     */
27
    private $recordOriginBlacklist;
28
29
    /**
30
     * @var array
31
     */
32
    private $exceptionFieldMap;
33
34
    /**
35
     * RecordOriginConstraint constructor.
36
     *
37
     * @param ConstraintUtils $utils                 Utils
38
     * @param string          $recordOriginField     name of the recordOrigin field
39
     * @param array           $recordOriginBlacklist list of recordOrigin values that cannot be modified
40
     * @param array           $exceptionFieldMap     field map from compiler pass with excluded fields
41
     */
42 4
    public function __construct(
43
        ConstraintUtils $utils,
44
        $recordOriginField,
45
        array $recordOriginBlacklist,
46
        array $exceptionFieldMap
47
    ) {
48 4
        $this->utils = $utils;
49 4
        $this->recordOriginField = $recordOriginField;
50 4
        $this->recordOriginBlacklist = $recordOriginBlacklist;
51 4
        $this->exceptionFieldMap = $exceptionFieldMap;
52 4
    }
53
54
    /**
55
     * Checks the recordOrigin rules and sets error in event if needed
56
     *
57
     * @param ConstraintEventSchema $event event class
58
     *
59
     * @return void
60
     */
61 4
    public function checkRecordOrigin(ConstraintEventSchema $event)
62
    {
63 4
        $currentRecord = $this->utils->getCurrentEntity();
64
65
        // if no recordorigin set on saved record; we let it through
66 4
        if (is_null($currentRecord) || !isset($currentRecord->{$this->recordOriginField})) {
67 4
            return;
68
        }
69
70
        $recordOrigin = $currentRecord->{$this->recordOriginField};
71
72
        // not in the blacklist? can also go through..
73
        if (!in_array($recordOrigin, $this->recordOriginBlacklist)) {
74
            return;
75
        }
76
77
        // ok, user is trying to modify an object with blacklist recordorigin.. let's check fields
78
        $schema = $event->getSchema();
79
        $data = $event->getElement();
80
        $isAllowed = true;
81
82
        if (!isset($schema->{'x-documentClass'})) {
83
            // this should never happen but we need to check. if schema has no information to *check* our rules, we
84
            // MUST deny it in that case..
85
            $event->addError(
86
                'Internal error, not enough schema information to validate recordOrigin rules.',
87
                $this->recordOriginField
88
            );
89
            return;
90
        }
91
92
        $documentClass = $schema->{'x-documentClass'};
93
94
        if (!isset($this->exceptionFieldMap[$documentClass])) {
95
            // if he wants to edit on blacklist, but we have no exceptions, also deny..
96
            $isAllowed = false;
97
        } else {
98
            // so to check our exceptions, we remove it from both documents (the stored and the clients) and compare
99
            $exceptions = $this->exceptionFieldMap[$documentClass];
100
101
            $accessor = PropertyAccess::createPropertyAccessorBuilder()
102
                                      ->enableMagicCall()
103
                                      ->getPropertyAccessor();
104
105
            $storedObject = clone $currentRecord;
106
            $userObject = clone $data;
107
108
            foreach ($exceptions as $fieldName) {
109 View Code Duplication
                if ($accessor->isWritable($storedObject, $fieldName)) {
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...
110
                    $accessor->setValue($storedObject, $fieldName, null);
111
                } else {
112
                    $this->addProperties($fieldName, $storedObject);
113
                }
114 View Code Duplication
                if ($accessor->isWritable($userObject, $fieldName)) {
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...
115
                    $accessor->setValue($userObject, $fieldName, null);
116
                } else {
117
                    $this->addProperties($fieldName, $userObject);
118
                }
119
            }
120
121
            // so now all unimportant fields were set to null on both - they should match if rest is untouched ;-)
122
            if ($userObject != $storedObject) {
123
                $isAllowed = false;
124
            }
125
        }
126
127
        if (!$isAllowed) {
128
            $event->addError(
129
                sprintf(
130
                    'Prohibited modification attempt on record with %s of %s',
131
                    $this->recordOriginField,
132
                    implode(', ', $this->recordOriginBlacklist)
133
                ),
134
                $this->recordOriginField
135
            );
136
        }
137
138
        return;
139
    }
140
141
    /**
142
     * if the user provides properties that are in the exception list but not on the currently saved
143
     * object, we try here to synthetically add them to our representation. and yes, this won't support
144
     * exclusions in an array structure for the moment, but that is also not needed for now.
145
     *
146
     * @param string $expression the expression
147
     * @param object $obj        the object
148
     *
149
     * @return object the modified object
150
     */
151
    private function addProperties($expression, $obj)
152
    {
153
        $val = &$obj;
154
        $parts = explode('.', $expression);
155
        $numParts = count($parts);
156
157
        if ($numParts == 1) {
158
            $val->{$parts[0]} = null;
159
        } else {
160
            $iteration = 1;
161
            foreach ($parts as $part) {
162
                if ($iteration < $numParts) {
163
                    if (!is_object($val->{$part})) {
164
                        $val->{$part} = new \stdClass();
165
                    }
166
                    $val = &$val->{$part};
167
                } else {
168
                    $val->{$part} = null;
169
                }
170
                $iteration++;
171
            }
172
        }
173
174
        return $val;
175
    }
176
}
177