Commit | Line | Data |
---|---|---|
19b53e5b C |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
41498ac5 | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
19b53e5b | 5 | | | |
41498ac5 TO |
6 | | This work is published under the GNU AGPLv3 license with some | |
7 | | permitted exceptions and without any warranty. For full license | | |
8 | | and copyright information, see https://civicrm.org/licensing | | |
19b53e5b C |
9 | +--------------------------------------------------------------------+ |
10 | */ | |
11 | ||
12 | namespace Civi\Api4\Generic; | |
13 | ||
14 | /** | |
15 | * Container for api results. | |
c2adedc1 CW |
16 | * |
17 | * The Result object has three functions: | |
18 | * | |
19 | * 1. Store the results of the API call (accessible via ArrayAccess). | |
20 | * 2. Store metadata like the Entity & Action names. | |
21 | * - Note: some actions extend the Result object to store extra metadata. | |
22 | * For example, BasicReplaceAction returns ReplaceResult which includes the additional $deleted property to list any items deleted by the operation. | |
23 | * 3. Provide convenience methods like `$result->first()` and `$result->indexBy($field)`. | |
19b53e5b | 24 | */ |
e26fdc3f | 25 | class Result extends \ArrayObject implements \JsonSerializable { |
19b53e5b C |
26 | /** |
27 | * @var string | |
28 | */ | |
29 | public $entity; | |
30 | /** | |
31 | * @var string | |
32 | */ | |
33 | public $action; | |
b65fa6dc CW |
34 | /** |
35 | * @var array | |
36 | */ | |
37 | public $debug; | |
19b53e5b C |
38 | /** |
39 | * Api version | |
40 | * @var int | |
41 | */ | |
42 | public $version = 4; | |
651c4c95 | 43 | /** |
54d8b867 RLAR |
44 | * Not for public use. Instead, please use countFetched(), countMatched() and count(). |
45 | * | |
651c4c95 CW |
46 | * @var int |
47 | */ | |
48 | public $rowCount; | |
19b53e5b | 49 | |
54d8b867 RLAR |
50 | /** |
51 | * How many entities matched the query, regardless of LIMIT clauses. | |
52 | * | |
53 | * This requires that row_count is included in the SELECT. | |
54 | * | |
55 | * @var int | |
56 | */ | |
57 | protected $matchedCount; | |
58 | ||
19b53e5b C |
59 | private $indexedBy; |
60 | ||
61 | /** | |
62 | * Return first result. | |
63 | * @return array|null | |
64 | */ | |
65 | public function first() { | |
66 | foreach ($this as $values) { | |
67 | return $values; | |
68 | } | |
69 | return NULL; | |
70 | } | |
71 | ||
72 | /** | |
73 | * Return last result. | |
74 | * @return array|null | |
75 | */ | |
76 | public function last() { | |
77 | $items = $this->getArrayCopy(); | |
78 | return array_pop($items); | |
79 | } | |
80 | ||
4c6ad36a TO |
81 | /** |
82 | * Return the one-and-only result record. | |
83 | * | |
84 | * If there are too many or too few results, then throw an exception. | |
85 | * | |
86 | * @return array | |
87 | * @throws \API_Exception | |
88 | */ | |
89 | public function single() { | |
90 | $result = NULL; | |
91 | foreach ($this as $values) { | |
92 | if ($result === NULL) { | |
93 | $result = $values; | |
94 | } | |
95 | else { | |
96 | throw new \API_Exception("Expected to find one {$this->entity} record, but there were multiple."); | |
97 | } | |
98 | } | |
99 | ||
100 | if ($result === NULL) { | |
101 | throw new \API_Exception("Expected to find one {$this->entity} record, but there were zero."); | |
102 | } | |
103 | ||
104 | return $result; | |
105 | } | |
106 | ||
19b53e5b C |
107 | /** |
108 | * @param int $index | |
109 | * @return array|null | |
110 | */ | |
111 | public function itemAt($index) { | |
112 | $length = $index < 0 ? 0 - $index : $index + 1; | |
113 | if ($length > count($this)) { | |
114 | return NULL; | |
115 | } | |
116 | return array_slice(array_values($this->getArrayCopy()), $index, 1)[0]; | |
117 | } | |
118 | ||
119 | /** | |
120 | * Re-index the results array (which by default is non-associative) | |
121 | * | |
122 | * Drops any item from the results that does not contain the specified key | |
123 | * | |
124 | * @param string $key | |
125 | * @return $this | |
126 | * @throws \API_Exception | |
127 | */ | |
128 | public function indexBy($key) { | |
129 | $this->indexedBy = $key; | |
130 | if (count($this)) { | |
131 | $newResults = []; | |
132 | foreach ($this as $values) { | |
133 | if (isset($values[$key])) { | |
134 | $newResults[$values[$key]] = $values; | |
135 | } | |
136 | } | |
137 | if (!$newResults) { | |
138 | throw new \API_Exception("Key $key not found in api results"); | |
139 | } | |
140 | $this->exchangeArray($newResults); | |
141 | } | |
142 | return $this; | |
143 | } | |
144 | ||
145 | /** | |
54d8b867 RLAR |
146 | * Returns the number of results. |
147 | * | |
148 | * If row_count was included in the select fields, then this will be the | |
149 | * number of matched entities, even if this differs from the number of | |
150 | * entities fetched. | |
151 | * | |
152 | * If row_count was not included, then this returns the number of entities | |
153 | * fetched, which may or may not be the number of matches. | |
154 | * | |
155 | * Your code might be easier to reason about if you use countFetched() or | |
156 | * countMatched() instead. | |
19b53e5b C |
157 | * |
158 | * @return int | |
159 | */ | |
d77b3d91 | 160 | public function count(): int { |
651c4c95 | 161 | return $this->rowCount ?? parent::count(); |
19b53e5b C |
162 | } |
163 | ||
54d8b867 RLAR |
164 | /** |
165 | * Returns the number of results fetched. | |
166 | * | |
167 | * If a limit was used, this will be a number up to that limit. | |
168 | * | |
169 | * In the case that *only* the row_count was fetched, this will be zero, since no *entities* were fetched. | |
170 | * | |
171 | * @return int | |
172 | */ | |
173 | public function countFetched() :int { | |
174 | return parent::count(); | |
175 | } | |
176 | ||
177 | /** | |
178 | * Returns the number of results | |
179 | * | |
180 | * @return int | |
181 | */ | |
182 | public function countMatched() :int { | |
183 | if (!isset($this->matchedCount)) { | |
184 | throw new \API_Exception("countMatched can only be used if there was no limit set or if row_count was included in the select fields."); | |
185 | } | |
186 | return $this->matchedCount; | |
187 | } | |
188 | ||
189 | /** | |
190 | * Provides a way for API implementations to set the *matched* count. | |
191 | * | |
192 | * The matched count is the number of matching entities, regardless of any imposed limit clause. | |
193 | */ | |
194 | public function setCountMatched(int $c) { | |
195 | $this->matchedCount = $c; | |
196 | ||
197 | // Set rowCount for backward compatibility. | |
198 | $this->rowCount = $c; | |
199 | } | |
200 | ||
19b53e5b C |
201 | /** |
202 | * Reduce each result to one field | |
203 | * | |
204 | * @param $name | |
205 | * @return array | |
206 | */ | |
207 | public function column($name) { | |
208 | return array_column($this->getArrayCopy(), $name, $this->indexedBy); | |
209 | } | |
210 | ||
e26fdc3f CW |
211 | /** |
212 | * @return array | |
213 | */ | |
d77b3d91 | 214 | #[\ReturnTypeWillChange] |
e26fdc3f CW |
215 | public function jsonSerialize() { |
216 | return $this->getArrayCopy(); | |
217 | } | |
218 | ||
19b53e5b | 219 | } |