ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
DatabaseGatewayTest.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
23use PHPUnit\Framework\TestCase;
30
31class DatabaseGatewayTest extends TestCase
32{
33 protected const OPTION_CHANGED = 'option_changed';
34 protected const OPTION_NOT_PERSISTED = 'option_not_persisted';
35 protected const TRANSLATION_CHANGED = 'translation_changed';
36 protected const TRANSLATION_NOT_PERSISTED = 'translation_not_persisted';
37
45 protected const ORIGINAL_DATA = [
46 ['field_id' => 1, 'lang_code' => 'de', 'idx' => 0, 'value' => '1val0de', 'position' => 1],
47 ['field_id' => 1, 'lang_code' => 'en', 'idx' => 0, 'value' => '1val0en', 'position' => 1],
48 ['field_id' => 1, 'lang_code' => 'de', 'idx' => 1, 'value' => '1val1de', 'position' => 0],
49 ['field_id' => 1, 'lang_code' => 'en', 'idx' => 1, 'value' => '1val1en', 'position' => 0],
50 ['field_id' => 1, 'lang_code' => 'de', 'idx' => 2, 'value' => '1val2de', 'position' => 2],
51 ['field_id' => 1, 'lang_code' => 'en', 'idx' => 2, 'value' => '1val2en', 'position' => 2],
52 ['field_id' => 2, 'lang_code' => 'de', 'idx' => 0, 'value' => '2val0de', 'position' => 1],
53 ['field_id' => 2, 'lang_code' => 'en', 'idx' => 0, 'value' => '2val0en', 'position' => 1],
54 ['field_id' => 2, 'lang_code' => 'de', 'idx' => 1, 'value' => '2val1de', 'position' => 0],
55 ['field_id' => 2, 'lang_code' => 'en', 'idx' => 1, 'value' => '2val1en', 'position' => 0],
56 ['field_id' => 2, 'lang_code' => 'de', 'idx' => 2, 'value' => '2val2de', 'position' => 2],
57 ['field_id' => 2, 'lang_code' => 'en', 'idx' => 2, 'value' => '2val2en', 'position' => 2]
58 ];
59
60 protected function doArraysHaveSameEntriesIgnoreStatus(array $a, array $b): bool
61 {
62 foreach ($a as $key => $item) {
63 if (isset($item['status'])) {
64 unset($a[$key]['status']);
65 }
66 }
67 foreach ($b as $key => $item) {
68 if (isset($item['status'])) {
69 unset($b[$key]['status']);
70 }
71 }
72
73 if (count($a) !== count($b)) {
74 return false;
75 }
76
77 foreach ($a as $item) {
78 if (!in_array($item, $b)) {
79 return false;
80 }
81 }
82
83 return true;
84 }
85
86 protected function getDBGateway()
87 {
88 return new class (self::ORIGINAL_DATA) extends DatabaseGatewayImplementation {
89 public function __construct(protected array $data)
90 {
91 }
92
93 public function exposeData(): array
94 {
95 return $this->data;
96 }
97
98 protected function deleteOptionsExcept(int $field_id, int ...$keep_option_ids): void
99 {
100 foreach ($this->data as $key => $datum) {
101 if ($datum['field_id'] !== $field_id) {
102 continue;
103 }
104 if (!in_array($datum['idx'], $keep_option_ids)) {
105 unset($this->data[$key]);
106 }
107 }
108 }
109
110 protected function deleteTranslationsOfOptionExcept(
111 int $field_id,
112 int $option_id,
113 string ...$keep_languages
114 ): void {
115 foreach ($this->data as $key => $datum) {
116 if ($datum['idx'] !== $option_id || $datum['field_id'] !== $field_id) {
117 continue;
118 }
119 if (!in_array($datum['lang_code'], $keep_languages)) {
120 unset($this->data[$key]);
121 }
122 }
123 }
124
125 protected function createTranslation(
126 int $field_id,
127 int $option_id,
128 int $position,
129 OptionTranslation $translation
130 ): void {
131 $this->data[] = [
132 'field_id' => $field_id,
133 'lang_code' => $translation->language(),
134 'idx' => $option_id,
135 'value' => $translation->getValue(),
136 'position' => $position
137 ];
138 }
139
140 protected function updateTranslation(
141 int $field_id,
142 int $option_id,
143 int $position,
144 OptionTranslation $translation
145 ): void {
146 foreach ($this->data as $key => $datum) {
147 if (
148 $datum['idx'] !== $option_id ||
149 $datum['field_id'] !== $field_id ||
150 $datum['lang_code'] !== $translation->language()
151 ) {
152 continue;
153 }
154 $this->data[$key]['value'] = $translation->getValue();
155 $this->data[$key]['position'] = $position;
156 }
157 }
158
159 protected function getNextOptionIDInField(int $field_id): int
160 {
161 $max_id = 0;
162 foreach ($this->data as $datum) {
163 if ($datum['field_id'] !== $field_id) {
164 continue;
165 }
166 $max_id = max($max_id, $datum['idx']);
167 }
168 return $max_id + 1;
169 }
170 };
171 }
172
173 protected function getData(
174 int $field_id,
175 bool $is_persisted,
176 bool $contains_changes,
177 array $rows
179 $rows_by_option_id = [];
180 foreach ($rows as $row) {
181 if ($row['field_id'] !== $field_id) {
182 continue;
183 }
184 $rows_by_option_id[$row['idx']][] = $row;
185 }
186
187 $options = [];
188 foreach ($rows_by_option_id as $rows) {
189 $options[] = $this->getOption($rows);
190 }
191
192 $field_id = $is_persisted ? $field_id : null;
193
194 return new class ($field_id, $contains_changes, $options) extends NullSelectSpecificData {
195 public function __construct(
196 protected ?int $field_id,
197 protected bool $contains_changes,
198 protected array $options
199 ) {
200 }
201
202 public function isPersisted(): bool
203 {
204 return !is_null($this->field_id);
205 }
206
207 public function containsChanges(): bool
208 {
209 return $this->contains_changes;
210 }
211
212 public function fieldID(): ?int
213 {
214 return $this->field_id;
215 }
216
217 public function getOptions(): \Generator
218 {
219 yield from $this->options;
220 }
221 };
222 }
223
224 protected function getOption(array $rows): Option
225 {
226 $first_row = $rows[0];
227 $option_id = in_array(self::OPTION_NOT_PERSISTED, $first_row['status'] ?? []) ?
228 null :
229 $first_row['idx'];
230 $position = $first_row['position'];
231 $contains_changes = in_array(self::OPTION_CHANGED, $first_row['status'] ?? []);
232
233 $translations = [];
234 foreach ($rows as $row) {
235 $translations[] = $this->getTranslation($row);
236 }
237
238 return new class ($option_id, $position, $contains_changes, $translations) extends NullOption {
239 public function __construct(
240 protected ?int $option_id,
241 protected int $position,
242 protected bool $contains_changes,
243 protected array $translations
244 ) {
245 }
246
247 public function isPersisted(): bool
248 {
249 return !is_null($this->option_id);
250 }
251
252 public function containsChanges(): bool
253 {
254 return $this->contains_changes;
255 }
256
257 public function optionID(): ?int
258 {
259 return $this->option_id;
260 }
261
262 public function getPosition(): int
263 {
264 return $this->position;
265 }
266
267 public function getTranslations(): \Generator
268 {
269 yield from $this->translations;
270 }
271 };
272 }
273
274 protected function getTranslation(array $row): OptionTranslation
275 {
276 $language = $row['lang_code'];
277 $value = $row['value'];
278 $is_persisted = !in_array(self::TRANSLATION_NOT_PERSISTED, $row['status'] ?? []);
279 $contains_changes = in_array(self::TRANSLATION_CHANGED, $row['status'] ?? []);
280
281 return new class ($language, $value, $is_persisted, $contains_changes) extends NullOptionTranslation {
282 public function __construct(
283 protected string $language,
284 protected string $value,
285 protected bool $is_persisted,
286 protected bool $contains_changes
287 ) {
288 }
289
290 public function isPersisted(): bool
291 {
292 return $this->is_persisted;
293 }
294
295 public function containsChanges(): bool
296 {
297 return $this->contains_changes;
298 }
299
300 public function language(): string
301 {
302 return $this->language;
303 }
304
305 public function getValue(): string
306 {
307 return $this->value;
308 }
309 };
310 }
311
312 public function testCreate(): void
313 {
314 $gateway = $this->getDBGateway();
315 $status = [
316 self::OPTION_CHANGED,
317 self::OPTION_NOT_PERSISTED,
318 self::TRANSLATION_CHANGED,
319 self::TRANSLATION_NOT_PERSISTED
320 ];
321 $added_data_array = [
322 ['field_id' => 78, 'lang_code' => 'de', 'idx' => 1, 'value' => '3val0de', 'position' => 1, 'status' => $status],
323 ['field_id' => 78, 'lang_code' => 'en', 'idx' => 1, 'value' => '3val0en', 'position' => 1, 'status' => $status],
324 ['field_id' => 78, 'lang_code' => 'de', 'idx' => 2, 'value' => '3val1de', 'position' => 0, 'status' => $status]
325 ];
326 $new_data_array = array_merge(self::ORIGINAL_DATA, $added_data_array);
327 $new_data = $this->getData(
328 78,
329 false,
330 true,
331 $new_data_array
332 );
333
334 $gateway->create(78, $new_data);
335 $this->assertTrue($this->doArraysHaveSameEntriesIgnoreStatus(
336 $new_data_array,
337 $gateway->exposeData()
338 ));
339 }
340
341 public function testUpdateRemoveOption(): void
342 {
343 $gateway = $this->getDBGateway();
344 $new_data_array = self::ORIGINAL_DATA;
345 foreach ($new_data_array as $key => $item) {
346 if ($item['field_id'] === 1 && $item['idx'] === 0) {
347 unset($new_data_array[$key]);
348 }
349 }
350 $new_data = $this->getData(
351 1,
352 true,
353 true,
354 $new_data_array
355 );
356
357 $gateway->update($new_data);
358 $this->assertTrue($this->doArraysHaveSameEntriesIgnoreStatus(
359 $new_data_array,
360 $gateway->exposeData()
361 ));
362 }
363
364 public function testUpdateAddOption(): void
365 {
366 $gateway = $this->getDBGateway();
367 $status = [
368 self::OPTION_CHANGED,
369 self::OPTION_NOT_PERSISTED,
370 self::TRANSLATION_CHANGED,
371 self::TRANSLATION_NOT_PERSISTED
372 ];
373 $added_data_array = [
374 ['field_id' => 1, 'lang_code' => 'de', 'idx' => 3, 'value' => '1val3de', 'position' => 3, 'status' => $status],
375 ['field_id' => 1, 'lang_code' => 'en', 'idx' => 3, 'value' => '1val3en', 'position' => 3, 'status' => $status]
376 ];
377 $new_data_array = array_merge(self::ORIGINAL_DATA, $added_data_array);
378 $new_data = $this->getData(
379 1,
380 true,
381 true,
382 $new_data_array
383 );
384
385 $gateway->update($new_data);
386 $this->assertTrue($this->doArraysHaveSameEntriesIgnoreStatus(
387 $new_data_array,
388 $gateway->exposeData()
389 ));
390 }
391
392 public function testUpdateChangeOptionPosition(): void
393 {
394 $gateway = $this->getDBGateway();
395 $new_data_array = self::ORIGINAL_DATA;
396 $status = [self::OPTION_CHANGED];
397 $new_data_array[0]['position'] = 54;
398 $new_data_array[0]['status'] = $status;
399 $new_data_array[1]['position'] = 54;
400 $new_data_array[1]['status'] = $status;
401 $new_data = $this->getData(
402 1,
403 true,
404 true,
405 $new_data_array
406 );
407
408 $gateway->update($new_data);
409 $this->assertTrue($this->doArraysHaveSameEntriesIgnoreStatus(
410 $new_data_array,
411 $gateway->exposeData()
412 ));
413 }
414
415 public function testUpdateRemoveTranslation(): void
416 {
417 $gateway = $this->getDBGateway();
418 $new_data_array = self::ORIGINAL_DATA;
419 $status = [self::OPTION_CHANGED];
420 foreach ($new_data_array as $key => $item) {
421 if ($item['field_id'] !== 1 || $item['idx'] !== 0) {
422 continue;
423 }
424 if ($item['lang_code'] === 'en') {
425 unset($new_data_array[$key]);
426 continue;
427 }
428 $new_data_array[$key]['status'] = $status;
429 }
430 $new_data = $this->getData(
431 1,
432 true,
433 true,
434 $new_data_array
435 );
436
437 $gateway->update($new_data);
438 $this->assertTrue($this->doArraysHaveSameEntriesIgnoreStatus(
439 $new_data_array,
440 $gateway->exposeData()
441 ));
442 }
443
444 public function testUpdateAddTranslation(): void
445 {
446 $gateway = $this->getDBGateway();
447 $status = [
448 self::OPTION_CHANGED,
449 self::TRANSLATION_CHANGED,
450 self::TRANSLATION_NOT_PERSISTED
451 ];
452 $new_data_array = self::ORIGINAL_DATA;
453 foreach ($new_data_array as $key => $item) {
454 if ($item['field_id'] === 1 || $item['idx'] === 1) {
455 $new_data_array[$key]['status'] = [self::OPTION_CHANGED];
456 }
457 }
458 $new_data_array[] =
459 ['field_id' => 1, 'lang_code' => 'fr', 'idx' => 1, 'value' => '1val1fr', 'position' => 0, 'status' => $status];
460 $new_data = $this->getData(
461 1,
462 true,
463 true,
464 $new_data_array
465 );
466
467 $gateway->update($new_data);
468 $this->assertTrue($this->doArraysHaveSameEntriesIgnoreStatus(
469 $new_data_array,
470 $gateway->exposeData()
471 ));
472 }
473
474 public function testUpdateChangeTranslationValue(): void
475 {
476 $gateway = $this->getDBGateway();
477 $new_data_array = self::ORIGINAL_DATA;
478 foreach ($new_data_array as $key => $item) {
479 if ($item['field_id'] === 2 || $item['idx'] === 1) {
480 $new_data_array[$key]['status'] = [self::OPTION_CHANGED];
481 }
482 }
483 $new_data_array[4]['value'] = 'different value';
484 $new_data_array[4]['status'] = [self::OPTION_CHANGED, self::TRANSLATION_CHANGED];
485 $new_data = $this->getData(
486 1,
487 true,
488 true,
489 $new_data_array
490 );
491
492 $gateway->update($new_data);
493 $this->assertTrue($this->doArraysHaveSameEntriesIgnoreStatus(
494 $new_data_array,
495 $gateway->exposeData()
496 ));
497 }
498}
getData(int $field_id, bool $is_persisted, bool $contains_changes, array $rows)
__construct()
Constructor setup ILIAS global object @access public.
Definition: class.ilias.php:76
getValue()
Get the value that is displayed in the input client side.
Definition: Group.php:49
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples