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