Commit | Line | Data |
---|---|---|
19b53e5b C |
1 | <?php |
2 | ||
3 | namespace Civi\Api4\Service\Schema; | |
4 | ||
5 | use Civi\Api4\Service\Schema\Joinable\BridgeJoinable; | |
6 | ||
7 | class SchemaMap { | |
8 | ||
9 | const MAX_JOIN_DEPTH = 3; | |
10 | ||
11 | /** | |
12 | * @var Table[] | |
13 | */ | |
14 | protected $tables = []; | |
15 | ||
16 | /** | |
17 | * @param $baseTableName | |
18 | * @param $targetTableAlias | |
19 | * | |
20 | * @return \Civi\Api4\Service\Schema\Joinable\Joinable[] | |
21 | * Array of links to the target table, empty if no path found | |
22 | */ | |
23 | public function getPath($baseTableName, $targetTableAlias) { | |
24 | $table = $this->getTableByName($baseTableName); | |
25 | $path = []; | |
26 | ||
27 | if (!$table) { | |
28 | return $path; | |
29 | } | |
30 | ||
31 | $this->findPaths($table, $targetTableAlias, 1, $path); | |
32 | ||
33 | foreach ($path as $index => $pathLink) { | |
34 | if ($pathLink instanceof BridgeJoinable) { | |
35 | $start = array_slice($path, 0, $index); | |
36 | $middle = [$pathLink->getMiddleLink()]; | |
37 | $end = array_slice($path, $index, count($path) - $index); | |
38 | $path = array_merge($start, $middle, $end); | |
39 | } | |
40 | } | |
41 | ||
42 | return $path; | |
43 | } | |
44 | ||
45 | /** | |
46 | * @return Table[] | |
47 | */ | |
48 | public function getTables() { | |
49 | return $this->tables; | |
50 | } | |
51 | ||
52 | /** | |
53 | * @param $name | |
54 | * | |
55 | * @return Table|null | |
56 | */ | |
57 | public function getTableByName($name) { | |
58 | foreach ($this->tables as $table) { | |
59 | if ($table->getName() === $name) { | |
60 | return $table; | |
61 | } | |
62 | } | |
63 | ||
64 | return NULL; | |
65 | } | |
66 | ||
67 | /** | |
68 | * Adds a table to the schema map if it has not already been added | |
69 | * | |
70 | * @param Table $table | |
71 | * | |
72 | * @return $this | |
73 | */ | |
74 | public function addTable(Table $table) { | |
75 | if (!$this->getTableByName($table->getName())) { | |
76 | $this->tables[] = $table; | |
77 | } | |
78 | ||
79 | return $this; | |
80 | } | |
81 | ||
82 | /** | |
83 | * @param array $tables | |
84 | */ | |
85 | public function addTables(array $tables) { | |
86 | foreach ($tables as $table) { | |
87 | $this->addTable($table); | |
88 | } | |
89 | } | |
90 | ||
91 | /** | |
92 | * Recursive function to traverse the schema looking for a path | |
93 | * | |
94 | * @param Table $table | |
95 | * The current table to base fromm | |
96 | * @param string $target | |
97 | * The target joinable table alias | |
98 | * @param int $depth | |
99 | * The current level of recursion which reflects the number of joins needed | |
100 | * @param \Civi\Api4\Service\Schema\Joinable\Joinable[] $path | |
101 | * (By-reference) The possible paths to the target table | |
102 | * @param \Civi\Api4\Service\Schema\Joinable\Joinable[] $currentPath | |
103 | * For internal use only to track the path to reach the target table | |
104 | */ | |
105 | private function findPaths(Table $table, $target, $depth, &$path, $currentPath = [] | |
106 | ) { | |
107 | static $visited = []; | |
108 | ||
109 | // reset if new call | |
110 | if ($depth === 1) { | |
111 | $visited = []; | |
112 | } | |
113 | ||
114 | $canBeShorter = empty($path) || count($currentPath) + 1 < count($path); | |
115 | $tooFar = $depth > self::MAX_JOIN_DEPTH; | |
116 | $beenHere = in_array($table->getName(), $visited); | |
117 | ||
118 | if ($tooFar || $beenHere || !$canBeShorter) { | |
119 | return; | |
120 | } | |
121 | ||
122 | // prevent circular reference | |
123 | $visited[] = $table->getName(); | |
124 | ||
125 | foreach ($table->getExternalLinks() as $link) { | |
126 | if ($link->getAlias() === $target) { | |
127 | $path = array_merge($currentPath, [$link]); | |
128 | } | |
129 | else { | |
130 | $linkTable = $this->getTableByName($link->getTargetTable()); | |
131 | if ($linkTable) { | |
132 | $nextStep = array_merge($currentPath, [$link]); | |
133 | $this->findPaths($linkTable, $target, $depth + 1, $path, $nextStep); | |
134 | } | |
135 | } | |
136 | } | |
137 | } | |
138 | ||
139 | } |