Completed
Branch master (7ef6dd)
by Bingo
07:42 queued 03:47
created

AbstractGraph   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 430
Duplicated Lines 0 %

Test Coverage

Coverage 71.3%

Importance

Changes 0
Metric Value
wmc 48
eloc 88
c 0
b 0
f 0
dl 0
loc 430
ccs 77
cts 108
cp 0.713
rs 8.5599

26 Methods

Rating   Name   Duplication   Size   Complexity  
A getType() 0 3 1
A removeAllVertices() 0 7 2
A assertVertexExists() 0 6 2
A vertexSet() 0 3 1
A getEdge() 0 3 1
A containsVertex() 0 3 1
A edgesOf() 0 4 1
A outgoingEdgesOf() 0 4 1
A addEdge() 0 24 6
A getAllEdges() 0 3 1
A createEdgeSpecifics() 0 3 2
A containsEdge() 0 9 3
A getEdgeSupplier() 0 3 1
A removeEdge() 0 18 5
A incomingEdgesOf() 0 4 1
A createSpecifics() 0 3 2
A getEdgeWeight() 0 3 1
A getVertexSupplier() 0 3 1
A getEdgeSource() 0 3 1
A addVertex() 0 8 2
A edgeSet() 0 3 1
A setEdgeWeight() 0 3 1
A __construct() 0 10 1
A getEdgeTarget() 0 3 1
A removeVertex() 0 9 2
A removeAllEdges() 0 23 6

How to fix   Complexity   

Complex Class

Complex classes like AbstractGraph often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractGraph, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace graphp\graph;
4
5
use InvalidArgumentException;
6
use graphp\graph\specifics\SpecificsInterface;
7
use graphp\graph\specifics\UndirectedSpecifics;
8
use graphp\graph\specifics\DirectedSpecifics;
9
use graphp\edge\EdgeInterface;
10
use graphp\edge\EdgeSet;
11
use graphp\edge\specifics\EdgeSpecificsInterface;
12
use graphp\edge\specifics\UniformEdgeSpecifics;
13
use graphp\edge\specifics\WeightedEdgeSpecifics;
14
use graphp\vertex\VertexInterface;
15
use graphp\vertex\VertexSet;
16
use graphp\util\SupplierInterface;
17
18
/**
19
 * Class AbstractGraph
20
 *
21
 * @package graphp\graph
22
 */
23
class AbstractGraph implements GraphInterface
24
{
25
    /**
26
     * The vertex supplier
27
     *
28
     * @var SupplierInterface
29
     */
30
    private $vertexSupplier;
31
32
    /**
33
     * The edge supplier
34
     *
35
     * @var SupplierInterface
36
     */
37
    private $edgeSupplier;
38
39
    /**
40
     * The graph type
41
     *
42
     * @var GraphTypeInterface
43
     */
44
    private $type;
45
46
    /**
47
     * The graph specifics
48
     *
49
     * @var SpecificsInterface
50
     */
51
    private $specifics;
52
53
    /**
54
     * The edge specifics
55
     *
56
     * @var EdgeSpecificsInterface
57
     */
58
    private $edgeSpecifics;
59
60
    /**
61
     * Construct a new graph
62
     *
63
     * @param SupplierInterface $vertexSupplier - the vertex supplier
64
     * @param SupplierInterface $edgeSupplier - the edge supplier
65
     * @param GraphTypeInterface $type - the graph type
66
     */
67 12
    protected function __construct(
68
        ?SupplierInterface $vertexSupplier = null,
69
        ?SupplierInterface $edgeSupplier = null,
70
        ?GraphTypeInterface $type = null
71
    ) {
72 12
        $this->vertexSupplier = $vertexSupplier;
73 12
        $this->edgeSupplier = $edgeSupplier;
74 12
        $this->type = $type;
75 12
        $this->specifics = $this->createSpecifics($type->isDirected());
0 ignored issues
show
Bug introduced by
The method isDirected() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

75
        $this->specifics = $this->createSpecifics($type->/** @scrutinizer ignore-call */ isDirected());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
76 12
        $this->edgeSpecifics = $this->createEdgeSpecifics($type->isWeighted());
77 12
    }
78
79
    /**
80
     * Get the graph specifics
81
     *
82
     * @param bool $isDirected - is directed graph?
83
     *
84
     * @return SpecificsInterface
85
     */
86 12
    public function createSpecifics(bool $isDirected): SpecificsInterface
87
    {
88 12
        return $isDirected ? new DirectedSpecifics($this) : new UndirectedSpecifics($this);
89
    }
90
91
    /**
92
     * Get the edge specifics
93
     *
94
     * @param bool $isWeighted - is weighted?
95
     *
96
     * @return EdgeSpecificsInterface
97
     */
98 12
    public function createEdgeSpecifics(bool $isWeighted): EdgeSpecificsInterface
99
    {
100 12
        return $isWeighted ? new WeightedEdgeSpecifics($this) : new UniformEdgeSpecifics($this);
0 ignored issues
show
Unused Code introduced by
The call to graphp\edge\specifics\We...pecifics::__construct() has too many arguments starting with $this. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

100
        return $isWeighted ? /** @scrutinizer ignore-call */ new WeightedEdgeSpecifics($this) : new UniformEdgeSpecifics($this);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Unused Code introduced by
The call to graphp\edge\specifics\Un...pecifics::__construct() has too many arguments starting with $this. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

100
        return $isWeighted ? new WeightedEdgeSpecifics($this) : /** @scrutinizer ignore-call */ new UniformEdgeSpecifics($this);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
101
    }
102
103
    /**
104
     * Get all edges connecting the source vertext to the target vertex
105
     *
106
     * @return EdgeSet
107
     */
108 1
    public function getAllEdges(VertexInterface $sourceVertex, VertexInterface $targetVertex): EdgeSet
109
    {
110 1
        return $this->specifics->getAllEdges($sourceVertex, $targetVertex);
111
    }
112
113
    /**
114
     * Get an edge connecting the source vertext to the target vertex
115
     *
116
     * @return null|EdgeInterface
117
     */
118 9
    public function getEdge(VertexInterface $sourceVertex, VertexInterface $targetVertex): ?EdgeInterface
119
    {
120 9
        return $this->specifics->getEdge($sourceVertex, $targetVertex);
121
    }
122
123
    /**
124
     * Get the vertex supplier that the graph uses whenever it needs to create new vertices
125
     *
126
     * @return null|SupplierInterface
127
     */
128 1
    public function getVertexSupplier(): ?SupplierInterface
129
    {
130 1
        return $this->vertexSupplier;
131
    }
132
133
    /**
134
     * Get the edge supplier that the graph uses whenever it needs to create new edges
135
     *
136
     * @return SupplierInterface
137
     */
138 4
    public function getEdgeSupplier(): SupplierInterface
139
    {
140 4
        return $this->edgeSupplier;
141
    }
142
143
    /**
144
     * Get the graph type
145
     *
146
     * @return GraphTypeInterface
147
     */
148 9
    public function getType(): GraphTypeInterface
149
    {
150 9
        return $this->type;
151
    }
152
153
    /**
154
     * Create a new edge in the graph. Return the newly created edge if added to the graph.
155
     *
156
     * @return EdgeInterface
157
     *
158
     * @throws InvalidArgumentException
159
     */
160 9
    public function addEdge(
161
        VertexInterface $sourceVertex,
162
        VertexInterface $targetVertex,
163
        ?EdgeInterface $edge = null
164
    ): ?EdgeInterface {
165 9
        $this->assertVertexExists($sourceVertex);
166 9
        $this->assertVertexExists($targetVertex);
167
        
168 9
        if (!$this->getType()->isAllowingMultipleEdges() && $this->containsEdge($sourceVertex, $targetVertex)) {
169 2
            return null;
170
        }
171
        
172 9
        if (!$this->getType()->isAllowingSelfLoops() && $sourceVertex->equals($targetVertex)) {
173
            throw new InvalidArgumentException("loops are not allowed");
174
        }
175
        
176 9
        $edgeSupplier = $this->edgeSupplier->get();
177
178 9
        if ($this->edgeSpecifics->add($edgeSupplier, $sourceVertex, $targetVertex)) {
179 9
            $this->specifics->addEdgeToTouchingVertices($edgeSupplier);
180 9
            return $edgeSupplier;
181
        }
182
                
183
        return null;
184
    }
185
186
    /**
187
     * Create and return a new vertex in the graph.
188
     *
189
     * @return null|VertexInterface
190
     */
191 10
    public function addVertex(VertexInterface $vertex): ?VertexInterface
192
    {
193 10
        if (!$this->containsVertex($vertex)) {
194 10
            $this->specifics->addVertex($vertex);
195 10
            return $vertex;
196
        }
197
        
198 2
        return null;
199
    }
200
201
    /**
202
     * Check if the graph contains the given edge, specified either by two vertices or by the edge itself
203
     *
204
     * @param VertexInterface $sourceVertex - the source vertex
205
     * @param VertexInterface $targetVertex - the target vertex
206
     * @param EdgeInterface $edge - the edge
207
     *
208
     * @return bool
209
     */
210 9
    public function containsEdge(
211
        ?VertexInterface $sourceVertex = null,
212
        ?VertexInterface $targetVertex = null,
213
        ?EdgeInterface $edge = null
214
    ): bool {
215 9
        if (!is_null($sourceVertex) && !is_null($targetVertex)) {
216 9
            return !is_null($this->getEdge($sourceVertex, $targetVertex));
217
        }
218 2
        return $this->edgeSpecifics->containsEdge($edge);
0 ignored issues
show
Bug introduced by
It seems like $edge can also be of type null; however, parameter $edge of graphp\edge\specifics\Ed...terface::containsEdge() does only seem to accept graphp\edge\EdgeInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

218
        return $this->edgeSpecifics->containsEdge(/** @scrutinizer ignore-type */ $edge);
Loading history...
219
    }
220
221
    /**
222
     * Check if the graph contains the given vertex
223
     *
224
     * @return bool
225
     */
226 10
    public function containsVertex(VertexInterface $vertex): bool
227
    {
228 10
        return $this->specifics->getVertexSet()->contains($vertex);
229
    }
230
231
    /**
232
     * Get a set of all edges touching the specified vertex
233
     *
234
     * @param VertexInterface - the vertex
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
235
     *
236
     * @return EdgeSet
237
     */
238 4
    public function edgesOf(VertexInterface $vertex): EdgeSet
239
    {
240 4
        $this->assertVertexExists($vertex);
241 4
        return $this->specifics->edgesOf($vertex);
242
    }
243
244
    /**
245
     * Get a set of all edges incoming into the specified vertex
246
     *
247
     * @param VertexInterface $vertex - the vertex
248
     *
249
     * @return EdgeSet
250
     */
251 3
    public function incomingEdgesOf(VertexInterface $vertex): EdgeSet
252
    {
253 3
        $this->assertVertexExists($vertex);
254 3
        return $this->specifics->incomingEdgesOf($vertex);
255
    }
256
257
    /**
258
     * Get a set of all edges outgoing from the specified vertex
259
     *
260
     * @param VertexInterface $vertex - the vertex
261
     *
262
     * @return EdgeSet
263
     */
264 3
    public function outgoingEdgesOf(VertexInterface $vertex): EdgeSet
265
    {
266 3
        $this->assertVertexExists($vertex);
267 3
        return $this->specifics->outgoingEdgesOf($vertex);
268
    }
269
270
    /**
271
     * Remove all edges specified by two vertices or the the list of edges themselves.
272
     * Return true, if graph was changed
273
     *
274
     * @param VertexInterface $vertex - the source vertex
275
     * @param VertexInterface $vertex - the target vertex
276
     * @param EdgeSet $edges - the edges
277
     *
278
     * @return bool
279
     */
280 1
    public function removeAllEdges(
281
        ?VertexInterface $sourceVertex = null,
282
        ?VertexInterface $targetVertex = null,
283
        EdgeSet $edges
284
    ): bool {
285 1
        $changed = false;
286 1
        if (!is_null($sourceVertex) && !is_null($targetVertex)) {
287
            $edge = $this->getEdge($sourceVertex, $targetVertex);
288
            if (!is_null($edge)) {
289
                $this->specifics->removeEdgeFromTouchingVertices($edge);
290
                $this->edgeSpecifics->remove($edge);
291
                return true;
292
            }
293
        } else {
294 1
            foreach ($edges as $edge) {
295 1
                if ($this->containsEdge(null, null, $edge)) {
296 1
                    $this->specifics->removeEdgeFromTouchingVertices($edge);
297 1
                    $this->edgeSpecifics->remove($edge);
298 1
                    $changed = true;
299
                }
300
            }
301
        }
302 1
        return $changed;
303
    }
304
305
    /**
306
     * Remove all specified vertices contained in the graph.
307
     * Return true, if graph was changed
308
     *
309
     * @param array $vertices - the vertices
310
     *
311
     * @return bool
312
     */
313
    public function removeAllVertices(array $vertices = []): bool
314
    {
315
        $changed = false;
316
        foreach ($vertices as $vertex) {
317
            $changed = $this->removeVertex($vertex);
318
        }
319
        return $changed;
320
    }
321
322
    /**
323
     * Remove the specifeid edge from the graph.
324
     * Return the edge, if it was removed
325
     *
326
     * @param VertexInterface $sourceVertex - the source vertex
327
     * @param VertexInterface $targetVertex - the target vertex
328
     * @param EdgeInterface $edge - the edge
329
     *
330
     * @return null|EdgeInterface
331
     */
332
    public function removeEdge(
333
        ?VertexInterface $sourceVertex = null,
334
        ?VertexInterface $targetVertex = null,
335
        ?EdgeInterface $edge = null
336
    ): ?EdgeInterface {
337
        if (!is_null($sourceVertex) && !is_null($targetVertex)) {
338
            $edge = $this->getEdge($sourceVertex, $targetVertex);
339
            if (!is_null($edge)) {
340
                $this->specifics->removeEdgeFromTouchingVertices($edge);
341
                $this->edgeSpecifics->remove($edge);
342
                return $edge;
343
            }
344
        } elseif (!is_null($edge)) {
345
            $this->specifics->removeEdgeFromTouchingVertices($edge);
346
            $this->edgeSpecifics->remove($edge);
347
            return $edge;
348
        }
349
        return null;
350
    }
351
352
    /**
353
     * Remove the specifeid vertex from the graph.
354
     * Return the true, if it was removed
355
     *
356
     * @param VertexInterface $vertex - the vertex
357
     *
358
     * @return bool
359
     */
360 1
    public function removeVertex(VertexInterface $vertex): bool
361
    {
362 1
        if ($this->containsVertex($vertex)) {
363 1
            $edges = $this->edgesOf($vertex);
364 1
            $this->removeAllEdges(null, null, $edges);
365 1
            $this->specifics->removeVertex($vertex);
366 1
            return true;
367
        }
368
        return false;
369
    }
370
371
    /**
372
     * Get the set of edges contained in the graph
373
     *
374
     * @return EdgeSet
375
     */
376 5
    public function edgeSet(): EdgeSet
377
    {
378 5
        return $this->edgeSpecifics->getEdgeSet();
379
    }
380
381
    /**
382
     * Get the set of vertices contained in the graph
383
     *
384
     * @return VertexSet
385
     */
386 3
    public function vertexSet(): VertexSet
387
    {
388 3
        return $this->specifics->getVertexSet();
389
    }
390
391
    /**
392
     * Get the edge source vertex
393
     *
394
     * @param EdgeInterface $edge - the edge
395
     *
396
     * @return VertexInterface
397
     */
398 9
    public function getEdgeSource(EdgeInterface $edge): VertexInterface
399
    {
400 9
        return $this->edgeSpecifics->getEdgeSource($edge);
401
    }
402
403
    /**
404
     * Get the edge target vertex
405
     *
406
     * @param EdgeInterface $edge - the edge
407
     *
408
     * @return VertexInterface
409
     */
410 9
    public function getEdgeTarget(EdgeInterface $edge): VertexInterface
411
    {
412 9
        return $this->edgeSpecifics->getEdgeTarget($edge);
413
    }
414
415
    /**
416
     * Get the edge weight
417
     *
418
     * @param EdgeInterface $edge - the edge
419
     *
420
     * @return float
421
     */
422
    public function getEdgeWeight(EdgeInterface $edge): float
423
    {
424
        return $this->edgeSpecifics->getEdgeWeight($edge);
425
    }
426
427
    /**
428
     * Set the edge weight
429
     *
430
     * @param EdgeInterface $edge - the edge
431
     * @param float $weight - the edge weight
432
     */
433
    public function setEdgeWeight(EdgeInterface $edge, float $weight): void
434
    {
435
        $this->edgeSpecifics->setEdgeWeight($edge, $weight);
436
    }
437
438
    /**
439
     * Assert the the vertex exists
440
     *
441
     * @param VertexInterface - the vertex
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
442
     *
443
     * @return bool
444
     *
445
     * @throws InvalidArgumentException
446
     */
447 9
    private function assertVertexExists(VertexInterface $vertex): bool
448
    {
449 9
        if ($this->containsVertex($vertex)) {
450 9
            return true;
451
        } else {
452
            throw new InvalidArgumentException("no such vertex in graph");
453
        }
454
    }
455
}
456