api4 - Import CRM/, Civi/, templates/, ang/, css/, js/, xml/menu
[civicrm-core.git] / Civi / Api4 / Service / Schema / SchemaMap.php
CommitLineData
19b53e5b
C
1<?php
2
3namespace Civi\Api4\Service\Schema;
4
5use Civi\Api4\Service\Schema\Joinable\BridgeJoinable;
6
7class 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}