Completed
Push — master ( 2a17e7...e610ee )
by Arne
04:45
created

StandardIndexMerger::isLocalObjectModified()   D

Complexity

Conditions 12
Paths 192

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 31
rs 4.8484
c 0
b 0
f 0
cc 12
eloc 17
nc 192
nop 2

How to fix   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
namespace Storeman\IndexMerger;
4
5
use Storeman\Config\Configuration;
6
use Storeman\ConflictHandler\ConflictHandlerInterface;
7
use Storeman\Hash\HashProvider;
8
use Storeman\Index\Index;
9
use Storeman\Index\IndexObject;
10
11
class StandardIndexMerger implements IndexMergerInterface
12
{
13
    /**
14
     * @var Configuration
15
     */
16
    protected $configuration;
17
18
    /**
19
     * @var HashProvider
20
     */
21
    protected $hashProvider;
22
23
    public function __construct(Configuration $configuration, HashProvider $hashProvider)
24
    {
25
        $this->configuration = $configuration;
26
        $this->hashProvider = $hashProvider;
27
    }
28
29
    /**
30
     * {@inheritdoc}
31
     */
32
    public function merge(ConflictHandlerInterface $conflictHandler, Index $remoteIndex, Index $localIndex, ?Index $lastLocalIndex): Index
33
    {
34
        $mergedIndex = new Index();
35
        $lastLocalIndex = $lastLocalIndex ?: new Index();
36
37
        $this->inspectLocalIndex($mergedIndex, $conflictHandler, $remoteIndex, $localIndex, $lastLocalIndex);
38
        $this->inspectRemoteIndex($mergedIndex, $conflictHandler, $remoteIndex, $localIndex, $lastLocalIndex);
39
40
        return $mergedIndex;
41
    }
42
43
    protected function inspectLocalIndex(Index $mergedIndex, ConflictHandlerInterface $conflictHandler, Index $remoteIndex, Index $localIndex, Index $lastLocalIndex): void
44
    {
45
        foreach ($localIndex as $localObject)
46
        {
47
            /** @var IndexObject $localObject */
48
49
            $remoteObject = $remoteIndex->getObjectByPath($localObject->getRelativePath());
50
            $lastLocalObject = $lastLocalIndex->getObjectByPath($localObject->getRelativePath());
51
52
53
            // compare existing to known object
54
            if ($lastLocalObject)
55
            {
56
                $localObjectModified = $this->isLocalObjectModified($localObject, $lastLocalObject);
57
                $remoteObjectModified = $this->isRemoteObjectModified($remoteObject, $lastLocalObject);
0 ignored issues
show
Bug introduced by
It seems like $remoteObject defined by $remoteIndex->getObjectB...ect->getRelativePath()) on line 49 can be null; however, Storeman\IndexMerger\Sta...sRemoteObjectModified() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
58
            }
59
60
            // object has been created
61
            else
62
            {
63
                $localObjectModified = true;
64
65
                // object has been created since last synchronization
66
                $remoteObjectModified = $remoteObject !== null;
67
            }
68
69
70
            // conflict if both the local and the remote object has been changed
71
            if ($localObjectModified && $remoteObjectModified)
72
            {
73
                $this->conflict($conflictHandler, $mergedIndex, $remoteObject, $localObject, $lastLocalObject);
0 ignored issues
show
Bug introduced by
It seems like $remoteObject defined by $remoteIndex->getObjectB...ect->getRelativePath()) on line 49 can be null; however, Storeman\IndexMerger\Sta...IndexMerger::conflict() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
74
            }
75
76
            // add the remote object if only it has been modified
77
            elseif ($remoteObjectModified)
78
            {
79
                $mergedIndex->addObject($remoteObject);
0 ignored issues
show
Bug introduced by
It seems like $remoteObject defined by $remoteIndex->getObjectB...ect->getRelativePath()) on line 49 can be null; however, Storeman\Index\Index::addObject() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
80
            }
81
82
            // add the local object otherwise if only it or none has been modified
83
            else
84
            {
85
                $mergedIndex->addObject($localObject);
86
            }
87
        }
88
    }
89
90
    protected function inspectRemoteIndex(Index $mergedIndex, ConflictHandlerInterface $conflictHandler, Index $remoteIndex, Index $localIndex, Index $lastLocalIndex): void
91
    {
92
        foreach ($remoteIndex as $remoteObject)
93
        {
94
            /** @var IndexObject $remoteObject */
95
96
            // only consider objects not existing locally as those are already considered
97
            if ($localIndex->getObjectByPath($remoteObject->getRelativePath()))
98
            {
99
                continue;
100
            }
101
102
103
            $lastLocalObject = $lastLocalIndex->getObjectByPath($remoteObject->getRelativePath());
104
105
            // local object has been deleted
106
            if ($lastLocalObject)
107
            {
108
                $localObjectModified = true;
109
110
                // compare remote object to object state at last sync
111
                $remoteObjectModified = $this->isRemoteObjectModified($remoteObject, $lastLocalObject);
112
            }
113
114
            // another client added the remote object
115
            else
116
            {
117
                $remoteObjectModified = true;
118
119
                // object already didn't exist locally
120
                $localObjectModified = false;
121
            }
122
123
124
            // conflict if both the local and the remote object has been changed
125
            if ($localObjectModified && $remoteObjectModified)
126
            {
127
                $this->conflict($conflictHandler, $mergedIndex, $remoteObject, null, $lastLocalObject);
128
            }
129
130
            // another client added the remote object
131
            elseif (!$lastLocalObject)
132
            {
133
                $mergedIndex->addObject($remoteObject);
134
            }
135
        }
136
    }
137
138
    protected function isLocalObjectModified(IndexObject $localObject, IndexObject $lastLocalObject): bool
139
    {
140
        $localObjectModified = false;
141
        $localObjectModified = $localObjectModified || ($localObject->getType() !== $lastLocalObject->getType());
142
        $localObjectModified = $localObjectModified || ($localObject->getMtime() !== $lastLocalObject->getMtime());
143
        $localObjectModified = $localObjectModified || ($localObject->getCtime() !== $lastLocalObject->getCtime());
144
        $localObjectModified = $localObjectModified || ($localObject->getMode() !== $lastLocalObject->getMode());
145
        $localObjectModified = $localObjectModified || ($localObject->getSize() !== $lastLocalObject->getSize());
146
        $localObjectModified = $localObjectModified || ($localObject->getLinkTarget() !== $lastLocalObject->getLinkTarget());
147
148
        if (!$localObjectModified && $localObject->isFile())
149
        {
150
            $existingHashes = iterator_to_array($lastLocalObject->getHashes());
151
            $configuredAlgorithms = $this->configuration->getFileChecksums();
152
153
            if (!empty($comparableAlgorithms = array_intersect($configuredAlgorithms, array_keys($existingHashes))))
154
            {
155
                $this->hashProvider->loadHashes($localObject, $comparableAlgorithms);
156
157
                foreach ($comparableAlgorithms as $algorithm)
158
                {
159
                    if ($this->hashProvider->getHash($localObject, $algorithm) !== $existingHashes[$algorithm])
160
                    {
161
                        $localObjectModified = false;
162
                    }
163
                }
164
            }
165
        }
166
167
        return $localObjectModified;
168
    }
169
170
    protected function isRemoteObjectModified(IndexObject $remoteObject, IndexObject $lastLocalObject): bool
171
    {
172
        // remote object has been modified if it does not equal the object on its last synchronization
173
        return !$lastLocalObject->equals($remoteObject);
174
    }
175
176
    protected function conflict(ConflictHandlerInterface $conflictHandler, Index $mergedIndex, IndexObject $remoteObject, IndexObject $localObject = null, IndexObject $lastLocalObject = null): void
177
    {
178
        assert($localObject->getRelativePath() === $remoteObject->getRelativePath());
0 ignored issues
show
Bug introduced by
It seems like $localObject is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
179
        assert($localObject->getRelativePath() === $lastLocalObject->getRelativePath());
0 ignored issues
show
Bug introduced by
It seems like $lastLocalObject is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
180
181
        $solution = $conflictHandler->handleConflict($remoteObject, $localObject, $lastLocalObject);
182
183
        switch ($solution)
184
        {
185
            case ConflictHandlerInterface::USE_LOCAL:
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
186
187
                if ($localObject)
188
                {
189
                    $mergedIndex->addObject($localObject);
190
                }
191
192
                break;
193
194
            case ConflictHandlerInterface::USE_REMOTE:
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
195
196
                if ($remoteObject)
197
                {
198
                    $mergedIndex->addObject($remoteObject);
199
                }
200
201
                break;
202
203
            default:
0 ignored issues
show
Coding Style introduced by
The default body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a default statement must start on the line immediately following the statement.

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
204
205
                throw new \LogicException();
206
        }
207
    }
208
}
209