Completed
Push — feature/EVO-6307-record-origin... ( d3f12f...a5a7d9 )
by Narcotic
14:39
created

RecordOriginConstraint::checkRecordOrigin()   D

Complexity

Conditions 14
Paths 10

Size

Total Lines 97
Code Lines 55

Duplication

Lines 10
Ratio 10.31 %

Code Coverage

Tests 9
CRAP Score 137.41

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 10
loc 97
ccs 9
cts 63
cp 0.1429
rs 4.9516
cc 14
eloc 55
nc 10
nop 1
crap 137.41

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 4
        $data = $event->getElement();
65
66
        // if no recordorigin set on saved record; we let it through
67 4
        if (is_null($currentRecord) || !isset($currentRecord->{$this->recordOriginField})) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
68
69
            // we have no current record.. but make sure user doesn't want to send the banned recordOrigin
70
            if (
71 4
                isset($data->{$this->recordOriginField}) &&
72 4
                !is_null($data->{$this->recordOriginField}) &&
73 2
                in_array($data->{$this->recordOriginField}, $this->recordOriginBlacklist)
74 2
            ) {
75
                $event->addError(
76
                    sprintf(
77
                        'Creating documents with the %s field having a value of %s is not permitted.',
78
                        $this->recordOriginField,
79
                        implode(', ', $this->recordOriginBlacklist)
80
                    ),
81
                    $this->recordOriginField
82
                );
83
                return;
84
            }
85
86 4
            return;
87
        }
88
89
        $recordOrigin = $currentRecord->{$this->recordOriginField};
90
91
        // not in the blacklist? can also go through..
92
        if (!in_array($recordOrigin, $this->recordOriginBlacklist)) {
93
            return;
94
        }
95
96
        // ok, user is trying to modify an object with blacklist recordorigin.. let's check fields
97
        $schema = $event->getSchema();
98
        $isAllowed = true;
99
100
        if (!isset($schema->{'x-documentClass'})) {
101
            // this should never happen but we need to check. if schema has no information to *check* our rules, we
102
            // MUST deny it in that case..
103
            $event->addError(
104
                'Internal error, not enough schema information to validate recordOrigin rules.',
105
                $this->recordOriginField
106
            );
107
            return;
108
        }
109
110
        $documentClass = $schema->{'x-documentClass'};
111
112
        if (!isset($this->exceptionFieldMap[$documentClass])) {
113
            // if he wants to edit on blacklist, but we have no exceptions, also deny..
114
            $isAllowed = false;
115
        } else {
116
            // so to check our exceptions, we remove it from both documents (the stored and the clients) and compare
117
            $exceptions = $this->exceptionFieldMap[$documentClass];
118
119
            $accessor = PropertyAccess::createPropertyAccessorBuilder()
120
                                      ->enableMagicCall()
121
                                      ->getPropertyAccessor();
122
123
            $storedObject = clone $currentRecord;
124
            $userObject = clone $data;
125
126
            foreach ($exceptions as $fieldName) {
127 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...
128
                    $accessor->setValue($storedObject, $fieldName, null);
129
                } else {
130
                    $this->addProperties($fieldName, $storedObject);
131
                }
132 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...
133
                    $accessor->setValue($userObject, $fieldName, null);
134
                } else {
135
                    $this->addProperties($fieldName, $userObject);
136
                }
137
            }
138
139
            // so now all unimportant fields were set to null on both - they should match if rest is untouched ;-)
140
            if ($userObject != $storedObject) {
141
                $isAllowed = false;
142
            }
143
        }
144
145
        if (!$isAllowed) {
146
            $event->addError(
147
                sprintf(
148
                    'Prohibited modification attempt on record with %s of %s',
149
                    $this->recordOriginField,
150
                    implode(', ', $this->recordOriginBlacklist)
151
                ),
152
                $this->recordOriginField
153
            );
154
        }
155
156
        return;
157
    }
158
159
    /**
160
     * if the user provides properties that are in the exception list but not on the currently saved
161
     * object, we try here to synthetically add them to our representation. and yes, this won't support
162
     * exclusions in an array structure for the moment, but that is also not needed for now.
163
     *
164
     * @param string $expression the expression
165
     * @param object $obj        the object
166
     *
167
     * @return object the modified object
168
     */
169
    private function addProperties($expression, $obj)
170
    {
171
        $val = &$obj;
172
        $parts = explode('.', $expression);
173
        $numParts = count($parts);
174
175
        if ($numParts == 1) {
176
            $val->{$parts[0]} = null;
177
        } else {
178
            $iteration = 1;
179
            foreach ($parts as $part) {
180
                if ($iteration < $numParts) {
181
                    if (!is_object($val->{$part})) {
182
                        $val->{$part} = new \stdClass();
183
                    }
184
                    $val = &$val->{$part};
185
                } else {
186
                    $val->{$part} = null;
187
                }
188
                $iteration++;
189
            }
190
        }
191
192
        return $val;
193
    }
194
}
195