Merge pull request #15345 from yashodha/CRM-21677
[civicrm-core.git] / Civi / Api4 / Service / Schema / SchemaMap.php
CommitLineData
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
38namespace Civi\Api4\Service\Schema;
39
40use Civi\Api4\Service\Schema\Joinable\BridgeJoinable;
41
42class 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}