Merge pull request #21455 from jaapjansma/issue_645
[civicrm-core.git] / Civi / Api4 / Generic / DAOGetAction.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 namespace Civi\Api4\Generic;
14
15 use Civi\Api4\Query\Api4SelectQuery;
16 use Civi\Api4\Utils\CoreUtil;
17
18 /**
19 * Retrieve $ENTITIES based on criteria specified in the `where` parameter.
20 *
21 * Use the `select` param to determine which fields are returned, defaults to `[*]`.
22 *
23 * Perform joins on other related entities using a dot notation.
24 *
25 * @method $this setHaving(array $clauses)
26 * @method array getHaving()
27 */
28 class DAOGetAction extends AbstractGetAction {
29 use Traits\DAOActionTrait;
30
31 /**
32 * Fields to return. Defaults to all standard (non-custom, non-extra) fields `['*']`.
33 *
34 * The keyword `"custom.*"` selects all custom fields. So to select all standard + custom fields, select `['*', 'custom.*']`.
35 *
36 * Use the dot notation to perform joins in the select clause, e.g. selecting `['*', 'contact.*']` from `Email::get()`
37 * will select all fields for the email + all fields for the related contact.
38 *
39 * @var array
40 * @inheritDoc
41 */
42 protected $select = [];
43
44 /**
45 * Joins to other entities.
46 *
47 * Each join is an array of properties:
48 *
49 * ```
50 * [Entity, Required, Bridge, [field, op, value]...]
51 * ```
52 *
53 * - `Entity`: the name of the api entity to join onto.
54 * - `Required`: `TRUE` for an `INNER JOIN`, `FALSE` for a `LEFT JOIN`.
55 * - `Bridge` (optional): Name of a Bridge to incorporate into the join.
56 * - `[field, op, value]...`: zero or more conditions for the ON clause, using the same nested format as WHERE and HAVING
57 * but with the difference that "value" is interpreted as an expression (e.g. can be the name of a field).
58 * Enclose literal values with quotes.
59 *
60 * @var array
61 * @see \Civi\Api4\Generic\Traits\EntityBridge
62 */
63 protected $join = [];
64
65 /**
66 * Field(s) by which to group the results.
67 *
68 * @var array
69 */
70 protected $groupBy = [];
71
72 /**
73 * Clause for filtering results after grouping and filters are applied.
74 *
75 * Each expression should correspond to an item from the SELECT array.
76 *
77 * @var array
78 */
79 protected $having = [];
80
81 /**
82 * @throws \API_Exception
83 * @throws \CRM_Core_Exception
84 */
85 public function _run(Result $result) {
86 // Early return if table doesn't exist yet due to pending upgrade
87 $baoName = $this->getBaoName();
88 if (!$baoName) {
89 // In some cases (eg. site spin-up) the code may attempt to call the api before the entity name is registered.
90 throw new \API_Exception("BAO for {$this->getEntityName()} is not available. This could be a load-order issue");
91 }
92 if (!$baoName::tableHasBeenAdded()) {
93 \Civi::log()->warning("Could not read from {$this->getEntityName()} before table has been added. Upgrade required.", ['civi.tag' => 'upgrade_needed']);
94 return;
95 }
96
97 $this->setDefaultWhereClause();
98 $this->expandSelectClauseWildcards();
99 $this->getObjects($result);
100 }
101
102 /**
103 * @param \Civi\Api4\Generic\Result $result
104 */
105 protected function getObjects(Result $result) {
106 $getCount = in_array('row_count', $this->getSelect());
107 $onlyCount = $this->getSelect() === ['row_count'];
108
109 if (!$onlyCount) {
110 $query = new Api4SelectQuery($this);
111 $rows = $query->run();
112 \CRM_Utils_API_HTMLInputCoder::singleton()->decodeRows($rows);
113 $result->exchangeArray($rows);
114 // No need to fetch count if we got a result set below the limit
115 if (!$this->getLimit() || count($rows) < $this->getLimit()) {
116 $result->rowCount = count($rows) + $this->getOffset();
117 $getCount = FALSE;
118 }
119 }
120 if ($getCount) {
121 $query = new Api4SelectQuery($this);
122 $result->rowCount = $query->getCount();
123 }
124 }
125
126 /**
127 * @param string $fieldName
128 * @param string $op
129 * @param mixed $value
130 * @param bool $isExpression
131 * @return $this
132 * @throws \API_Exception
133 */
134 public function addWhere(string $fieldName, string $op, $value = NULL, bool $isExpression = FALSE) {
135 if (!in_array($op, CoreUtil::getOperators())) {
136 throw new \API_Exception('Unsupported operator');
137 }
138 $this->where[] = [$fieldName, $op, $value, $isExpression];
139 return $this;
140 }
141
142 /**
143 * @return array
144 */
145 public function getGroupBy(): array {
146 return $this->groupBy;
147 }
148
149 /**
150 * @param array $groupBy
151 * @return $this
152 */
153 public function setGroupBy(array $groupBy) {
154 $this->groupBy = $groupBy;
155 return $this;
156 }
157
158 /**
159 * @param string $field
160 * @return $this
161 */
162 public function addGroupBy(string $field) {
163 $this->groupBy[] = $field;
164 return $this;
165 }
166
167 /**
168 * @param string $expr
169 * @param string $op
170 * @param mixed $value
171 * @return $this
172 * @throws \API_Exception
173 */
174 public function addHaving(string $expr, string $op, $value = NULL) {
175 if (!in_array($op, CoreUtil::getOperators())) {
176 throw new \API_Exception('Unsupported operator');
177 }
178 $this->having[] = [$expr, $op, $value];
179 return $this;
180 }
181
182 /**
183 * @param string $entity
184 * @param string|bool $type
185 * @param string $bridge
186 * @param array ...$conditions
187 * @return DAOGetAction
188 */
189 public function addJoin(string $entity, $type = 'LEFT', $bridge = NULL, ...$conditions): DAOGetAction {
190 if ($bridge) {
191 array_unshift($conditions, $bridge);
192 }
193 array_unshift($conditions, $entity, $type);
194 $this->join[] = $conditions;
195 return $this;
196 }
197
198 /**
199 * @param array $join
200 * @return DAOGetAction
201 */
202 public function setJoin(array $join): DAOGetAction {
203 $this->join = $join;
204 return $this;
205 }
206
207 /**
208 * @return array
209 */
210 public function getJoin(): array {
211 return $this->join;
212 }
213
214 }