packages/ztd-query-core/src/Shadow/CascadedChildren.php
1<?php
2
3declare(strict_types=1);
4
5namespace ZtdQuery\Shadow;
6
7use ZtdQuery\Schema\RowSet;
8use ZtdQuery\Schema\TableDefinition;
9use ZtdQuery\Shadow\Row\RowChange;
10
11/**
12 * The rows of one child table as a cascade leaves them.
13 *
14 * Following a constraint reads the child rows once and then rewrites some and
15 * drops others, and the caller needs both the rows to write back and an
16 * account of what happened to them. Keeping the two together is what makes
17 * the account impossible to disagree with the rows.
18 *
19 * @phpstan-import-type Row from TableDefinition
20 */
21final class CascadedChildren
22{
23 private RowSet $rows;
24
25 private RowSet $deleted;
26
27 /** @var list<RowChange> */
28 private array $updated = [];
29
30 /**
31 * @param array<int, Row> $rows The child rows as they stood
32 */
33 public function __construct(array $rows)
34 {
35 $this->rows = new RowSet($rows);
36 $this->deleted = new RowSet();
37 }
38
39 /**
40 * Answers the child rows as they stand.
41 *
42 * @return array<int, Row> The rows
43 */
44 public function rows(): array
45 {
46 return $this->rows->rows;
47 }
48
49 /**
50 * Writes a row over the one in that position, and records the change.
51 *
52 * @param int $index Position of the row being written over
53 * @param Row $row The row as it should now be
54 */
55 public function replace(int $index, array $row): void
56 {
57 $rows = $this->rows->rows;
58 $this->updated[] = new RowChange($rows[$index], $row);
59 $rows[$index] = $row;
60 $this->rows = new RowSet($rows);
61 }
62
63 /**
64 * Drops the rows in those positions, and records that they went.
65 *
66 * @param list<int> $indexes Positions of the rows that went
67 */
68 public function remove(array $indexes): void
69 {
70 $remaining = [];
71 $deleted = $this->deleted->rows;
72 foreach ($this->rows->rows as $index => $row) {
73 if (in_array($index, $indexes, true)) {
74 $deleted[] = $row;
75 continue;
76 }
77 $remaining[] = $row;
78 }
79 $this->rows = new RowSet($remaining);
80 $this->deleted = new RowSet($deleted);
81 }
82
83 /**
84 * Answers the rows that went.
85 *
86 * @return list<Row> The rows
87 */
88 public function deleted(): array
89 {
90 return array_values($this->deleted->rows);
91 }
92
93 /**
94 * Answers what happened to the rows that stayed.
95 *
96 * @return list<RowChange> The changes
97 */
98 public function updated(): array
99 {
100 return $this->updated;
101 }
102
103 /**
104 * Reports whether the cascade reached this table at all.
105 *
106 * @return bool True when nothing went and nothing changed
107 */
108 public function areUnchanged(): bool
109 {
110 return $this->deleted->rows === [] && $this->updated === [];
111 }
112}
113