Completed
Push — master ( 65b5b4...2290e5 )
by Arne
05:25
created

StandardIndexMerger::merge()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 4
1
<?php
2
3
namespace Storeman\IndexMerger;
4
5
use Storeman\ConflictHandler\ConflictHandlerInterface;
6
use Storeman\Index\Index;
7
use Storeman\Index\IndexObject;
8
9
class StandardIndexMerger implements IndexMergerInterface
10
{
11
    /**
12
     * {@inheritdoc}
13
     */
14
    public function merge(ConflictHandlerInterface $conflictHandler, Index $remoteIndex, Index $localIndex, ?Index $lastLocalIndex): Index
15
    {
16
        $mergedIndex = new Index();
17
        $lastLocalIndex = $lastLocalIndex ?: new Index();
18
19
        $this->inspectLocalIndex($mergedIndex, $conflictHandler, $remoteIndex, $localIndex, $lastLocalIndex);
20
        $this->inspectRemoteIndex($mergedIndex, $conflictHandler, $remoteIndex, $localIndex, $lastLocalIndex);
21
22
        return $mergedIndex;
23
    }
24
25
    protected function inspectLocalIndex(Index $mergedIndex, ConflictHandlerInterface $conflictHandler, Index $remoteIndex, Index $localIndex, Index $lastLocalIndex): void
26
    {
27
        foreach ($localIndex as $localObject)
28
        {
29
            /** @var IndexObject $localObject */
30
31
            $remoteObject = $remoteIndex->getObjectByPath($localObject->getRelativePath());
32
            $lastLocalObject = $lastLocalIndex->getObjectByPath($localObject->getRelativePath());
33
34
35
            // compare existing to known object
36
            if ($lastLocalObject)
37
            {
38
                $localObjectModified = $this->isLocalObjectModified($localObject, $lastLocalObject);
39
                $remoteObjectModified = $this->isRemoteObjectModified($remoteObject, $lastLocalObject);
0 ignored issues
show
Bug introduced by
It seems like $remoteObject defined by $remoteIndex->getObjectB...ect->getRelativePath()) on line 31 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...
40
            }
41
42
            // object has been created
43
            else
44
            {
45
                $localObjectModified = true;
46
47
                // object has been created since last synchronization
48
                $remoteObjectModified = $remoteObject !== null;
49
            }
50
51
52
            // conflict if both the local and the remote object has been changed
53
            if ($localObjectModified && $remoteObjectModified)
54
            {
55
                $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 31 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...
56
            }
57
58
            // add the remote object if only it has been modified
59
            elseif ($remoteObjectModified)
60
            {
61
                $mergedIndex->addObject($remoteObject);
0 ignored issues
show
Bug introduced by
It seems like $remoteObject defined by $remoteIndex->getObjectB...ect->getRelativePath()) on line 31 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...
62
            }
63
64
            // add the local object otherwise if only it or none has been modified
65
            else
66
            {
67
                $mergedIndex->addObject($localObject);
68
            }
69
        }
70
    }
71
72
    protected function inspectRemoteIndex(Index $mergedIndex, ConflictHandlerInterface $conflictHandler, Index $remoteIndex, Index $localIndex, Index $lastLocalIndex): void
73
    {
74
        foreach ($remoteIndex as $remoteObject)
75
        {
76
            /** @var IndexObject $remoteObject */
77
78
            // only consider objects not existing locally as those are already considered
79
            if ($localIndex->getObjectByPath($remoteObject->getRelativePath()))
80
            {
81
                continue;
82
            }
83
84
85
            $lastLocalObject = $lastLocalIndex->getObjectByPath($remoteObject->getRelativePath());
86
87
            // local object has been deleted
88
            if ($lastLocalObject)
89
            {
90
                $localObjectModified = true;
91
92
                // compare remote object to object state at last sync
93
                $remoteObjectModified = $this->isRemoteObjectModified($remoteObject, $lastLocalObject);
94
            }
95
96
            // another client added the remote object
97
            else
98
            {
99
                $remoteObjectModified = true;
100
101
                // object already didn't exist locally
102
                $localObjectModified = false;
103
            }
104
105
106
            // conflict if both the local and the remote object has been changed
107
            if ($localObjectModified && $remoteObjectModified)
108
            {
109
                $this->conflict($conflictHandler, $mergedIndex, $remoteObject, null, $lastLocalObject);
110
            }
111
112
            // another client added the remote object
113
            elseif (!$lastLocalObject)
114
            {
115
                $mergedIndex->addObject($remoteObject);
116
            }
117
        }
118
    }
119
120
    protected function isLocalObjectModified(IndexObject $localObject, IndexObject $lastLocalObject): bool
121
    {
122
        $localObjectModified = false;
123
        $localObjectModified = $localObjectModified || ($localObject->getType() !== $lastLocalObject->getType());
124
        $localObjectModified = $localObjectModified || ($localObject->getMtime() !== $lastLocalObject->getMtime());
125
        $localObjectModified = $localObjectModified || ($localObject->getCtime() !== $lastLocalObject->getCtime());
126
        $localObjectModified = $localObjectModified || ($localObject->getMode() !== $lastLocalObject->getMode());
127
        $localObjectModified = $localObjectModified || ($localObject->getSize() !== $lastLocalObject->getSize());
128
        $localObjectModified = $localObjectModified || ($localObject->getLinkTarget() !== $lastLocalObject->getLinkTarget());
129
130
        return $localObjectModified;
131
    }
132
133
    protected function isRemoteObjectModified(IndexObject $remoteObject, IndexObject $lastLocalObject): bool
134
    {
135
        // remote object has been modified if it does not equal the object on its last synchronization
136
        return !$lastLocalObject->equals($remoteObject);
137
    }
138
139
    protected function conflict(ConflictHandlerInterface $conflictHandler, Index $mergedIndex, IndexObject $remoteObject, IndexObject $localObject = null, IndexObject $lastLocalObject = null): void
140
    {
141
        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...
142
        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...
143
144
        $solution = $conflictHandler->handleConflict($remoteObject, $localObject, $lastLocalObject);
145
146
        switch ($solution)
147
        {
148
            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...
149
150
                if ($localObject)
151
                {
152
                    $mergedIndex->addObject($localObject);
153
                }
154
155
                break;
156
157
            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...
158
159
                if ($remoteObject)
160
                {
161
                    $mergedIndex->addObject($remoteObject);
162
                }
163
164
                break;
165
166
            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...
167
168
                throw new \LogicException();
169
        }
170
    }
171
}
172