Commit | Line | Data |
---|---|---|
19b53e5b C |
1 | <?php |
2 | ||
380f3545 TO |
3 | /* |
4 | +--------------------------------------------------------------------+ | |
5 | | CiviCRM version 5 | | |
6 | +--------------------------------------------------------------------+ | |
7 | | Copyright CiviCRM LLC (c) 2004-2019 | | |
8 | +--------------------------------------------------------------------+ | |
9 | | This file is a part of CiviCRM. | | |
10 | | | | |
11 | | CiviCRM is free software; you can copy, modify, and distribute it | | |
12 | | under the terms of the GNU Affero General Public License | | |
13 | | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. | | |
14 | | | | |
15 | | CiviCRM is distributed in the hope that it will be useful, but | | |
16 | | WITHOUT ANY WARRANTY; without even the implied warranty of | | |
17 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | | |
18 | | See the GNU Affero General Public License for more details. | | |
19 | | | | |
20 | | You should have received a copy of the GNU Affero General Public | | |
21 | | License and the CiviCRM Licensing Exception along | | |
22 | | with this program; if not, contact CiviCRM LLC | | |
23 | | at info[AT]civicrm[DOT]org. If you have questions about the | | |
24 | | GNU Affero General Public License or the licensing of CiviCRM, | | |
25 | | see the CiviCRM license FAQ at http://civicrm.org/licensing | | |
26 | +--------------------------------------------------------------------+ | |
27 | */ | |
28 | ||
29 | /** | |
30 | * | |
31 | * @package CRM | |
32 | * @copyright CiviCRM LLC (c) 2004-2019 | |
33 | * $Id$ | |
34 | * | |
35 | */ | |
36 | ||
37 | ||
19b53e5b C |
38 | namespace Civi\Api4\Service\Schema; |
39 | ||
40 | use Civi\Api4\Service\Schema\Joinable\BridgeJoinable; | |
41 | ||
42 | class SchemaMap { | |
43 | ||
44 | const MAX_JOIN_DEPTH = 3; | |
45 | ||
46 | /** | |
47 | * @var Table[] | |
48 | */ | |
49 | protected $tables = []; | |
50 | ||
51 | /** | |
52 | * @param $baseTableName | |
53 | * @param $targetTableAlias | |
54 | * | |
55 | * @return \Civi\Api4\Service\Schema\Joinable\Joinable[] | |
56 | * Array of links to the target table, empty if no path found | |
57 | */ | |
58 | public function getPath($baseTableName, $targetTableAlias) { | |
59 | $table = $this->getTableByName($baseTableName); | |
60 | $path = []; | |
61 | ||
62 | if (!$table) { | |
63 | return $path; | |
64 | } | |
65 | ||
66 | $this->findPaths($table, $targetTableAlias, 1, $path); | |
67 | ||
68 | foreach ($path as $index => $pathLink) { | |
69 | if ($pathLink instanceof BridgeJoinable) { | |
70 | $start = array_slice($path, 0, $index); | |
71 | $middle = [$pathLink->getMiddleLink()]; | |
72 | $end = array_slice($path, $index, count($path) - $index); | |
73 | $path = array_merge($start, $middle, $end); | |
74 | } | |
75 | } | |
76 | ||
77 | return $path; | |
78 | } | |
79 | ||
80 | /** | |
81 | * @return Table[] | |
82 | */ | |
83 | public function getTables() { | |
84 | return $this->tables; | |
85 | } | |
86 | ||
87 | /** | |
88 | * @param $name | |
89 | * | |
90 | * @return Table|null | |
91 | */ | |
92 | public function getTableByName($name) { | |
93 | foreach ($this->tables as $table) { | |
94 | if ($table->getName() === $name) { | |
95 | return $table; | |
96 | } | |
97 | } | |
98 | ||
99 | return NULL; | |
100 | } | |
101 | ||
102 | /** | |
103 | * Adds a table to the schema map if it has not already been added | |
104 | * | |
105 | * @param Table $table | |
106 | * | |
107 | * @return $this | |
108 | */ | |
109 | public function addTable(Table $table) { | |
110 | if (!$this->getTableByName($table->getName())) { | |
111 | $this->tables[] = $table; | |
112 | } | |
113 | ||
114 | return $this; | |
115 | } | |
116 | ||
117 | /** | |
118 | * @param array $tables | |
119 | */ | |
120 | public function addTables(array $tables) { | |
121 | foreach ($tables as $table) { | |
122 | $this->addTable($table); | |
123 | } | |
124 | } | |
125 | ||
126 | /** | |
127 | * Recursive function to traverse the schema looking for a path | |
128 | * | |
129 | * @param Table $table | |
130 | * The current table to base fromm | |
131 | * @param string $target | |
132 | * The target joinable table alias | |
133 | * @param int $depth | |
134 | * The current level of recursion which reflects the number of joins needed | |
135 | * @param \Civi\Api4\Service\Schema\Joinable\Joinable[] $path | |
136 | * (By-reference) The possible paths to the target table | |
137 | * @param \Civi\Api4\Service\Schema\Joinable\Joinable[] $currentPath | |
138 | * For internal use only to track the path to reach the target table | |
139 | */ | |
140 | private function findPaths(Table $table, $target, $depth, &$path, $currentPath = [] | |
141 | ) { | |
142 | static $visited = []; | |
143 | ||
144 | // reset if new call | |
145 | if ($depth === 1) { | |
146 | $visited = []; | |
147 | } | |
148 | ||
149 | $canBeShorter = empty($path) || count($currentPath) + 1 < count($path); | |
150 | $tooFar = $depth > self::MAX_JOIN_DEPTH; | |
151 | $beenHere = in_array($table->getName(), $visited); | |
152 | ||
153 | if ($tooFar || $beenHere || !$canBeShorter) { | |
154 | return; | |
155 | } | |
156 | ||
157 | // prevent circular reference | |
158 | $visited[] = $table->getName(); | |
159 | ||
160 | foreach ($table->getExternalLinks() as $link) { | |
161 | if ($link->getAlias() === $target) { | |
162 | $path = array_merge($currentPath, [$link]); | |
163 | } | |
164 | else { | |
165 | $linkTable = $this->getTableByName($link->getTargetTable()); | |
166 | if ($linkTable) { | |
167 | $nextStep = array_merge($currentPath, [$link]); | |
168 | $this->findPaths($linkTable, $target, $depth + 1, $path, $nextStep); | |
169 | } | |
170 | } | |
171 | } | |
172 | } | |
173 | ||
174 | } |