Index   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 241
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 33
lcom 1
cbo 7
dl 0
loc 241
rs 9.76
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A addObject() 0 23 4
A getObjectByPath() 0 6 2
A getObjectByBlobId() 0 14 3
A equals() 0 9 3
A isSubsetOf() 0 14 3
A getDifference() 0 9 1
A getIntersection() 0 18 3
A merge() 0 25 5
A count() 0 4 1
A getIterator() 0 4 1
A addDiffTo() 0 19 6
1
<?php
2
3
namespace Storeman\Index;
4
5
use Storeman\Exception;
6
use Storeman\Index\Comparison\IndexComparison;
7
use Storeman\Index\Comparison\IndexObjectComparison;
8
9
/**
10
 * As the name suggests an index is a representation of the vault at some point in time.
11
 * On iteration the objects are returned sorted by topological order.
12
 */
13
class Index implements \Countable, \IteratorAggregate
14
{
15
    /**
16
     * @var IndexNode
17
     */
18
    protected $rootNode;
19
20
    public function __construct()
21
    {
22
        $this->rootNode = new IndexNode();
23
    }
24
25
    /**
26
     * Adds the given object to the index.
27
     *
28
     * @param IndexObject $indexObject
29
     * @return Index
30
     * @throws Exception
31
     */
32
    public function addObject(IndexObject $indexObject): Index
33
    {
34
        $parentNode = $this->rootNode;
35
36
        // ensure existence of containing directory
37
        if (substr_count($indexObject->getRelativePath(), '/') > 0)
38
        {
39
            $parentNode = $this->rootNode->getNodeByPath(dirname($indexObject->getRelativePath()));
40
41
            if ($parentNode === null)
42
            {
43
                throw new Exception("Trying to add object {$indexObject->getRelativePath()} without existing parent node");
44
            }
45
            elseif (!$parentNode->getIndexObject()->isDirectory())
46
            {
47
                throw new Exception("Trying to add object {$indexObject->getRelativePath()} under parent node which is not a directory");
48
            }
49
        }
50
51
        $parentNode->addChild(new IndexNode($indexObject, $parentNode));
52
53
        return $this;
54
    }
55
56
    /**
57
     * Returns an index object by a given relative path.
58
     *
59
     * @param string $path
60
     * @return IndexObject|null
61
     */
62
    public function getObjectByPath(string $path): ?IndexObject
63
    {
64
        $node = $this->rootNode->getNodeByPath($path);
65
66
        return $node ? $node->getIndexObject() : null;
67
    }
68
69
    /**
70
     * Returns an index object by a given blob id.
71
     *
72
     * @param string $blobId
73
     * @return IndexObject|null
74
     */
75
    public function getObjectByBlobId(string $blobId): ?IndexObject
76
    {
77
        foreach ($this as $object)
78
        {
79
            /** @var IndexObject $object */
80
81
            if ($object->getBlobId() === $blobId)
82
            {
83
                return $object;
84
            }
85
        }
86
87
        return null;
88
    }
89
90
    /**
91
     * Compares this index to the given index and returns the comparison result as boolean indicator.
92
     *
93
     * @param Index|null $other
94
     * @param int $options
95
     * @return bool
96
     */
97
    public function equals(Index $other = null, int $options = 0): bool
98
    {
99
        if ($other === null)
100
        {
101
            return false;
102
        }
103
104
        return $this->isSubsetOf($other, $options) && $other->isSubsetOf($this, $options);
105
    }
106
107
    /**
108
     * Returns true if this index is a subset of the given index.
109
     *
110
     * @param Index $other
111
     * @param int $options
112
     * @return bool
113
     */
114
    public function isSubsetOf(Index $other, int $options = 0): bool
115
    {
116
        foreach ($this as $indexObject)
117
        {
118
            /** @var IndexObject $indexObject */
119
120
            if (!$indexObject->equals($other->getObjectByPath($indexObject->getRelativePath()), $options))
0 ignored issues
show
Bug introduced by
It seems like $other->getObjectByPath(...ect->getRelativePath()) can be null; however, equals() 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...
121
            {
122
                return false;
123
            }
124
        }
125
126
        return true;
127
    }
128
129
    /**
130
     * Returns the difference between this and the given index.
131
     * The resulting difference contains objects from this index that are not present in or different to the
132
     * corresponding object in the given index and objects from the given index that do not have a correspondence in
133
     * this index.
134
     *
135
     * @param Index $other
136
     * @param int $options
137
     * @return IndexComparison
138
     */
139
    public function getDifference(Index $other, int $options = 0): IndexComparison
140
    {
141
        $diff = new IndexComparison();
142
143
        $this->addDiffTo($other, $diff, $options, false);
144
        $other->addDiffTo($this, $diff, $options, true);
145
146
        return $diff;
147
    }
148
149
    /**
150
     * Returns the intersection of this and the given index.
151
     *
152
     * @param Index $other
153
     * @param int $options
154
     * @return IndexComparison
155
     */
156
    public function getIntersection(Index $other, int $options = 0): IndexComparison
157
    {
158
        $intersection = new IndexComparison();
159
160
        foreach ($this as $object)
161
        {
162
            /** @var IndexObject $object */
163
164
            $otherObject = $other->getObjectByPath($object->getRelativePath());
165
166
            if ($object->equals($otherObject, $options))
0 ignored issues
show
Bug introduced by
It seems like $otherObject defined by $other->getObjectByPath(...ect->getRelativePath()) on line 164 can be null; however, Storeman\Index\IndexObject::equals() 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...
167
            {
168
                $intersection->addObjectComparison(new IndexObjectComparison($object, $otherObject));
169
            }
170
        }
171
172
        return $intersection;
173
    }
174
175
    /**
176
     * Merges the given index into this index instance.
177
     * Eventually existing objects with the same path are overridden.
178
     * The contents of directories under the same path are merged together.
179
     *
180
     * @param Index $other
181
     * @return Index
182
     */
183
    public function merge(Index $other): Index
184
    {
185
        foreach ($other as $object)
186
        {
187
            /** @var IndexObject $object */
188
189
            $existingObject = $this->getObjectByPath($object->getRelativePath());
190
191
            // merge directory contents
192
            if ($existingObject && $existingObject->isDirectory() && $object->isDirectory())
193
            {
194
                $existingNode = $this->rootNode->getNodeByPath($object->getRelativePath());
195
                $existingNode->setIndexObject($object);
196
                $existingNode->addChildren($other->rootNode->getNodeByPath($object->getRelativePath())->getChildren());
197
            }
198
199
            // add object or override existing
200
            else
201
            {
202
                $this->addObject($object);
203
            }
204
        }
205
206
        return $this;
207
    }
208
209
    /**
210
     * {@inheritdoc}
211
     */
212
    public function count(): int
213
    {
214
        return $this->rootNode->recursiveCount();
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     */
220
    public function getIterator(): \Traversable
221
    {
222
        return new IndexIterator(new RecursiveIndexIterator($this->rootNode));
223
    }
224
225
    /**
226
     * Returns all those objects in this index that are not existent or are different in the given index.
227
     *
228
     * @param Index $other
229
     * @param IndexComparison $indexDifference
230
     * @param int $options
231
     * @param bool $flipped
232
     * @return IndexComparison
233
     */
234
    protected function addDiffTo(Index $other, IndexComparison $indexDifference, int $options, bool $flipped): IndexComparison
235
    {
236
        foreach ($this as $object)
237
        {
238
            /** @var IndexObject $object */
239
240
            $otherObject = $other->getObjectByPath($object->getRelativePath());
241
242
            if (!$object->equals($otherObject, $options) && !$indexDifference->hasObjectComparison($object->getRelativePath()))
0 ignored issues
show
Bug introduced by
It seems like $otherObject defined by $other->getObjectByPath(...ect->getRelativePath()) on line 240 can be null; however, Storeman\Index\IndexObject::equals() 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...
243
            {
244
                $objectA = $flipped ? $otherObject : $object;
245
                $objectB = $flipped ? $object : $otherObject;
246
247
                $indexDifference->addObjectComparison(new IndexObjectComparison($objectA, $objectB));
248
            }
249
        }
250
251
        return $indexDifference;
252
    }
253
}
254