1
|
|
|
<?php |
2
|
|
|
/****************************************************************************** |
3
|
|
|
* An implementation of dicto (scg.unibe.ch/dicto) in and for PHP. |
4
|
|
|
* |
5
|
|
|
* Copyright (c) 2016 Richard Klees <[email protected]> |
6
|
|
|
* |
7
|
|
|
* This software is licensed under The MIT License. You should have received |
8
|
|
|
* a copy of the license along with the code. |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace Lechimp\Dicto\Graph; |
12
|
|
|
|
13
|
|
|
use Lechimp\Dicto\Graph\Predicate; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* The complete graph. |
17
|
|
|
*/ |
18
|
|
|
class Graph { |
19
|
|
|
/** |
20
|
|
|
* @var array<string, array<int, Node>> |
21
|
|
|
*/ |
22
|
|
|
protected $nodes = []; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @var int |
26
|
|
|
*/ |
27
|
|
|
protected $id_counter = 0; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Create a new node in the graph. |
31
|
|
|
* |
32
|
|
|
* @param string $type |
33
|
|
|
* @param array<string,mixed>|null $properties |
34
|
|
|
* @return Node |
35
|
|
|
*/ |
36
|
80 |
|
public function create_node($type, array $properties = null) { |
37
|
80 |
|
$node = $this->build_node($this->id_counter, $type, $properties); |
38
|
80 |
|
if (!array_key_exists($type, $this->nodes)) { |
39
|
80 |
|
$this->nodes[$type] = []; |
40
|
80 |
|
} |
41
|
80 |
|
$this->nodes[$type][$this->id_counter] = $node; |
42
|
80 |
|
$this->id_counter++; |
43
|
80 |
|
return $node; |
44
|
|
|
} |
45
|
|
|
|
46
|
80 |
|
protected function build_node($id, $type, array $properties = null) { |
47
|
80 |
|
return new Node($id, $type, $properties); |
|
|
|
|
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Add a relation to the graph. |
52
|
|
|
* |
53
|
|
|
* @param Node $left |
54
|
|
|
* @param string $type |
55
|
|
|
* @param array<string,mixed> $properties |
56
|
|
|
* @param Node $right |
57
|
|
|
* @return Relation |
58
|
|
|
*/ |
59
|
52 |
|
public function add_relation(Node $left, $type, array $properties, Node $right) { |
60
|
52 |
|
return $left->add_relation($type, $properties, $right); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Get nodes from the graph, maybe filtered by a filter. |
65
|
|
|
* |
66
|
|
|
* @param Predicate|null $filter |
67
|
|
|
* @return Iterator <Node> |
68
|
|
|
*/ |
69
|
64 |
|
public function nodes(Predicate $filter = null) { |
70
|
64 |
|
if ($filter !== null) { |
71
|
55 |
|
return $this->filtered_nodes($filter); |
72
|
|
|
} |
73
|
|
|
else { |
74
|
9 |
|
return $this->all_nodes(); |
75
|
|
|
} |
76
|
|
|
} |
77
|
|
|
|
78
|
9 |
|
protected function all_nodes() { |
79
|
9 |
|
foreach($this->nodes as $nodes) { |
80
|
8 |
|
foreach ($nodes as $node) { |
81
|
8 |
|
yield $node; |
82
|
8 |
|
} |
83
|
9 |
|
} |
84
|
9 |
|
} |
85
|
|
|
|
86
|
55 |
|
protected function filtered_nodes(Predicate $filter) { |
87
|
55 |
|
$types = $filter->for_types(array_keys($this->nodes)); |
88
|
55 |
|
$filter = $filter->compile(); |
89
|
55 |
|
foreach ($types as $type) { |
90
|
55 |
|
$nodes = $this->nodes[$type]; |
91
|
55 |
|
foreach ($nodes as $node) { |
92
|
55 |
|
if ($filter($node)) { |
93
|
53 |
|
yield $node; |
94
|
53 |
|
} |
95
|
55 |
|
} |
96
|
55 |
|
} |
97
|
55 |
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Get the node with the given id. |
101
|
|
|
* |
102
|
|
|
* @param int $id |
103
|
|
|
* @throws \InvalidArgumentException if $id is unknown |
104
|
|
|
* @return Node |
105
|
|
|
*/ |
106
|
2 |
|
public function node($id) { |
107
|
2 |
|
assert('is_int($id)'); |
108
|
2 |
|
foreach ($this->nodes as $nodes) { |
109
|
2 |
|
if (array_key_exists($id, $nodes)) { |
110
|
2 |
|
return $nodes[$id]; |
111
|
|
|
} |
112
|
2 |
|
} |
113
|
1 |
|
throw new \InvalidArgumentException("Unknown node id '$id'"); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Build a query on the graph. |
118
|
|
|
* |
119
|
|
|
* @return Query |
120
|
|
|
*/ |
121
|
61 |
|
public function query() { |
122
|
61 |
|
return new QueryImpl($this); |
123
|
|
|
} |
124
|
|
|
} |
125
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.