1 | <?php |
||
8 | class Record implements ArrayAccess |
||
9 | { |
||
10 | /** |
||
11 | * Means record is not yet inserted in database |
||
12 | */ |
||
13 | const STATE_NEW = 'new'; |
||
14 | |||
15 | |||
16 | /** |
||
17 | * Means record have been deleted |
||
18 | */ |
||
19 | const STATE_DELETED = 'deleted'; |
||
20 | |||
21 | /** |
||
22 | * Means record comes from database |
||
23 | * but one of its data have been modified |
||
24 | */ |
||
25 | const STATE_DIRTY = 'dirty'; |
||
26 | |||
27 | /** |
||
28 | * Means record comes from database |
||
29 | * but none of its data have been modified |
||
30 | */ |
||
31 | const STATE_CLEAN = 'clean'; |
||
32 | |||
33 | /** |
||
34 | * @var \ArrayObject |
||
35 | */ |
||
36 | protected $_securedFieldForArrayAccess; |
||
37 | |||
38 | /** |
||
39 | * |
||
40 | * @var ArrayObject |
||
41 | */ |
||
42 | protected $data; |
||
43 | |||
44 | |||
45 | |||
46 | /** |
||
47 | * |
||
48 | * @var string |
||
49 | */ |
||
50 | protected $state; |
||
51 | |||
52 | |||
53 | |||
54 | /** |
||
55 | * |
||
56 | * @param array|ArrayObject $data |
||
57 | * @param Table $table |
||
58 | */ |
||
59 | 50 | public function __construct($data, Table $table) |
|
60 | { |
||
61 | 50 | $this->_securedFieldForArrayAccess = new \ArrayObject(); |
|
62 | 50 | $this->_securedFieldForArrayAccess['table'] = $table; |
|
63 | 50 | $this->_securedFieldForArrayAccess['state'] = null; |
|
64 | //$this->table = $table; |
||
65 | 50 | $this->setData($data); |
|
66 | 49 | } |
|
67 | |||
68 | /** |
||
69 | * Delte record |
||
70 | * |
||
71 | * @throws Exception\LogicException if Record has already been deleted |
||
72 | * @return int affected rows can be > 1 if triggers ... |
||
73 | */ |
||
74 | 9 | public function delete() |
|
75 | { |
||
76 | 9 | $state = $this->getState(); |
|
77 | 9 | if ($state == self::STATE_DELETED) { |
|
78 | 2 | throw new Exception\LogicException(__METHOD__ . ": Record has already been deleted in database."); |
|
79 | } |
||
80 | 9 | if ($state == self::STATE_NEW) { |
|
81 | 2 | throw new Exception\LogicException(__METHOD__ . ": Record has not already been saved in database."); |
|
82 | } |
||
83 | 7 | $affected_rows = $this->getTable()->deleteBy($this->getRecordPrimaryKeyPredicate()); |
|
84 | 7 | $this->setState(self::STATE_DELETED); |
|
85 | 7 | return $affected_rows; |
|
86 | } |
||
87 | |||
88 | /** |
||
89 | * Save a record in database |
||
90 | * |
||
91 | * @throws Exception\LogicException when record has already been deleted |
||
92 | * |
||
93 | * @param boolean $validate_datatype if true will ensure record data correspond to column datatype |
||
94 | * |
||
95 | * @return Record freshly modified record (from database) |
||
96 | */ |
||
97 | 6 | public function save($validate_datatype = false) |
|
98 | { |
||
99 | 6 | $state = $this->getState(); |
|
100 | 6 | if ($state == self::STATE_DELETED) { |
|
101 | 2 | throw new Exception\LogicException(__METHOD__ . "Record has already been deleted in database."); |
|
102 | } |
||
103 | |||
104 | switch ($state) { |
||
105 | 5 | case self::STATE_NEW: |
|
106 | // Means insert |
||
107 | 4 | $new_record = $this->getTable()->insert($this->toArray(), $validate_datatype); |
|
108 | 3 | break; |
|
109 | 3 | case self::STATE_CLEAN: |
|
110 | 3 | case self::STATE_DIRTY: |
|
111 | // Means update |
||
112 | 3 | $predicate = $this->getRecordPrimaryKeyPredicate(); |
|
113 | 2 | $this->getTable()->update($this->toArray(), $predicate, $combination = Predicate\PredicateSet::OP_AND, $validate_datatype); |
|
114 | 1 | $new_record = $this->getTable()->findOneBy($predicate); |
|
115 | 1 | break; |
|
116 | //@codeCoverageIgnoreStart |
||
117 | default: |
||
118 | throw new Exception\LogicException(__METHOD__ . ": Record is not on manageable state."); |
||
119 | //@codeCoverageIgnoreEnd |
||
120 | } |
||
121 | /* |
||
122 | if ($state == self::STATE_NEW) { |
||
123 | // Means insert |
||
124 | $new_record = $this->getTable()->insert($data, $validate_datatype); |
||
125 | |||
126 | } elseif ($state == self::STATE_CLEAN || $state == self::STATE_DIRTY) { |
||
127 | // Means update |
||
128 | $predicate = $this->getRecordPrimaryKeyPredicate(); |
||
129 | $this->getTable()->update($data, $predicate, $combination=Predicate\PredicateSet::OP_AND, $validate_datatype); |
||
130 | $new_record = $this->getTable()->findOneBy($predicate); |
||
131 | } else { |
||
132 | //@codeCoverageIgnoreStart |
||
133 | |||
134 | throw new Exception\LogicException(__METHOD__ . ": Record is not on manageable state."); |
||
135 | //@codeCoverageIgnoreEnd |
||
136 | } |
||
137 | |||
138 | */ |
||
139 | 3 | $this->setData($new_record->toArray()); |
|
140 | 3 | $this->setState(Record::STATE_CLEAN); |
|
141 | 3 | unset($new_record); |
|
142 | 3 | return $this; |
|
143 | } |
||
144 | |||
145 | |||
146 | /** |
||
147 | * Set record data |
||
148 | * |
||
149 | * @param array|ArrayObject $data |
||
150 | * @throws Exception\InvalidArgumentException when the para |
||
151 | * @throws Exception\LogicException when the record has been deleted |
||
152 | * @return Record |
||
153 | */ |
||
154 | 50 | public function setData($data) |
|
169 | |||
170 | /** |
||
171 | * Return an array version of the record |
||
172 | * |
||
173 | * @throws Exception\LogicException when the record has been deleted |
||
174 | * @return array |
||
175 | */ |
||
176 | 8 | public function toArray() |
|
177 | { |
||
178 | 8 | if ($this->getState() == self::STATE_DELETED) { |
|
179 | 1 | throw new Exception\LogicException(__METHOD__ . ": Logic exception, cannot operate on record that was deleted"); |
|
180 | } |
||
181 | 7 | return (array) $this->_securedFieldForArrayAccess['data']; |
|
182 | } |
||
183 | |||
184 | /** |
||
185 | * Return an json version of the record |
||
186 | * |
||
187 | * @throws Exception\LogicException when the record has been deleted |
||
188 | * @return string |
||
189 | */ |
||
190 | 1 | public function toJson() |
|
194 | |||
195 | |||
196 | |||
197 | |||
198 | /** |
||
199 | * |
||
200 | * @param string $field |
||
201 | * @return boolean |
||
202 | */ |
||
203 | 2 | public function offsetExists($field) |
|
204 | { |
||
205 | 2 | if ($this->getState() == self::STATE_DELETED) { |
|
206 | 1 | throw new Exception\LogicException(__METHOD__ . ": Logic exception, cannot operate on record that was deleted"); |
|
207 | } |
||
208 | |||
209 | 1 | return array_key_exists($field, $this->_securedFieldForArrayAccess['data']); |
|
210 | } |
||
211 | |||
212 | /** |
||
213 | * Get field value |
||
214 | * @param string $field |
||
215 | * @return mixed |
||
216 | * @throws Exception\FieldNotFoundException |
||
217 | */ |
||
218 | 41 | public function offsetGet($field) |
|
219 | { |
||
220 | 41 | if ($this->getState() == self::STATE_DELETED) { |
|
221 | 1 | throw new Exception\LogicException(__METHOD__ . ": Logic exception, cannot operate on record that was deleted"); |
|
222 | } |
||
223 | |||
224 | 41 | if (!array_key_exists($field, $this->_securedFieldForArrayAccess['data'])) { |
|
225 | 3 | throw new Exception\FieldNotFoundException(__METHOD__ . ": Cannot get field value, field '$field' does not exists in record."); |
|
226 | } |
||
227 | |||
228 | 41 | return $this->_securedFieldForArrayAccess['data'][$field]; |
|
229 | } |
||
230 | |||
231 | /** |
||
232 | * Set a field |
||
233 | * @param string $field |
||
234 | * @param mixed $value |
||
235 | * @throws Exception\LogicException when the record has been deleted |
||
236 | * @return Record |
||
237 | */ |
||
238 | 6 | public function offsetSet($field, $value) |
|
239 | { |
||
240 | 6 | $state = $this->getState(); |
|
241 | 6 | if ($state == self::STATE_DELETED) { |
|
242 | 1 | throw new Exception\LogicException(__METHOD__ . ": Logic exception, cannot operate on record that was deleted"); |
|
243 | } |
||
244 | |||
245 | |||
246 | 5 | $this->_securedFieldForArrayAccess['data'][$field] = $value; |
|
247 | 5 | if ($state != self::STATE_NEW) { |
|
248 | 4 | $this->setState(self::STATE_DIRTY); |
|
249 | 4 | } |
|
250 | 5 | return $this; |
|
251 | } |
||
252 | |||
253 | /** |
||
254 | * Unset a field |
||
255 | * |
||
256 | * @param string $field |
||
257 | * @throws Exception\LogicException when the record has been deleted |
||
258 | * @return Record |
||
259 | */ |
||
260 | 2 | public function offsetUnset($field) |
|
261 | { |
||
262 | 2 | $state = $this->getState(); |
|
263 | 2 | if ($state == self::STATE_DELETED) { |
|
264 | 1 | throw new Exception\LogicException(__METHOD__ . ": Logic exception, cannot operate on record that was deleted"); |
|
265 | } |
||
266 | |||
267 | 1 | unset($this->_securedFieldForArrayAccess['data'][$field]); |
|
268 | |||
269 | 1 | if ($state != self::STATE_NEW) { |
|
270 | 1 | $this->setState(self::STATE_DIRTY); |
|
271 | 1 | } |
|
272 | |||
273 | 1 | return $this; |
|
274 | } |
||
275 | |||
276 | |||
277 | /** |
||
278 | * Magic setter |
||
279 | * |
||
280 | * @throws Exception\LogicException when the record has been deleted |
||
281 | * @param string $field |
||
282 | * @param mixed $value |
||
283 | * @return void |
||
284 | */ |
||
285 | |||
286 | 2 | public function __set($field, $value) |
|
287 | { |
||
288 | 2 | $state = $this->getState(); |
|
289 | 2 | if ($state == self::STATE_DELETED) { |
|
290 | 1 | throw new Exception\LogicException(__METHOD__ . ": Logic exception, cannot operate on record that was deleted"); |
|
291 | } |
||
292 | |||
293 | |||
294 | 1 | $this->_securedFieldForArrayAccess['data'][$field] = $value; |
|
295 | 1 | if ($state != self::STATE_NEW) { |
|
296 | 1 | $this->setState(self::STATE_DIRTY); |
|
297 | 1 | } |
|
298 | 1 | } |
|
299 | |||
300 | /** |
||
301 | * Magical getter |
||
302 | * |
||
303 | * @param string $field |
||
304 | * @throws Exception\FieldNotFoundException |
||
305 | * @throws Exception\LogicException when the record has been deleted |
||
306 | * @return mixed |
||
307 | */ |
||
308 | 4 | public function __get($field) |
|
309 | { |
||
310 | 4 | if ($this->getState() == self::STATE_DELETED) { |
|
311 | 1 | throw new Exception\LogicException(__METHOD__ . ": Logic exception, cannot operate on record that was deleted"); |
|
312 | } |
||
313 | |||
314 | 3 | if (!array_key_exists($field, $this->_securedFieldForArrayAccess['data'])) { |
|
315 | 1 | throw new Exception\FieldNotFoundException(__METHOD__ . ": Cannot get field value, field '$field' does not exists in record."); |
|
316 | } |
||
317 | |||
318 | 3 | return $this->_securedFieldForArrayAccess['data'][$field]; |
|
319 | } |
||
320 | |||
321 | |||
322 | |||
323 | /** |
||
324 | * For internal use only |
||
325 | * |
||
326 | * @param string $state |
||
327 | * @return Record |
||
328 | */ |
||
329 | 49 | public function setState($state) |
|
330 | { |
||
331 | 49 | $this->_securedFieldForArrayAccess['state'] = $state; |
|
332 | 49 | return $this; |
|
333 | } |
||
334 | |||
335 | /** |
||
336 | * |
||
337 | * @return string |
||
338 | */ |
||
339 | 50 | public function getState() |
|
343 | |||
344 | |||
345 | /** |
||
346 | * Return primary key predicate on record |
||
347 | * |
||
348 | * |
||
349 | * @throws Exception\PrimaryKeyNotFoundException |
||
350 | * @throws Exception\UnexcpectedValueException |
||
351 | * @return array predicate |
||
352 | */ |
||
353 | 10 | protected function getRecordPrimaryKeyPredicate() |
|
354 | { |
||
355 | // Get table primary keys |
||
356 | 10 | $primary_keys = $this->getTable()->getPrimaryKeys(); |
|
357 | 10 | $predicate = []; |
|
358 | 10 | foreach ($primary_keys as $column) { |
|
359 | 10 | $pk_value = $this->offsetGet($column); |
|
360 | 10 | if ($pk_value != '') { |
|
361 | 9 | $predicate[$column] = $pk_value; |
|
362 | 9 | } else { |
|
363 | 1 | throw new Exception\UnexpectedValueException(__METHOD__ . ": Cannot find record primary key values. Record has no primary key value set"); |
|
364 | } |
||
365 | 9 | } |
|
366 | 9 | return $predicate; |
|
367 | } |
||
368 | |||
369 | /** |
||
370 | * Return originating table |
||
371 | * @return Table |
||
372 | */ |
||
373 | 12 | public function getTable() |
|
377 | } |
||
378 |