PageRenderTime 44ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/extras/notorm/NotORM/Result.php

https://gitlab.com/gustCL/syscar
PHP | 830 lines | 632 code | 55 blank | 143 comment | 175 complexity | 135ab99725031254827cb7e40188ec50 MD5 | raw file
  1. <?php
  2. /** Filtered table representation
  3. * @method NotORM_Result and(mixed $condition, mixed $parameters = array()) Add AND condition
  4. * @method NotORM_Result or(mixed $condition, mixed $parameters = array()) Add OR condition
  5. */
  6. class NotORM_Result extends NotORM_Abstract implements Iterator, ArrayAccess, Countable, JsonSerializable {
  7. protected $single;
  8. protected $select = array(), $conditions = array(), $where = array(), $parameters = array(), $order = array(), $limit = null, $offset = null, $group = "", $having = "", $lock = null;
  9. protected $union = array(), $unionOrder = array(), $unionLimit = null, $unionOffset = null;
  10. protected $data, $referencing = array(), $aggregation = array(), $accessed, $access, $keys = array();
  11. /** Create table result
  12. * @param string
  13. * @param NotORM
  14. * @param bool single row
  15. * @access protected must be public because it is called from NotORM
  16. */
  17. function __construct($table, NotORM $notORM, $single = false) {
  18. $this->table = $table;
  19. $this->notORM = $notORM;
  20. $this->single = $single;
  21. $this->primary = $notORM->structure->getPrimary($table);
  22. }
  23. /** Save data to cache and empty result
  24. */
  25. function __destruct() {
  26. if ($this->notORM->cache && !$this->select && isset($this->rows)) {
  27. $access = $this->access;
  28. if (is_array($access)) {
  29. $access = array_filter($access);
  30. }
  31. $this->notORM->cache->save("$this->table;" . implode(",", $this->conditions), $access);
  32. }
  33. $this->rows = null;
  34. unset($this->data);
  35. }
  36. protected function limitString($limit, $offset = null) {
  37. $return = "";
  38. if (isset($limit) && $this->notORM->driver != "oci" && $this->notORM->driver != "dblib" && $this->notORM->driver != "mssql" && $this->notORM->driver != "sqlsrv") {
  39. $return .= " LIMIT $limit";
  40. if ($offset) {
  41. $return .= " OFFSET $offset";
  42. }
  43. }
  44. return $return;
  45. }
  46. protected function removeExtraDots($expression) {
  47. return preg_replace('~(?:\\b[a-z_][a-z0-9_.:]*[.:])?([a-z_][a-z0-9_]*)[.:]([a-z_*])~i', '\\1.\\2', $expression); // rewrite tab1.tab2.col
  48. }
  49. protected function whereString() {
  50. $return = "";
  51. if ($this->group) {
  52. $return .= " GROUP BY $this->group";
  53. }
  54. if ($this->having) {
  55. $return .= " HAVING $this->having";
  56. }
  57. if ($this->order) {
  58. $return .= " ORDER BY " . implode(", ", $this->order);
  59. }
  60. $return = $this->removeExtraDots($return);
  61. $where = $this->where;
  62. if (isset($this->limit) && $this->notORM->driver == "oci") {
  63. $where[] = ($where ? " AND " : "") . "(" . ($this->offset ? "rownum > $this->offset AND " : "") . "rownum <= " . ($this->limit + $this->offset) . ")"; //! rownum > doesn't work - requires subselect (see adminer/drivers/oracle.inc.php)
  64. }
  65. if ($where) {
  66. $return = " WHERE " . implode($where) . $return;
  67. }
  68. $return .= $this->limitString($this->limit, $this->offset);
  69. if (isset($this->lock)) {
  70. $return .= ($this->lock ? " FOR UPDATE" : " LOCK IN SHARE MODE");
  71. }
  72. return $return;
  73. }
  74. protected function topString($limit, $offset = null) {
  75. if (isset($limit) && ($this->notORM->driver == "dblib" || $this->notORM->driver == "mssql" || $this->notORM->driver == "sqlsrv")) {
  76. return " TOP ($this->limit)"; //! offset is not supported
  77. }
  78. return "";
  79. }
  80. protected function createJoins($val) {
  81. $return = array();
  82. preg_match_all('~\\b([a-z_][a-z0-9_.:]*[.:])[a-z_*]~i', $val, $matches);
  83. foreach ($matches[1] as $names) {
  84. $parent = $this->table;
  85. if ($names != "$parent.") { // case-sensitive
  86. preg_match_all('~\\b([a-z_][a-z0-9_]*)([.:])~i', $names, $matches, PREG_SET_ORDER);
  87. foreach ($matches as $match) {
  88. list(, $name, $delimiter) = $match;
  89. $table = $this->notORM->structure->getReferencedTable($name, $parent);
  90. $column = ($delimiter == ':' ? $this->notORM->structure->getPrimary($parent) : $this->notORM->structure->getReferencedColumn($name, $parent));
  91. $primary = ($delimiter == ':' ? $this->notORM->structure->getReferencedColumn($parent, $table) : $this->notORM->structure->getPrimary($table));
  92. $return[$name] = " LEFT JOIN $table" . ($table != $name ? " AS $name" : "") . " ON $parent.$column = $name.$primary"; // should use alias if the table is used on more places
  93. $parent = $name;
  94. }
  95. }
  96. }
  97. return $return;
  98. }
  99. /** Get SQL query
  100. * @return string
  101. */
  102. function __toString() {
  103. $return = "SELECT" . $this->topString($this->limit, $this->offset) . " ";
  104. $join = $this->createJoins(implode(",", $this->conditions) . "," . implode(",", $this->select) . ",$this->group,$this->having," . implode(",", $this->order));
  105. if (!isset($this->rows) && $this->notORM->cache && !is_string($this->accessed)) {
  106. $this->accessed = $this->notORM->cache->load("$this->table;" . implode(",", $this->conditions));
  107. $this->access = $this->accessed;
  108. }
  109. if ($this->select) {
  110. $return .= $this->removeExtraDots(implode(", ", $this->select));
  111. } elseif ($this->accessed) {
  112. $return .= ($join ? "$this->table." : "") . implode(", " . ($join ? "$this->table." : ""), array_keys($this->accessed));
  113. } else {
  114. $return .= ($join ? "$this->table." : "") . "*";
  115. }
  116. $return .= " FROM $this->table" . implode($join) . $this->whereString();
  117. if ($this->union) {
  118. $return = ($this->notORM->driver == "sqlite" || $this->notORM->driver == "oci" ? $return : "($return)") . implode($this->union);
  119. if ($this->unionOrder) {
  120. $return .= " ORDER BY " . implode(", ", $this->unionOrder);
  121. }
  122. $return .= $this->limitString($this->unionLimit, $this->unionOffset);
  123. $top = $this->topString($this->unionLimit, $this->unionOffset);
  124. if ($top) {
  125. $return = "SELECT$top * FROM ($return) t";
  126. }
  127. }
  128. return $return;
  129. }
  130. protected function query($query, $parameters) {
  131. if ($this->notORM->debug) {
  132. if (!is_callable($this->notORM->debug)) {
  133. $debug = "$query;";
  134. if ($parameters) {
  135. $debug .= " -- " . implode(", ", array_map(array($this, 'quote'), $parameters));
  136. }
  137. $pattern = '(^' . preg_quote(dirname(__FILE__)) . '(\\.php$|[/\\\\]))'; // can be static
  138. foreach (debug_backtrace() as $backtrace) {
  139. if (isset($backtrace["file"]) && !preg_match($pattern, $backtrace["file"])) { // stop on first file outside NotORM source codes
  140. break;
  141. }
  142. }
  143. error_log("$backtrace[file]:$backtrace[line]:$debug\n", 0);
  144. } elseif (call_user_func($this->notORM->debug, $query, $parameters) === false) {
  145. return false;
  146. }
  147. }
  148. $return = $this->notORM->connection->prepare($query);
  149. if (!$return || !$return->execute(array_map(array($this, 'formatValue'), $parameters))) {
  150. $return = false;
  151. }
  152. if ($this->notORM->debugTimer) {
  153. call_user_func($this->notORM->debugTimer);
  154. }
  155. return $return;
  156. }
  157. protected function formatValue($val) {
  158. if ($val instanceof DateTime) {
  159. return $val->format("Y-m-d H:i:s"); //! may be driver specific
  160. }
  161. return $val;
  162. }
  163. protected function quote($val) {
  164. if (!isset($val)) {
  165. return "NULL";
  166. }
  167. if (is_array($val)) { // (a, b) IN ((1, 2), (3, 4))
  168. return "(" . implode(", ", array_map(array($this, 'quote'), $val)) . ")";
  169. }
  170. $val = $this->formatValue($val);
  171. if (is_float($val)) {
  172. return sprintf("%F", $val); // otherwise depends on setlocale()
  173. }
  174. if ($val === false) {
  175. return "0";
  176. }
  177. if (is_int($val) || $val instanceof NotORM_Literal) { // number or SQL code - for example "NOW()"
  178. return (string) $val;
  179. }
  180. return $this->notORM->connection->quote($val);
  181. }
  182. /** Shortcut for call_user_func_array(array($this, 'insert'), $rows)
  183. * @param array
  184. * @return int number of affected rows or false in case of an error
  185. */
  186. function insert_multi(array $rows) {
  187. if ($this->notORM->freeze) {
  188. return false;
  189. }
  190. if (!$rows) {
  191. return 0;
  192. }
  193. $data = reset($rows);
  194. $parameters = array();
  195. if ($data instanceof NotORM_Result) {
  196. $parameters = $data->parameters; //! other parameters
  197. $data = (string) $data;
  198. } elseif ($data instanceof Traversable) {
  199. $data = iterator_to_array($data);
  200. }
  201. $insert = $data;
  202. if (is_array($data)) {
  203. $values = array();
  204. foreach ($rows as $value) {
  205. if ($value instanceof Traversable) {
  206. $value = iterator_to_array($value);
  207. }
  208. $values[] = $this->quote($value);
  209. foreach ($value as $val) {
  210. if ($val instanceof NotORM_Literal && $val->parameters) {
  211. $parameters = array_merge($parameters, $val->parameters);
  212. }
  213. }
  214. }
  215. //! driver specific extended insert
  216. $insert = ($data || $this->notORM->driver == "mysql"
  217. ? "(" . implode(", ", array_keys($data)) . ") VALUES " . implode(", ", $values)
  218. : "DEFAULT VALUES"
  219. );
  220. }
  221. // requires empty $this->parameters
  222. $return = $this->query("INSERT INTO $this->table $insert", $parameters);
  223. if (!$return) {
  224. return false;
  225. }
  226. $this->rows = null;
  227. return $return->rowCount();
  228. }
  229. /** Insert row in a table
  230. * @param mixed array($column => $value)|Traversable for single row insert or NotORM_Result|string for INSERT ... SELECT
  231. * @param ... used for extended insert
  232. * @return mixed inserted NotORM_Row or false in case of an error or number of affected rows for INSERT ... SELECT
  233. */
  234. function insert($data) {
  235. $rows = func_get_args();
  236. $return = $this->insert_multi($rows);
  237. if (!$return) {
  238. return false;
  239. }
  240. if (!is_array($data)) {
  241. return $return;
  242. }
  243. if (!isset($data[$this->primary]) && ($id = $this->notORM->connection->lastInsertId($this->notORM->structure->getSequence($this->table)))) {
  244. $data[$this->primary] = $id;
  245. }
  246. return new $this->notORM->rowClass($data, $this);
  247. }
  248. /** Update all rows in result set
  249. * @param array ($column => $value)
  250. * @return int number of affected rows or false in case of an error
  251. */
  252. function update(array $data) {
  253. if ($this->notORM->freeze) {
  254. return false;
  255. }
  256. if (!$data) {
  257. return 0;
  258. }
  259. $values = array();
  260. $parameters = array();
  261. foreach ($data as $key => $val) {
  262. // doesn't use binding because $this->parameters can be filled by ? or :name
  263. $values[] = "$key = " . $this->quote($val);
  264. if ($val instanceof NotORM_Literal && $val->parameters) {
  265. $parameters = array_merge($parameters, $val->parameters);
  266. }
  267. }
  268. if ($this->parameters) {
  269. $parameters = array_merge($parameters, $this->parameters);
  270. }
  271. // joins in UPDATE are supported only in MySQL
  272. $return = $this->query("UPDATE" . $this->topString($this->limit, $this->offset) . " $this->table SET " . implode(", ", $values) . $this->whereString(), $parameters);
  273. if (!$return) {
  274. return false;
  275. }
  276. return $return->rowCount();
  277. }
  278. /** Insert row or update if it already exists
  279. * @param array ($column => $value)
  280. * @param array ($column => $value)
  281. * @param array ($column => $value), empty array means use $insert
  282. * @return int number of affected rows or false in case of an error
  283. */
  284. function insert_update(array $unique, array $insert, array $update = array()) {
  285. if (!$update) {
  286. $update = $insert;
  287. }
  288. $insert = $unique + $insert;
  289. $values = "(" . implode(", ", array_keys($insert)) . ") VALUES " . $this->quote($insert);
  290. //! parameters
  291. if ($this->notORM->driver == "mysql") {
  292. $set = array();
  293. if (!$update) {
  294. $update = $unique;
  295. }
  296. foreach ($update as $key => $val) {
  297. $set[] = "$key = " . $this->quote($val);
  298. //! parameters
  299. }
  300. return $this->insert("$values ON DUPLICATE KEY UPDATE " . implode(", ", $set));
  301. } else {
  302. $connection = $this->notORM->connection;
  303. $errorMode = $connection->getAttribute(PDO::ATTR_ERRMODE);
  304. $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  305. try {
  306. $return = $this->insert($values);
  307. $connection->setAttribute(PDO::ATTR_ERRMODE, $errorMode);
  308. return $return;
  309. } catch (PDOException $e) {
  310. $connection->setAttribute(PDO::ATTR_ERRMODE, $errorMode);
  311. if ($e->getCode() == "23000" || $e->getCode() == "23505") { // "23000" - duplicate key, "23505" unique constraint pgsql
  312. if (!$update) {
  313. return 0;
  314. }
  315. $clone = clone $this;
  316. $return = $clone->where($unique)->update($update);
  317. return ($return ? $return + 1 : $return);
  318. }
  319. if ($errorMode == PDO::ERRMODE_EXCEPTION) {
  320. throw $e;
  321. } elseif ($errorMode == PDO::ERRMODE_WARNING) {
  322. trigger_error("PDOStatement::execute(): " . $e->getMessage(), E_USER_WARNING); // E_WARNING is unusable
  323. }
  324. }
  325. }
  326. }
  327. /** Get last insert ID
  328. * @return string number
  329. */
  330. function insert_id() {
  331. return $this->notORM->connection->lastInsertId();
  332. }
  333. /** Delete all rows in result set
  334. * @return int number of affected rows or false in case of an error
  335. */
  336. function delete() {
  337. if ($this->notORM->freeze) {
  338. return false;
  339. }
  340. $return = $this->query("DELETE" . $this->topString($this->limit, $this->offset) . " FROM $this->table" . $this->whereString(), $this->parameters);
  341. if (!$return) {
  342. return false;
  343. }
  344. return $return->rowCount();
  345. }
  346. /** Add select clause, more calls appends to the end
  347. * @param string for example "column, MD5(column) AS column_md5", empty string to reset previously set columns
  348. * @param string ...
  349. * @return NotORM_Result fluent interface
  350. */
  351. function select($columns) {
  352. $this->__destruct();
  353. if ($columns != "") {
  354. foreach (func_get_args() as $columns) {
  355. $this->select[] = $columns;
  356. }
  357. } else {
  358. $this->select = array();
  359. }
  360. return $this;
  361. }
  362. /** Add where condition, more calls appends with AND
  363. * @param mixed string possibly containing ? or :name; or array($condition => $parameters, ...)
  364. * @param mixed array accepted by PDOStatement::execute or a scalar value
  365. * @param mixed ...
  366. * @return NotORM_Result fluent interface
  367. */
  368. function where($condition, $parameters = array()) {
  369. $args = func_get_args();
  370. return $this->whereOperator("AND", $args);
  371. }
  372. protected function whereOperator($operator, array $args) {
  373. $condition = $args[0];
  374. $parameters = (count($args) > 1 ? $args[1] : array());
  375. if (is_array($condition)) { // where(array("column1" => 1, "column2 > ?" => 2))
  376. foreach ($condition as $key => $val) {
  377. $this->where($key, $val);
  378. }
  379. return $this;
  380. }
  381. $this->__destruct();
  382. $this->conditions[] = "$operator $condition";
  383. $condition = $this->removeExtraDots($condition);
  384. if (count($args) != 2 || strpbrk($condition, "?:")) { // where("column < ? OR column > ?", array(1, 2))
  385. if (count($args) != 2 || !is_array($parameters)) { // where("column < ? OR column > ?", 1, 2)
  386. $parameters = array_slice($args, 1);
  387. }
  388. $this->parameters = array_merge($this->parameters, $parameters);
  389. } elseif ($parameters === null) { // where("column", null)
  390. $condition .= " IS NULL";
  391. } elseif ($parameters instanceof NotORM_Result) { // where("column", $db->$table())
  392. $clone = clone $parameters;
  393. if (!$clone->select) {
  394. $clone->select($this->notORM->structure->getPrimary($clone->table));
  395. }
  396. if ($this->notORM->driver != "mysql") {
  397. if ($clone instanceof NotORM_MultiResult) {
  398. array_shift($clone->select);
  399. $clone->single();
  400. }
  401. $condition .= " IN ($clone)";
  402. $this->parameters = array_merge($this->parameters, $clone->parameters);
  403. } else {
  404. $in = array();
  405. foreach ($clone as $row) {
  406. $row = array_values(iterator_to_array($row));
  407. if ($clone instanceof NotORM_MultiResult && count($row) > 1) {
  408. array_shift($row);
  409. }
  410. if (count($row) == 1) {
  411. $in[] = $this->quote($row[0]);
  412. } else {
  413. $in[] = $this->quote($row);
  414. }
  415. }
  416. if ($in) {
  417. $condition .= " IN (" . implode(", ", $in) . ")";
  418. } else {
  419. $condition = "($condition) IS NOT NULL AND $condition IS NULL"; // $condition = "NOT id"
  420. }
  421. }
  422. } elseif (!is_array($parameters)) { // where("column", "x")
  423. $condition .= " = " . $this->quote($parameters);
  424. } else { // where("column", array(1, 2))
  425. $condition = $this->whereIn($condition, $parameters);
  426. }
  427. $this->where[] = (preg_match('~^\)+$~', $condition)
  428. ? $condition
  429. : ($this->where ? " $operator " : "") . "($condition)"
  430. );
  431. return $this;
  432. }
  433. protected function whereIn($condition, $parameters) {
  434. if (!$parameters) {
  435. $condition = "($condition) IS NOT NULL AND $condition IS NULL";
  436. } elseif ($this->notORM->driver != "oci") {
  437. $column = $condition;
  438. $condition .= " IN " . $this->quote($parameters);
  439. $nulls = array_filter($parameters, 'is_null');
  440. if ($nulls) {
  441. $condition = "$condition OR $column IS NULL";
  442. }
  443. } else { // http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/expressions014.htm
  444. $or = array();
  445. for ($i=0; $i < count($parameters); $i += 1000) {
  446. $or[] = "$condition IN " . $this->quote(array_slice($parameters, $i, 1000));
  447. }
  448. $condition = implode(" OR ", $or);
  449. }
  450. return $condition;
  451. }
  452. function __call($name, array $args) {
  453. $operator = strtoupper($name);
  454. switch ($operator) {
  455. case "AND":
  456. case "OR":
  457. return $this->whereOperator($operator, $args);
  458. }
  459. trigger_error("Call to undefined method NotORM_Result::$name()", E_USER_ERROR);
  460. }
  461. /** Shortcut for where()
  462. * @param string
  463. * @param mixed
  464. * @param mixed ...
  465. * @return NotORM_Result fluent interface
  466. */
  467. function __invoke($where, $parameters = array()) {
  468. $args = func_get_args();
  469. return $this->whereOperator("AND", $args);
  470. }
  471. /** Add order clause, more calls appends to the end
  472. * @param mixed "column1, column2 DESC" or array("column1", "column2 DESC"), empty string to reset previous order
  473. * @param string ...
  474. * @return NotORM_Result fluent interface
  475. */
  476. function order($columns) {
  477. $this->rows = null;
  478. if ($columns != "") {
  479. $columns = (is_array($columns) ? $columns : func_get_args());
  480. foreach ($columns as $column) {
  481. if ($this->union) {
  482. $this->unionOrder[] = $column;
  483. } else {
  484. $this->order[] = $column;
  485. }
  486. }
  487. } elseif ($this->union) {
  488. $this->unionOrder = array();
  489. } else {
  490. $this->order = array();
  491. }
  492. return $this;
  493. }
  494. /** Set limit clause, more calls rewrite old values
  495. * @param int
  496. * @param int
  497. * @return NotORM_Result fluent interface
  498. */
  499. function limit($limit, $offset = null) {
  500. $this->rows = null;
  501. if ($this->union) {
  502. $this->unionLimit = +$limit;
  503. $this->unionOffset = +$offset;
  504. } else {
  505. $this->limit = +$limit;
  506. $this->offset = +$offset;
  507. }
  508. return $this;
  509. }
  510. /** Set group clause, more calls rewrite old values
  511. * @param string
  512. * @param string
  513. * @return NotORM_Result fluent interface
  514. */
  515. function group($columns, $having = "") {
  516. $this->__destruct();
  517. $this->group = $columns;
  518. $this->having = $having;
  519. return $this;
  520. }
  521. /** Set select FOR UPDATE or LOCK IN SHARE MODE
  522. * @param bool
  523. * @return NotORM_Result fluent interface
  524. */
  525. function lock($exclusive = true) {
  526. $this->lock = $exclusive;
  527. return $this;
  528. }
  529. /**
  530. * @param NotORM_Result
  531. * @param bool
  532. * @return NotORM_Result fluent interface
  533. */
  534. function union(NotORM_Result $result, $all = false) {
  535. $this->union[] = " UNION " . ($all ? "ALL " : "") . ($this->notORM->driver == "sqlite" || $this->notORM->driver == "oci" ? $result : "($result)");
  536. $this->parameters = array_merge($this->parameters, $result->parameters);
  537. return $this;
  538. }
  539. /** Execute aggregation function
  540. * @param string
  541. * @return string
  542. */
  543. function aggregation($function) {
  544. $join = $this->createJoins(implode(",", $this->conditions) . ",$function");
  545. $query = "SELECT $function FROM $this->table" . implode($join);
  546. if ($this->where) {
  547. $query .= " WHERE " . implode($this->where);
  548. }
  549. foreach ($this->query($query, $this->parameters)->fetch() as $return) {
  550. return $return;
  551. }
  552. }
  553. /** Count number of rows
  554. * @param string
  555. * @return int
  556. */
  557. function count($column = "") {
  558. if (!$column) {
  559. $this->execute();
  560. return count($this->data);
  561. }
  562. return $this->aggregation("COUNT($column)");
  563. }
  564. /** Return minimum value from a column
  565. * @param string
  566. * @return int
  567. */
  568. function min($column) {
  569. return $this->aggregation("MIN($column)");
  570. }
  571. /** Return maximum value from a column
  572. * @param string
  573. * @return int
  574. */
  575. function max($column) {
  576. return $this->aggregation("MAX($column)");
  577. }
  578. /** Return sum of values in a column
  579. * @param string
  580. * @return int
  581. */
  582. function sum($column) {
  583. return $this->aggregation("SUM($column)");
  584. }
  585. /** Execute the built query
  586. * @return null
  587. */
  588. protected function execute() {
  589. if (!isset($this->rows)) {
  590. $result = false;
  591. $exception = null;
  592. $parameters = array();
  593. foreach (array_merge($this->select, array($this, $this->group, $this->having), $this->order, $this->unionOrder) as $val) {
  594. if (($val instanceof NotORM_Literal || $val instanceof self) && $val->parameters) {
  595. $parameters = array_merge($parameters, $val->parameters);
  596. }
  597. }
  598. try {
  599. $result = $this->query($this->__toString(), $parameters);
  600. } catch (PDOException $exception) {
  601. // handled later
  602. }
  603. if (!$result) {
  604. if (!$this->select && $this->accessed) {
  605. $this->accessed = '';
  606. $this->access = array();
  607. $result = $this->query($this->__toString(), $parameters);
  608. } elseif ($exception) {
  609. throw $exception;
  610. }
  611. }
  612. $this->rows = array();
  613. if ($result) {
  614. $result->setFetchMode(PDO::FETCH_ASSOC);
  615. foreach ($result as $key => $row) {
  616. if (isset($row[$this->primary])) {
  617. $key = $row[$this->primary];
  618. if (!is_string($this->access)) {
  619. $this->access[$this->primary] = true;
  620. }
  621. }
  622. $this->rows[$key] = new $this->notORM->rowClass($row, $this);
  623. }
  624. }
  625. $this->data = $this->rows;
  626. }
  627. }
  628. /** Fetch next row of result
  629. * @param string column name to return or an empty string for the whole row
  630. * @return mixed string or null with $column, NotORM_Row without $column, false if there is no row
  631. */
  632. function fetch($column = '') {
  633. // no $this->select($column) because next calls can access different columns
  634. $this->execute();
  635. $return = current($this->data);
  636. next($this->data);
  637. if ($return && $column != '') {
  638. return $return[$column];
  639. }
  640. return $return;
  641. }
  642. /** Fetch all rows as associative array
  643. * @param string
  644. * @param string column name used for an array value or an empty string for the whole row
  645. * @return array
  646. */
  647. function fetchPairs($key, $value = '') {
  648. $return = array();
  649. $clone = clone $this;
  650. if ($value != "") {
  651. $clone->select = array();
  652. $clone->select("$key, $value"); // MultiResult adds its column
  653. } elseif ($clone->select) {
  654. array_unshift($clone->select, $key);
  655. } else {
  656. $clone->select = array("$key, $this->table.*");
  657. }
  658. foreach ($clone as $row) {
  659. $values = array_values(iterator_to_array($row));
  660. if ($value != "" && $clone instanceof NotORM_MultiResult) {
  661. array_shift($values);
  662. }
  663. $return[(string) $values[0]] = ($value != "" ? $values[(array_key_exists(1, $values) ? 1 : 0)] : $row); // isset($values[1]) - fetchPairs("id", "id")
  664. }
  665. return $return;
  666. }
  667. protected function access($key, $delete = false) {
  668. if ($delete) {
  669. if (is_array($this->access)) {
  670. $this->access[$key] = false;
  671. }
  672. return false;
  673. }
  674. if (!isset($key)) {
  675. $this->access = '';
  676. } elseif (!is_string($this->access)) {
  677. $this->access[$key] = true;
  678. }
  679. if (!$this->select && $this->accessed && (!isset($key) || !isset($this->accessed[$key]))) {
  680. $this->accessed = '';
  681. $this->rows = null;
  682. return true;
  683. }
  684. return false;
  685. }
  686. protected function single() {
  687. }
  688. // Iterator implementation (not IteratorAggregate because $this->data can be changed during iteration)
  689. function rewind() {
  690. $this->execute();
  691. $this->keys = array_keys($this->data);
  692. reset($this->keys);
  693. }
  694. /** @return NotORM_Row */
  695. function current() {
  696. return $this->data[current($this->keys)];
  697. }
  698. /** @return string row ID */
  699. function key() {
  700. return current($this->keys);
  701. }
  702. function next() {
  703. next($this->keys);
  704. }
  705. function valid() {
  706. return current($this->keys) !== false;
  707. }
  708. // ArrayAccess implementation
  709. /** Test if row exists
  710. * @param string row ID or array for where conditions
  711. * @return bool
  712. */
  713. function offsetExists($key) {
  714. $row = $this->offsetGet($key);
  715. return isset($row);
  716. }
  717. /** Get specified row
  718. * @param string row ID or array for where conditions
  719. * @return NotORM_Row or null if there is no such row
  720. */
  721. function offsetGet($key) {
  722. if ($this->single && !isset($this->data)) {
  723. $clone = clone $this;
  724. if (is_array($key)) {
  725. $clone->where($key)->limit(1);
  726. } else {
  727. $clone->where($this->primary, $key);
  728. }
  729. $return = $clone->fetch();
  730. if ($return) {
  731. return $return;
  732. }
  733. } else {
  734. $this->execute();
  735. if (is_array($key)) {
  736. foreach ($this->data as $row) {
  737. foreach ($key as $k => $v) {
  738. if ((isset($v) && $row[$k] !== null ? $row[$k] != $v : $row[$k] !== $v)) {
  739. continue 2;
  740. }
  741. }
  742. return $row;
  743. }
  744. } elseif (isset($this->data[$key])) {
  745. return $this->data[$key];
  746. }
  747. }
  748. }
  749. /** Mimic row
  750. * @param string row ID
  751. * @param NotORM_Row
  752. * @return null
  753. */
  754. function offsetSet($key, $value) {
  755. $this->execute();
  756. $this->data[$key] = $value;
  757. }
  758. /** Remove row from result set
  759. * @param string row ID
  760. * @return null
  761. */
  762. function offsetUnset($key) {
  763. $this->execute();
  764. unset($this->data[$key]);
  765. }
  766. // JsonSerializable implementation
  767. function jsonSerialize() {
  768. $this->execute();
  769. if ($this->notORM->jsonAsArray) {
  770. return array_values($this->data);
  771. } else {
  772. return $this->data;
  773. }
  774. }
  775. }