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

RecordOriginConstraint::checkRecordOrigin()   C

Complexity

Conditions 11
Paths 9

Size

Total Lines 79
Code Lines 44

Duplication

Lines 10
Ratio 12.66 %

Code Coverage

Tests 4
CRAP Score 105.2212

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 10
loc 79
ccs 4
cts 50
cp 0.08
rs 5.3086
cc 11
eloc 44
nc 9
nop 1
crap 105.2212

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
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