refactor relations
[m6w6/pq-gateway] / lib / pq / Gateway / Row.php
1 <?php
2
3 namespace pq\Gateway;
4
5 use \pq\Query\Expr as QueryExpr;
6
7 class Row implements \JsonSerializable
8 {
9 /**
10 * @var \pq\Gateway\Table
11 */
12 protected $table;
13
14 /**
15 * @var array
16 */
17 protected $data;
18
19 /**
20 * @var array
21 */
22 protected $cell = array();
23
24 /**
25 * @param \pq\Gateway\Table $table
26 * @param array $data
27 * @param bool $prime whether to mark all columns as modified
28 */
29 function __construct(Table $table, array $data = null, $prime = false) {
30 $this->table = $table;
31 $this->data = (array) $data;
32
33 if ($prime) {
34 $this->prime();
35 }
36 }
37
38 /**
39 * Copy constructor
40 * @param array $data
41 * @return \pq\Gateway\Row
42 */
43 function __invoke(array $data) {
44 $that = clone $this;
45 $that->data = $data;
46 return $that->prime();
47 }
48
49 /**
50 * Export current state as an array
51 * @return array
52 * @throws \UnexpectedValueException if a cell has been modified by an expression
53 */
54 function export() {
55 $export = array_merge($this->data, $this->cell);
56 foreach ($export as &$val) {
57 if ($val instanceof Cell) {
58 if ($val->isExpr()) {
59 throw new \UnexpectedValueException("Cannot export an SQL expression");
60 }
61 $val = $val->get();
62 }
63 }
64 return $export;
65 }
66
67 /**
68 * Export current state with security sensitive data removed. You should override that.
69 * Just calls export() by default.
70 * @return array
71 */
72 function exportPublic() {
73 return $this->export();
74 }
75
76 /**
77 * @implements JsonSerializable
78 * @return array
79 */
80 function jsonSerialize() {
81 return $this->exportPublic();
82 }
83
84 /**
85 * @return \pq\Gateway\Table
86 */
87 function getTable() {
88 return $this->table;
89 }
90
91 /**
92 * @return array
93 */
94 function getData() {
95 return $this->data;
96 }
97
98 /**
99 * Get all column/value pairs to possibly uniquely identify this row
100 * @return array
101 * @throws \OutOfBoundsException if any primary key column is not present in the row
102 */
103 function getIdentity() {
104 $cols = array();
105 if (count($identity = $this->getTable()->getIdentity())) {
106 foreach ($identity as $col) {
107 if (!array_key_exists($col, $this->data)) {
108 throw new \OutOfBoundsException(
109 sprintf("Column '%s' does not exist in row of table '%s'",
110 $col, $this->getTable()->getName()
111 )
112 );
113 }
114 $cols[$col] = $this->data[$col];
115 }
116 } else {
117 $cols = $this->data;
118 }
119 return $cols;
120 }
121
122 /**
123 * Check whether the row contains modifications
124 * @return boolean
125 */
126 function isDirty() {
127 foreach ($this->cell as $cell) {
128 if ($cell->isDirty()) {
129 return true;
130 }
131 }
132 return false;
133 }
134
135 /**
136 * Refresh the rows data
137 * @return \pq\Gateway\Row
138 */
139 function refresh() {
140 $this->data = $this->table->find($this->criteria(), null, 1, 0)->current()->data;
141 $this->cell = array();
142 return $this;
143 }
144
145 /**
146 * Fill modified cells
147 * @return \pq\Gateway\Row
148 */
149 protected function prime() {
150 $this->cell = array();
151 foreach ($this->data as $key => $val) {
152 $this->cell[$key] = new Cell($this, $key, $val, true);
153 }
154 return $this;
155 }
156
157 /**
158 * Transform the row's identity to where criteria
159 * @return array
160 */
161 protected function criteria() {
162 $where = array();
163 foreach ($this->getIdentity() as $col => $val) {
164 if (isset($val)) {
165 $where["$col="] = $val;
166 } else {
167 $where["$col IS"] = new QueryExpr("NULL");
168 }
169 }
170 return $where;
171 }
172
173 /**
174 * Get an array of changed properties
175 * @return array
176 */
177 protected function changes() {
178 $changes = array();
179 foreach ($this->cell as $name => $cell) {
180 if ($cell->isDirty()) {
181 $changes[$name] = $cell->get();
182 }
183 }
184 return $changes;
185 }
186
187 /**
188 * Cell accessor
189 * @param string $p column name
190 * @return \pq\Gateway\Cell
191 */
192 protected function cell($p) {
193 if (!isset($this->cell[$p])) {
194 $this->cell[$p] = new Cell($this, $p, isset($this->data[$p]) ? $this->data[$p] : null);
195 }
196 return $this->cell[$p];
197 }
198
199 /**
200 * Get a cell
201 * @param string $p
202 * @return \pq\Gateway\Cell
203 */
204 function __get($p) {
205 return $this->cell($p);
206 }
207
208 /**
209 * Set a cell value
210 * @param string $p
211 * @param mixed $v
212 */
213 function __set($p, $v) {
214 $this->cell($p)->set($v);
215 }
216
217 /**
218 * Unset a cell value
219 * @param string $p
220 */
221 function __unset($p) {
222 unset($this->data[$p]);
223 unset($this->cell[$p]);
224 }
225
226 /**
227 * Check if a cell isset
228 * @param string $p
229 * @return bool
230 */
231 function __isset($p) {
232 return isset($this->data[$p]) || isset($this->cell[$p]);
233 }
234
235 /**
236 * Get the parent row
237 * @see \pq\Gateway\Table::by()
238 * @param mixed $foreign table (name)
239 * @param string $ref
240 * @return \pq\Gateway\Rowset
241 */
242 function ofWhich($foreign, $ref = null) {
243 if (!($foreign instanceof Table)) {
244 $foreign = forward_static_call(
245 [get_class($this->getTable()), "resolve"],
246 $foreign
247 );
248 }
249 return $foreign->by($this, $ref);
250 }
251
252 /**
253 * Get child rows of this row by foreign key
254 * @see \pq\Gateway\Table::of()
255 * @param mixed $foreign table (name)
256 * @param string $order
257 * @param int $limit
258 * @param int $offset
259 * @return \pq\Gateway\Rowset
260 */
261 function allOf($foreign, $ref = null, $order = null, $limit = 0, $offset = 0) {
262 if (!($foreign instanceof Table)) {
263 $foreign = forward_static_call(
264 [get_class($this->getTable()), "resolve"],
265 $foreign
266 );
267 }
268 return $foreign->of($this, $ref, $order, $limit, $offset);
269 }
270
271 /**
272 * Create this row in the database
273 * @return \pq\Gateway\Row
274 */
275 function create() {
276 $this->table->notify($this, "create");
277 $rowset = $this->table->create($this->changes());
278 if (!count($rowset)) {
279 throw new \UnexpectedValueException("No row created");
280 }
281 $this->data = $rowset->current()->data;
282 $this->cell = array();
283 return $this;
284 }
285
286 /**
287 * Update this row in the database
288 * @return \pq\Gateway\Row
289 */
290 function update() {
291 $where = $this->criteria();
292 $this->table->notify($this, "update", $where);
293 $rowset = $this->table->update($where, $this->changes());
294 if (!count($rowset)) {
295 throw new \UnexpectedValueException("No row updated");
296 }
297 $this->data = $rowset->current()->data;
298 $this->cell = array();
299 return $this;
300 }
301
302 /**
303 * Delete this row in the database
304 * @return \pq\Gateway\Row
305 */
306 function delete() {
307 $this->table->notify($this, "delete");
308 $rowset = $this->table->delete($this->criteria(), "*");
309 if (!count($rowset)) {
310 throw new \UnexpectedValueException("No row deleted");
311 }
312 $this->data = $rowset->current()->data;
313 return $this->prime();
314 }
315 }