Completed
Push — master ( 65570c...bf6ec7 )
by Arne
03:29 queued 12s
created

Index::addObject()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 8.7972
c 0
b 0
f 0
cc 4
eloc 10
nc 4
nop 1
1
<?php
2
3
namespace Storeman\Index;
4
5
use Storeman\Exception\Exception;
6
use Storeman\Index\Diff\IndexDifference;
7
use Storeman\Index\Diff\IndexObjectDifference;
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(), DIRECTORY_SEPARATOR) > 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
     * @return bool
95
     */
96
    public function equals(Index $other = null): bool
97
    {
98
        if ($other === null)
99
        {
100
            return false;
101
        }
102
103
        return $this->isSubsetOf($other) && $other->isSubsetOf($this);
104
    }
105
106
    /**
107
     * Returns true if this index is a subset of the given index.
108
     *
109
     * @param Index $other
110
     * @return bool
111
     */
112
    public function isSubsetOf(Index $other): bool
113
    {
114
        foreach ($this as $indexObject)
115
        {
116
            /** @var IndexObject $indexObject */
117
118
            if (!$indexObject->equals($other->getObjectByPath($indexObject->getRelativePath())))
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...
119
            {
120
                return false;
121
            }
122
        }
123
124
        return true;
125
    }
126
127
    /**
128
     * Returns the difference between this and the given index.
129
     * The resulting difference contains objects from this index that are not present in or different to the
130
     * corresponding object in the given index and objects from the given index that do not have a correspondence in
131
     * this index.
132
     *
133
     * @param Index $other
134
     * @return IndexDifference
135
     */
136
    public function getDifference(Index $other): IndexDifference
137
    {
138
        $diff = new IndexDifference();
139
140
        $this->addDiffTo($other, $diff);
141
        $other->addDiffTo($this, $diff);
142
143
        return $diff;
144
    }
145
146
    /**
147
     * Merges the given index into this index instance.
148
     * Eventually existing objects with the same path are overridden.
149
     * The contents of directories under the same path are merged together.
150
     *
151
     * @param Index $other
152
     * @return Index
153
     */
154
    public function merge(Index $other): Index
155
    {
156
        foreach ($other as $object)
157
        {
158
            /** @var IndexObject $object */
159
160
            $existingObject = $this->getObjectByPath($object->getRelativePath());
161
162
            // merge directory contents
163
            if ($existingObject && $existingObject->isDirectory() && $object->isDirectory())
164
            {
165
                $existingNode = $this->rootNode->getNodeByPath($object->getRelativePath());
166
                $existingNode->setIndexObject($object);
167
                $existingNode->addChildren($other->rootNode->getNodeByPath($object->getRelativePath())->getChildren());
168
            }
169
170
            // add object or override existing
171
            else
172
            {
173
                $this->addObject($object);
174
            }
175
        }
176
177
        return $this;
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     */
183
    public function count(): int
184
    {
185
        return $this->rootNode->recursiveCount();
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     */
191
    public function getIterator(): \Traversable
192
    {
193
        return new IndexIterator(new RecursiveIndexIterator($this->rootNode));
194
    }
195
196
    /**
197
     * Returns all those objects in this index that are not existent or are different in the given index.
198
     *
199
     * @param Index $other
200
     * @param IndexDifference $indexDifference
201
     * @return IndexDifference
202
     */
203
    protected function addDiffTo(Index $other, IndexDifference $indexDifference): IndexDifference
204
    {
205
        foreach ($this as $object)
206
        {
207
            /** @var IndexObject $object */
208
209
            $otherObject = $other->getObjectByPath($object->getRelativePath());
210
211
            if (!$object->equals($otherObject) && !$indexDifference->hasDifference($object->getRelativePath()))
0 ignored issues
show
Bug introduced by
It seems like $otherObject defined by $other->getObjectByPath(...ect->getRelativePath()) on line 209 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...
212
            {
213
                $indexDifference->addDifference(new IndexObjectDifference($object, $otherObject));
214
            }
215
        }
216
217
        return $indexDifference;
218
    }
219
}
220