6d9c19e750b1b215da48b1b90959692f3f6c9fd8
[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 * Get a cell or parent rows
189 * @param string $p
190 * @return \pq\Gateway\Cell|\pq\Gateway\Rowset
191 */
192 function __get($p) {
193 if ($this->table->hasRelation($p)) {
194 return $this->table->by($this, $p);
195 }
196 if (!isset($this->cell[$p])) {
197 $this->cell[$p] = new Cell($this, $p, isset($this->data[$p]) ? $this->data[$p] : null);
198 }
199 return $this->cell[$p];
200 }
201
202 /**
203 * Set a cell value
204 * @param string $p
205 * @param mixed $v
206 */
207 function __set($p, $v) {
208 $this->__get($p)->set($v);
209 }
210
211 /**
212 * Unset a cell value
213 * @param string $p
214 */
215 function __unset($p) {
216 unset($this->data[$p]);
217 unset($this->cell[$p]);
218 }
219
220 /**
221 * Check if a cell isset
222 * @param string $p
223 * @return bool
224 */
225 function __isset($p) {
226 return isset($this->data[$p]) || isset($this->cell[$p]);
227 }
228
229 /**
230 * Get child rows of this row by foreign key
231 * @see \pq\Gateway\Table::of()
232 * @param string $foreign
233 * @param array $args [order, limit, offset]
234 * @return \pq\Gateway\Rowset
235 */
236 function __call($foreign, array $args) {
237 array_unshift($args, $this);
238 $table = forward_static_call(array(get_class($this->getTable()), "resolve"), $foreign);
239 return call_user_func_array(array($table, "of"), $args);
240 }
241
242 /**
243 * Create this row in the database
244 * @return \pq\Gateway\Row
245 */
246 function create() {
247 $rowset = $this->table->create($this->changes());
248 if (!count($rowset)) {
249 throw new \UnexpectedValueException("No row created");
250 }
251 $this->data = $rowset->current()->data;
252 $this->cell = array();
253 return $this;
254 }
255
256 /**
257 * Update this row in the database
258 * @return \pq\Gateway\Row
259 */
260 function update() {
261 $criteria = $this->criteria();
262 if (($lock = $this->getTable()->getLock())) {
263 $lock->onUpdate($this, $criteria);
264 }
265 $rowset = $this->table->update($criteria, $this->changes());
266 if (!count($rowset)) {
267 throw new \UnexpectedValueException("No row updated");
268 }
269 $this->data = $rowset->current()->data;
270 $this->cell = array();
271 return $this;
272 }
273
274 /**
275 * Delete this row in the database
276 * @return \pq\Gateway\Row
277 */
278 function delete() {
279 $rowset = $this->table->delete($this->criteria(), "*");
280 if (!count($rowset)) {
281 throw new \UnexpectedValueException("No row deleted");
282 }
283 $this->data = $rowset->current()->data;
284 return $this->prime();
285 }
286 }