Completed
Pull Request — develop (#609)
by Narcotic
12:02 queued 07:18
created

VersionServiceConstraint::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
rs 9.4285
ccs 0
cts 4
cp 0
cc 1
eloc 3
nc 1
nop 1
crap 2
1
<?php
2
/**
3
 * Schema constraint that validates if readOnly: true fields are manipulated and rejects changes on those.
4
 */
5
6
namespace Graviton\SchemaBundle\Constraint;
7
8
use Graviton\JsonSchemaBundle\Validator\Constraint\Event\ConstraintEventSchema;
9
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
10
use Symfony\Component\PropertyAccess\PropertyAccess;
11
12
/**
13
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
14
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
15
 * @link     http://swisscom.ch
16
 */
17
class VersionServiceConstraint
18
{
19
20
    /** DB Field name used for validation and incremental */
21
    const FIELD_NAME = 'version';
22
23
    /** Header name used to inform user */
24
    const HEADER_NAME = 'X-Current-Version';
25
26
    /** @var int */
27
    private $version;
28
29
    /**
30
     * @var \Symfony\Component\PropertyAccess\PropertyAccessor
31
     */
32
    private $accessor;
33
34
    /**
35
     * ReadOnlyFieldConstraint constructor.
36
     *
37
     * @param ConstraintUtils $utils utils
38
     */
39
    public function __construct(ConstraintUtils $utils)
40
    {
41
        $this->utils = $utils;
42
        $this->accessor = PropertyAccess::createPropertyAccessor();
43
    }
44
45
    /**
46
     * Checks the readOnly fields and sets error in event if needed
47
     *
48
     * @param ConstraintEventSchema $event event class
49
     *
50
     * @return void
51
     */
52
    public function checkVersionField(ConstraintEventSchema $event)
53
    {
54
        if (!$this->isVersioningService()) {
55
            return;
56
        }
57
58
        $data = $event->getElement();
59
60
        // get the current record
61
        if ($currentRecord = $this->utils->getCurrentEntity()) {
62
            $userVersion = $this->getUserVersion($data);
63
            $storedVersion = $this->getVersionFromObject($currentRecord);
64
            if ($userVersion !== $storedVersion) {
65
                $event->addError(
66
                    sprintf(
67
                        'The value you provided does not match current version of the document. '.
68
                        'See the \'%s\' header in this response to determine current version.',
69
                        self::HEADER_NAME
70
                    ),
71
                    self::FIELD_NAME
72
                );
73
74
                // store version for response header
75
                $this->version = $storedVersion;
76
            }
77
        }
78
    }
79
80
    /**
81
     * tells whether the current service has versioning activated or not
82
     *
83
     * @return bool true if yes, false otherwise
84
     */
85
    public function isVersioningService()
86
    {
87
        $schema = $this->utils->getCurrentSchema();
88
        if (isset($schema->{'x-versioning'}) && $schema->{'x-versioning'} === true) {
89
            return true;
90
        }
91
        return false;
92
    }
93
94
    /**
95
     * returns the version from a given object
96
     *
97
     * @param object $object object
98
     *
99
     * @return int|null null or the specified version
100
     */
101
    private function getVersionFromObject($object)
102
    {
103
        $version = null;
104
105
        if ($this->accessor->isReadable($object, self::FIELD_NAME)) {
106
            $version = $this->accessor->getValue($object, self::FIELD_NAME);
107
        }
108
        return $version;
109
    }
110
111
    /**
112
     * Gets the user provided version, handling different scenarios
113
     *
114
     * @param object $object object
115
     *
116
     * @return int|null null or the specified version
117
     */
118
    private function getUserVersion($object)
119
    {
120
        if ($this->utils->getCurrentRequestMethod() == 'PATCH') {
121
            $content = json_decode($this->utils->getCurrentRequestContent(), true);
122
123
            $hasVersion = array_filter(
124
                $content,
125
                function ($val) {
126
                    if ($val['path'] == '/'.self::FIELD_NAME) {
127
                        return true;
128
                    }
129
                    return false;
130
                }
131
            );
132
133
            if (empty($hasVersion)) {
134
                return -1;
135
            }
136
        }
137
138
        return $this->getVersionFromObject($object);
139
    }
140
141
    /**
142
     * Setting if needed the headers to let user know what was the new version.
143
     *
144
     * @param FilterResponseEvent $event SF response event
145
     * @return void
146
     */
147
    public function setCurrentVersionHeader(FilterResponseEvent $event)
148
    {
149
        if ($this->version) {
150
            $event->getResponse()->headers->set(self::HEADER_NAME, $this->version);
151
        }
152
    }
153
}
154