AllCoreTables - Allow virtual entities to share a DAO class
[civicrm-core.git] / Civi / Api4 / Service / Schema / SchemaMapBuilder.php
CommitLineData
19b53e5b
C
1<?php
2
380f3545
TO
3/*
4 +--------------------------------------------------------------------+
41498ac5 5 | Copyright CiviCRM LLC. All rights reserved. |
380f3545 6 | |
41498ac5
TO
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 |
380f3545
TO
10 +--------------------------------------------------------------------+
11 */
12
19b53e5b
C
13namespace Civi\Api4\Service\Schema;
14
15use Civi\Api4\Entity;
16use Civi\Api4\Event\Events;
17use Civi\Api4\Event\SchemaMapBuildEvent;
18use Civi\Api4\Service\Schema\Joinable\CustomGroupJoinable;
19use Civi\Api4\Service\Schema\Joinable\Joinable;
20use Symfony\Component\EventDispatcher\EventDispatcherInterface;
19b53e5b 21use CRM_Core_DAO_AllCoreTables as AllCoreTables;
19b53e5b
C
22
23class SchemaMapBuilder {
24 /**
25 * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
26 */
27 protected $dispatcher;
28 /**
29 * @var array
30 */
31 protected $apiEntities;
32
33 /**
34 * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
35 */
36 public function __construct(EventDispatcherInterface $dispatcher) {
37 $this->dispatcher = $dispatcher;
fe806431 38 $this->apiEntities = array_keys((array) Entity::get(FALSE)->addSelect('name')->execute()->indexBy('name'));
19b53e5b
C
39 }
40
41 /**
42 * @return SchemaMap
43 */
44 public function build() {
45 $map = new SchemaMap();
46 $this->loadTables($map);
47
48 $event = new SchemaMapBuildEvent($map);
49 $this->dispatcher->dispatch(Events::SCHEMA_MAP_BUILD, $event);
50
51 return $map;
52 }
53
54 /**
55 * Add all tables and joins
56 *
57 * @param SchemaMap $map
58 */
59 private function loadTables(SchemaMap $map) {
60 /** @var \CRM_Core_DAO $daoName */
40ac12d3 61 foreach (AllCoreTables::get() as $data) {
19b53e5b 62 $table = new Table($data['table']);
40ac12d3 63 foreach ($data['class']::fields() as $fieldData) {
60677581 64 $this->addJoins($table, $fieldData['name'], $fieldData);
19b53e5b
C
65 }
66 $map->addTable($table);
67 if (in_array($data['name'], $this->apiEntities)) {
68 $this->addCustomFields($map, $table, $data['name']);
69 }
70 }
19b53e5b
C
71 }
72
73 /**
74 * @param Table $table
75 * @param string $field
76 * @param array $data
77 */
78 private function addJoins(Table $table, $field, array $data) {
1b8e3bc8 79 $fkClass = $data['FKClassName'] ?? NULL;
19b53e5b
C
80
81 // can there be multiple methods e.g. pseudoconstant and fkclass
82 if ($fkClass) {
83 $tableName = AllCoreTables::getTableForClass($fkClass);
1b8e3bc8 84 $fkKey = $data['FKKeyColumn'] ?? 'id';
2b139d1b
CW
85 // Backward-compatibility for older api calls using e.g. "contact" instead of "contact_id"
86 if (strpos($field, '_id')) {
87 $alias = str_replace('_id', '', $field);
88 $joinable = new Joinable($tableName, $fkKey, $alias);
89 $joinable->setJoinType($joinable::JOIN_TYPE_MANY_TO_ONE);
90 $joinable->setDeprecated();
91 $table->addTableLink($field, $joinable);
92 }
93 $joinable = new Joinable($tableName, $fkKey, $field);
19b53e5b
C
94 $joinable->setJoinType($joinable::JOIN_TYPE_MANY_TO_ONE);
95 $table->addTableLink($field, $joinable);
96 }
19b53e5b
C
97 }
98
19b53e5b
C
99 /**
100 * @param \Civi\Api4\Service\Schema\SchemaMap $map
101 * @param \Civi\Api4\Service\Schema\Table $baseTable
cbda9790 102 * @param string $entityName
19b53e5b 103 */
cbda9790
CW
104 private function addCustomFields(SchemaMap $map, Table $baseTable, string $entityName) {
105 $customInfo = \Civi\Api4\Utils\CoreUtil::getCustomGroupExtends($entityName);
19b53e5b 106 // Don't be silly
cbda9790 107 if (!$customInfo) {
19b53e5b
C
108 return;
109 }
19b53e5b
C
110 $fieldData = \CRM_Utils_SQL_Select::from('civicrm_custom_field f')
111 ->join('custom_group', 'INNER JOIN civicrm_custom_group g ON g.id = f.custom_group_id')
1c339746 112 ->select(['g.name as custom_group_name', 'g.table_name', 'g.is_multiple', 'f.name', 'f.data_type', 'label', 'column_name', 'option_group_id', 'serialize'])
cbda9790 113 ->where('g.extends IN (@entity)', ['@entity' => $customInfo['extends']])
19b53e5b
C
114 ->where('g.is_active')
115 ->where('f.is_active')
116 ->execute();
117
118 $links = [];
119
120 while ($fieldData->fetch()) {
121 $tableName = $fieldData->table_name;
122
123 $customTable = $map->getTableByName($tableName);
124 if (!$customTable) {
125 $customTable = new Table($tableName);
126 }
127
19b53e5b
C
128 $map->addTable($customTable);
129
130 $alias = $fieldData->custom_group_name;
131 $links[$alias]['tableName'] = $tableName;
132 $links[$alias]['isMultiple'] = !empty($fieldData->is_multiple);
133 $links[$alias]['columns'][$fieldData->name] = $fieldData->column_name;
5e327f37
CW
134
135 // Add backreference
136 if (!empty($fieldData->is_multiple)) {
cbda9790 137 $joinable = new Joinable($baseTable->getName(), $customInfo['column'], AllCoreTables::convertEntityNameToLower($entityName));
5e327f37
CW
138 $customTable->addTableLink('entity_id', $joinable);
139 }
ede387bb
CW
140
141 if ($fieldData->data_type === 'ContactReference') {
142 $joinable = new Joinable('civicrm_contact', 'id', $fieldData->name);
1c339746
CW
143 if ($fieldData->serialize) {
144 $joinable->setSerialize((int) $fieldData->serialize);
145 }
ede387bb
CW
146 $customTable->addTableLink($fieldData->column_name, $joinable);
147 }
19b53e5b
C
148 }
149
150 foreach ($links as $alias => $link) {
6fe7bdee 151 $joinable = new CustomGroupJoinable($link['tableName'], $alias, $link['isMultiple'], $link['columns']);
cbda9790 152 $baseTable->addTableLink($customInfo['column'], $joinable);
19b53e5b
C
153 }
154 }
155
156}