X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fpq-gateway;a=blobdiff_plain;f=lib%2Fpq%2FGateway%2FRow.php;h=cda8d4af67a0ba857a6854b4f8c262f016f5e34d;hp=981a58d9afad2db64c150420b32c23138478d1b6;hb=b39e14404cfeac177d41b152690b6adbb2b1e4bf;hpb=caa2499169b61c7dc6254886e73dcfc000737ed6 diff --git a/lib/pq/Gateway/Row.php b/lib/pq/Gateway/Row.php index 981a58d..cda8d4a 100644 --- a/lib/pq/Gateway/Row.php +++ b/lib/pq/Gateway/Row.php @@ -2,7 +2,9 @@ namespace pq\Gateway; -class Row +use \pq\Query\Expr as QueryExpr; + +class Row implements \JsonSerializable { /** * @var \pq\Gateway\Table @@ -17,39 +19,277 @@ class Row /** * @var array */ - protected $mods = array(); + protected $cell = array(); /** * @param \pq\Gateway\Table $table * @param array $data + * @param bool $prime whether to mark all columns as modified */ - function __construct(Table $table, array $data = null) { + function __construct(Table $table, array $data = null, $prime = false) { $this->table = $table; - $this->data = $data; + $this->data = (array) $data; + + if ($prime) { + $this->prime(); + } + } + + /** + * Copy constructor + * @param array $data + * @return \pq\Gateway\Row + */ + function __invoke(array $data) { + $that = clone $this; + $that->data = $data; + return $that->prime(); + } + + /** + * Export current state as an array + * @return array + * @throws \UnexpectedValueException if a cell has been modified by an expression + */ + function export() { + $export = array_merge($this->data, $this->cell); + foreach ($export as &$val) { + if ($val instanceof Cell) { + if ($val->isExpr()) { + throw new \UnexpectedValueException("Cannot export an SQL expression"); + } + $val = $val->get(); + } + } + return $export; + } + + /** + * Export current state with security sensitive data removed. You should override that. + * Just calls export() by default. + * @return array + */ + function exportPublic() { + return $this->export(); + } + + /** + * @implements JsonSerializable + * @return array + */ + function jsonSerialize() { + return $this->exportPublic(); + } + + /** + * @return \pq\Gateway\Table + */ + function getTable() { + return $this->table; } + /** + * @return array + */ + function getData() { + return $this->data; + } + + /** + * Get all column/value pairs to possibly uniquely identify this row + * @return array + * @throws \OutOfBoundsException if any primary key column is not present in the row + */ + function getIdentity() { + $cols = array(); + if (count($identity = $this->getTable()->getIdentity())) { + foreach ($identity as $col) { + if (!array_key_exists($col, $this->data)) { + throw new \OutOfBoundsException( + sprintf("Column '%s' does not exist in row of table '%s'", + $col, $this->getTable()->getName() + ) + ); + } + $cols[$col] = $this->data[$col]; + } + } else { + $cols = $this->data; + } + return $cols; + } + + /** + * Check whether the row contains modifications + * @return boolean + */ + function isDirty() { + foreach ($this->cell as $cell) { + if ($cell->isDirty()) { + return true; + } + } + return false; + } + + /** + * Refresh the rows data + * @return \pq\Gateway\Row + */ + function refresh() { + $this->data = $this->table->find($this->criteria(), null, 1, 0)->current()->data; + $this->cell = array(); + return $this; + } + + /** + * Fill modified cells + * @return \pq\Gateway\Row + */ + protected function prime() { + $this->cell = array(); + foreach ($this->data as $key => $val) { + $this->cell[$key] = new Cell($this, $key, $val, true); + } + return $this; + } + + /** + * Transform the row's identity to where criteria + * @return array + */ + protected function criteria() { + $where = array(); + foreach ($this->getIdentity() as $col => $val) { + if (isset($val)) { + $where["$col="] = $val; + } else { + $where["$col IS"] = new QueryExpr("NULL"); + } + } + return $where; + } + + /** + * Get an array of changed properties + * @return array + */ + protected function changes() { + $changes = array(); + foreach ($this->cell as $name => $cell) { + if ($cell->isDirty()) { + $changes[$name] = $cell->get(); + } + } + return $changes; + } + + /** + * Cell accessor + * @param string $p column name + * @return \pq\Gateway\Cell + */ + protected function cell($p) { + if (!isset($this->cell[$p])) { + $this->cell[$p] = new Cell($this, $p, isset($this->data[$p]) ? $this->data[$p] : null); + } + return $this->cell[$p]; + } + + /** + * Get a cell or parent rows + * @param string $p + * @return \pq\Gateway\Cell|\pq\Gateway\Rowset + */ function __get($p) { - if (!isset($this->mod[$p])) { - $this->mod[$p] = new Cell($this, $p); + if ($this->table->hasRelation($p)) { + return $this->table->by($this, $p); } - return $this->mod[$p]; + return $this->cell($p); + } + + /** + * Set a cell value + * @param string $p + * @param mixed $v + */ + function __set($p, $v) { + $this->cell($p)->set($v); } + /** + * Unset a cell value + * @param string $p + */ + function __unset($p) { + unset($this->data[$p]); + unset($this->cell[$p]); + } + + /** + * Check if a cell isset + * @param string $p + * @return bool + */ + function __isset($p) { + return isset($this->data[$p]) || isset($this->cell[$p]); + } + + /** + * Get child rows of this row by foreign key + * @see \pq\Gateway\Table::of() + * @param string $foreign + * @param array $args [order, limit, offset] + * @return \pq\Gateway\Rowset + */ + function __call($foreign, array $args) { + array_unshift($args, $this); + $table = forward_static_call(array(get_class($this->getTable()), "resolve"), $foreign); + return call_user_func_array(array($table, "of"), $args); + } + + /** + * Create this row in the database + * @return \pq\Gateway\Row + */ function create() { - $this->data = $this->table->create($this->mods)->getIterator()->current()->data; - $this->mods = array(); + $rowset = $this->table->create($this->changes()); + if (!count($rowset)) { + throw new \UnexpectedValueException("No row created"); + } + $this->data = $rowset->current()->data; + $this->cell = array(); return $this; } + /** + * Update this row in the database + * @return \pq\Gateway\Row + */ function update() { - $this->data = $this->table->update($this->data, $this->mods)->getIterator()->current()->data; - $this->mods = array(); + $criteria = $this->criteria(); + if (($lock = $this->getTable()->getLock())) { + $lock->onUpdate($this, $criteria); + } + $rowset = $this->table->update($criteria, $this->changes()); + if (!count($rowset)) { + throw new \UnexpectedValueException("No row updated"); + } + $this->data = $rowset->current()->data; + $this->cell = array(); return $this; } + /** + * Delete this row in the database + * @return \pq\Gateway\Row + */ function delete() { - $this->data = $this->table->delete($this->data, "*")->getIterator()->current()->data; - $this->mods = array(); - return $this; + $rowset = $this->table->delete($this->criteria(), "*"); + if (!count($rowset)) { + throw new \UnexpectedValueException("No row deleted"); + } + $this->data = $rowset->current()->data; + return $this->prime(); } }