namespace pq\Gateway;
-class Row
+use \pq\Query\Expr as QueryExpr;
+
+class Row implements \JsonSerializable
{
/**
* @var \pq\Gateway\Table
/**
* @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
+ * @param string $p
+ * @return \pq\Gateway\Cell
+ */
function __get($p) {
- if (!isset($this->mod[$p])) {
- $this->mod[$p] = new Cell($this, $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 the parent row
+ * @see \pq\Gateway\Table::by()
+ * @param mixed $foreign table (name)
+ * @param string $ref
+ * @return \pq\Gateway\Rowset
+ */
+ function ofWhich($foreign, $ref = null) {
+ if (!($foreign instanceof Table)) {
+ $foreign = forward_static_call(
+ [get_class($this->getTable()), "resolve"],
+ $foreign
+ );
+ }
+ return $foreign->by($this, $ref);
+ }
+
+ /**
+ * Get child rows of this row by foreign key
+ * @see \pq\Gateway\Table::of()
+ * @param mixed $foreign table (name)
+ * @param string $order
+ * @param int $limit
+ * @param int $offset
+ * @return \pq\Gateway\Rowset
+ */
+ function allOf($foreign, $ref = null, $order = null, $limit = 0, $offset = 0) {
+ if (!($foreign instanceof Table)) {
+ $foreign = forward_static_call(
+ [get_class($this->getTable()), "resolve"],
+ $foreign
+ );
}
- return $this->mod[$p];
+ return $foreign->of($this, $ref, $order, $limit, $offset);
}
+ /**
+ * 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();
+ $this->table->notify($this, "create");
+ $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();
+ $where = $this->criteria();
+ $this->table->notify($this, "update", $where);
+ $rowset = $this->table->update($where, $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;
+ $this->table->notify($this, "delete");
+ $rowset = $this->table->delete($this->criteria(), "*");
+ if (!count($rowset)) {
+ throw new \UnexpectedValueException("No row deleted");
+ }
+ $this->data = $rowset->current()->data;
+ return $this->prime();
}
}