Completed
Push — feature/EVO-5751-mongodb-3.x ( e24a58...f7410f )
by Lucas
65:41
created

RecordOriginConstraint::checkRecordOrigin()   D

Complexity

Conditions 14
Paths 10

Size

Total Lines 95
Code Lines 54

Duplication

Lines 10
Ratio 10.53 %

Importance

Changes 3
Bugs 1 Features 1
Metric Value
dl 10
loc 95
rs 4.9516
c 3
b 1
f 1
cc 14
eloc 54
nc 10
nop 1

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