mapper.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. <?php
  2. /*
  3. Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
  4. This file is part of the Fat-Free Framework (http://fatfreeframework.com).
  5. This is free software: you can redistribute it and/or modify it under the
  6. terms of the GNU General Public License as published by the Free Software
  7. Foundation, either version 3 of the License, or later.
  8. Fat-Free Framework is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. General Public License for more details.
  12. You should have received a copy of the GNU General Public License along
  13. with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. namespace DB\SQL;
  16. //! SQL data mapper
  17. class Mapper extends \DB\Cursor {
  18. //@{ Error messages
  19. const
  20. E_PKey='Table %s does not have a primary key';
  21. //@}
  22. protected
  23. //! PDO wrapper
  24. $db,
  25. //! Database engine
  26. $engine,
  27. //! SQL table
  28. $source,
  29. //! SQL table (quoted)
  30. $table,
  31. //! Alias for SQL table
  32. $as,
  33. //! Last insert ID
  34. $_id,
  35. //! Defined fields
  36. $fields,
  37. //! Adhoc fields
  38. $adhoc=[],
  39. //! Dynamic properties
  40. $props=[];
  41. /**
  42. * Return database type
  43. * @return string
  44. **/
  45. function dbtype() {
  46. return 'SQL';
  47. }
  48. /**
  49. * Return mapped table
  50. * @return string
  51. **/
  52. function table() {
  53. return $this->source;
  54. }
  55. /**
  56. * Return TRUE if any/specified field value has changed
  57. * @return bool
  58. * @param $key string
  59. **/
  60. function changed($key=NULL) {
  61. if (isset($key))
  62. return $this->fields[$key]['changed'];
  63. foreach($this->fields as $key=>$field)
  64. if ($field['changed'])
  65. return TRUE;
  66. return FALSE;
  67. }
  68. /**
  69. * Return TRUE if field is defined
  70. * @return bool
  71. * @param $key string
  72. **/
  73. function exists($key) {
  74. return array_key_exists($key,$this->fields+$this->adhoc);
  75. }
  76. /**
  77. * Assign value to field
  78. * @return scalar
  79. * @param $key string
  80. * @param $val scalar
  81. **/
  82. function set($key,$val) {
  83. if (array_key_exists($key,$this->fields)) {
  84. $val=is_null($val) && $this->fields[$key]['nullable']?
  85. NULL:$this->db->value($this->fields[$key]['pdo_type'],$val);
  86. if ($this->fields[$key]['initial']!==$val ||
  87. $this->fields[$key]['default']!==$val && is_null($val))
  88. $this->fields[$key]['changed']=TRUE;
  89. return $this->fields[$key]['value']=$val;
  90. }
  91. // Adjust result on existing expressions
  92. if (isset($this->adhoc[$key]))
  93. $this->adhoc[$key]['value']=$val;
  94. elseif (is_string($val))
  95. // Parenthesize expression in case it's a subquery
  96. $this->adhoc[$key]=['expr'=>'('.$val.')','value'=>NULL];
  97. else
  98. $this->props[$key]=$val;
  99. return $val;
  100. }
  101. /**
  102. * Retrieve value of field
  103. * @return scalar
  104. * @param $key string
  105. **/
  106. function &get($key) {
  107. if ($key=='_id')
  108. return $this->_id;
  109. elseif (array_key_exists($key,$this->fields))
  110. return $this->fields[$key]['value'];
  111. elseif (array_key_exists($key,$this->adhoc))
  112. return $this->adhoc[$key]['value'];
  113. elseif (array_key_exists($key,$this->props))
  114. return $this->props[$key];
  115. user_error(sprintf(self::E_Field,$key),E_USER_ERROR);
  116. }
  117. /**
  118. * Clear value of field
  119. * @return NULL
  120. * @param $key string
  121. **/
  122. function clear($key) {
  123. if (array_key_exists($key,$this->adhoc))
  124. unset($this->adhoc[$key]);
  125. else
  126. unset($this->props[$key]);
  127. }
  128. /**
  129. * Invoke dynamic method
  130. * @return mixed
  131. * @param $func string
  132. * @param $args array
  133. **/
  134. function __call($func,$args) {
  135. return call_user_func_array(
  136. (array_key_exists($func,$this->props)?
  137. $this->props[$func]:
  138. $this->$func),$args
  139. );
  140. }
  141. /**
  142. * Convert array to mapper object
  143. * @return static
  144. * @param $row array
  145. **/
  146. function factory($row) {
  147. $mapper=clone($this);
  148. $mapper->reset();
  149. foreach ($row as $key=>$val) {
  150. if (array_key_exists($key,$this->fields))
  151. $var='fields';
  152. elseif (array_key_exists($key,$this->adhoc))
  153. $var='adhoc';
  154. else
  155. continue;
  156. $mapper->{$var}[$key]['value']=$val;
  157. $mapper->{$var}[$key]['initial']=$val;
  158. if ($var=='fields' && $mapper->{$var}[$key]['pkey'])
  159. $mapper->{$var}[$key]['previous']=$val;
  160. }
  161. $mapper->query=[clone($mapper)];
  162. if (isset($mapper->trigger['load']))
  163. \Base::instance()->call($mapper->trigger['load'],$mapper);
  164. return $mapper;
  165. }
  166. /**
  167. * Return fields of mapper object as an associative array
  168. * @return array
  169. * @param $obj object
  170. **/
  171. function cast($obj=NULL) {
  172. if (!$obj)
  173. $obj=$this;
  174. return array_map(
  175. function($row) {
  176. return $row['value'];
  177. },
  178. $obj->fields+$obj->adhoc
  179. );
  180. }
  181. /**
  182. * Build query string and arguments
  183. * @return array
  184. * @param $fields string
  185. * @param $filter string|array
  186. * @param $options array
  187. **/
  188. function stringify($fields,$filter=NULL,array $options=NULL) {
  189. if (!$options)
  190. $options=[];
  191. $options+=[
  192. 'group'=>NULL,
  193. 'order'=>NULL,
  194. 'limit'=>0,
  195. 'offset'=>0,
  196. 'comment'=>NULL
  197. ];
  198. $db=$this->db;
  199. $sql='SELECT '.$fields.' FROM '.$this->table;
  200. if (isset($this->as))
  201. $sql.=' AS '.$this->db->quotekey($this->as);
  202. $args=[];
  203. if (is_array($filter)) {
  204. $args=isset($filter[1]) && is_array($filter[1])?
  205. $filter[1]:
  206. array_slice($filter,1,NULL,TRUE);
  207. $args=is_array($args)?$args:[1=>$args];
  208. list($filter)=$filter;
  209. }
  210. if ($filter)
  211. $sql.=' WHERE '.$filter;
  212. if ($options['group']) {
  213. $sql.=' GROUP BY '.implode(',',array_map(
  214. function($str) use($db) {
  215. return preg_replace_callback(
  216. '/\b(\w+[._\-\w]*)\h*(HAVING.+|$)/i',
  217. function($parts) use($db) {
  218. return $db->quotekey($parts[1]).
  219. (isset($parts[2])?(' '.$parts[2]):'');
  220. },
  221. $str
  222. );
  223. },
  224. explode(',',$options['group'])));
  225. }
  226. if ($options['order']) {
  227. $char=substr($db->quotekey(''),0,1);// quoting char
  228. $order=' ORDER BY '.(is_bool(strpos($options['order'],$char))?
  229. implode(',',array_map(function($str) use($db) {
  230. return preg_match('/^\h*(\w+[._\-\w]*)'.
  231. '(?:\h+((?:ASC|DESC)[\w\h]*))?\h*$/i',
  232. $str,$parts)?
  233. ($db->quotekey($parts[1]).
  234. (isset($parts[2])?(' '.$parts[2]):'')):$str;
  235. },explode(',',$options['order']))):
  236. $options['order']);
  237. }
  238. // SQL Server fixes
  239. if (preg_match('/mssql|sqlsrv|odbc/', $this->engine) &&
  240. ($options['limit'] || $options['offset'])) {
  241. // order by pkey when no ordering option was given
  242. if (!$options['order'])
  243. foreach ($this->fields as $key=>$field)
  244. if ($field['pkey']) {
  245. $order=' ORDER BY '.$db->quotekey($key);
  246. break;
  247. }
  248. $ofs=$options['offset']?(int)$options['offset']:0;
  249. $lmt=$options['limit']?(int)$options['limit']:0;
  250. if (strncmp($db->version(),'11',2)>=0) {
  251. // SQL Server >= 2012
  252. $sql.=$order.' OFFSET '.$ofs.' ROWS';
  253. if ($lmt)
  254. $sql.=' FETCH NEXT '.$lmt.' ROWS ONLY';
  255. }
  256. else {
  257. // SQL Server 2008
  258. $sql=preg_replace('/SELECT/',
  259. 'SELECT '.
  260. ($lmt>0?'TOP '.($ofs+$lmt):'').' ROW_NUMBER() '.
  261. 'OVER ('.$order.') AS rnum,',$sql.$order,1);
  262. $sql='SELECT * FROM ('.$sql.') x WHERE rnum > '.($ofs);
  263. }
  264. }
  265. else {
  266. if (isset($order))
  267. $sql.=$order;
  268. if ($options['limit'])
  269. $sql.=' LIMIT '.(int)$options['limit'];
  270. if ($options['offset'])
  271. $sql.=' OFFSET '.(int)$options['offset'];
  272. }
  273. if ($options['comment'])
  274. $sql.="\n".' /* '.$options['comment'].' */';
  275. return [$sql,$args];
  276. }
  277. /**
  278. * Build query string and execute
  279. * @return static[]
  280. * @param $fields string
  281. * @param $filter string|array
  282. * @param $options array
  283. * @param $ttl int|array
  284. **/
  285. function select($fields,$filter=NULL,array $options=NULL,$ttl=0) {
  286. list($sql,$args)=$this->stringify($fields,$filter,$options);
  287. $result=$this->db->exec($sql,$args,$ttl);
  288. $out=[];
  289. foreach ($result as &$row) {
  290. foreach ($row as $field=>&$val) {
  291. if (array_key_exists($field,$this->fields)) {
  292. if (!is_null($val) || !$this->fields[$field]['nullable'])
  293. $val=$this->db->value(
  294. $this->fields[$field]['pdo_type'],$val);
  295. }
  296. unset($val);
  297. }
  298. $out[]=$this->factory($row);
  299. unset($row);
  300. }
  301. return $out;
  302. }
  303. /**
  304. * Return records that match criteria
  305. * @return static[]
  306. * @param $filter string|array
  307. * @param $options array
  308. * @param $ttl int|array
  309. **/
  310. function find($filter=NULL,array $options=NULL,$ttl=0) {
  311. if (!$options)
  312. $options=[];
  313. $options+=[
  314. 'group'=>NULL,
  315. 'order'=>NULL,
  316. 'limit'=>0,
  317. 'offset'=>0
  318. ];
  319. $adhoc='';
  320. foreach ($this->adhoc as $key=>$field)
  321. $adhoc.=','.$field['expr'].' AS '.$this->db->quotekey($key);
  322. return $this->select(
  323. ($options['group'] && !preg_match('/mysql|sqlite/',$this->engine)?
  324. $options['group']:
  325. implode(',',array_map([$this->db,'quotekey'],
  326. array_keys($this->fields)))).$adhoc,$filter,$options,$ttl);
  327. }
  328. /**
  329. * Count records that match criteria
  330. * @return int
  331. * @param $filter string|array
  332. * @param $options array
  333. * @param $ttl int|array
  334. **/
  335. function count($filter=NULL,array $options=NULL,$ttl=0) {
  336. $adhoc=[];
  337. // with grouping involved, we need to wrap the actualy query and count the results
  338. if ($subquery_mode=($options && !empty($options['group']))) {
  339. $group_string=preg_replace('/HAVING.+$/i','',$options['group']);
  340. $group_fields=array_flip(array_map('trim',explode(',',$group_string)));
  341. foreach ($this->adhoc as $key=>$field)
  342. // add adhoc fields that are used for grouping
  343. if (isset($group_fields[$key]))
  344. $adhoc[]=$field['expr'].' AS '.$this->db->quotekey($key);
  345. $fields=implode(',',$adhoc);
  346. if (empty($fields))
  347. // Select at least one field, ideally the grouping fields
  348. // or sqlsrv fails
  349. $fields=$group_string;
  350. if (preg_match('/mssql|dblib|sqlsrv/',$this->engine))
  351. $fields='TOP 100 PERCENT '.$fields;
  352. } else {
  353. // for simple count just add a new adhoc counter
  354. $fields='COUNT(*) AS '.$this->db->quotekey('_rows');
  355. }
  356. list($sql,$args)=$this->stringify($fields,$filter,$options);
  357. if ($subquery_mode)
  358. $sql='SELECT COUNT(*) AS '.$this->db->quotekey('_rows').' '.
  359. 'FROM ('.$sql.') AS '.$this->db->quotekey('_temp');
  360. $result=$this->db->exec($sql,$args,$ttl);
  361. unset($this->adhoc['_rows']);
  362. return (int)$result[0]['_rows'];
  363. }
  364. /**
  365. * Return record at specified offset using same criteria as
  366. * previous load() call and make it active
  367. * @return static
  368. * @param $ofs int
  369. **/
  370. function skip($ofs=1) {
  371. $out=parent::skip($ofs);
  372. $dry=$this->dry();
  373. foreach ($this->fields as $key=>&$field) {
  374. $field['value']=$dry?NULL:$out->fields[$key]['value'];
  375. $field['initial']=$field['value'];
  376. $field['changed']=FALSE;
  377. if ($field['pkey'])
  378. $field['previous']=$dry?NULL:$out->fields[$key]['value'];
  379. unset($field);
  380. }
  381. foreach ($this->adhoc as $key=>&$field) {
  382. $field['value']=$dry?NULL:$out->adhoc[$key]['value'];
  383. unset($field);
  384. }
  385. if (!$dry && isset($this->trigger['load']))
  386. \Base::instance()->call($this->trigger['load'],$this);
  387. return $out;
  388. }
  389. /**
  390. * Insert new record
  391. * @return static
  392. **/
  393. function insert() {
  394. $args=[];
  395. $actr=0;
  396. $nctr=0;
  397. $fields='';
  398. $values='';
  399. $filter='';
  400. $pkeys=[];
  401. $nkeys=[];
  402. $ckeys=[];
  403. $inc=NULL;
  404. foreach ($this->fields as $key=>$field)
  405. if ($field['pkey'])
  406. $pkeys[$key]=$field['previous'];
  407. if (isset($this->trigger['beforeinsert']) &&
  408. \Base::instance()->call($this->trigger['beforeinsert'],
  409. [$this,$pkeys])===FALSE)
  410. return $this;
  411. if ($this->valid())
  412. // duplicate record
  413. foreach ($this->fields as $key=>&$field) {
  414. $field['changed']=true;
  415. if ($field['pkey'] && !$inc && $field['pdo_type']==\PDO::PARAM_INT
  416. && !$field['nullable'])
  417. $inc=$key;
  418. unset($field);
  419. }
  420. foreach ($this->fields as $key=>&$field) {
  421. if ($field['pkey']) {
  422. $field['previous']=$field['value'];
  423. if (!$inc && $field['pdo_type']==\PDO::PARAM_INT &&
  424. empty($field['value']) && !$field['nullable'] &&
  425. is_null($field['default']))
  426. $inc=$key;
  427. $filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?';
  428. $nkeys[$nctr+1]=[$field['value'],$field['pdo_type']];
  429. $nctr++;
  430. }
  431. if ($field['changed'] && $key!=$inc) {
  432. $fields.=($actr?',':'').$this->db->quotekey($key);
  433. $values.=($actr?',':'').'?';
  434. $args[$actr+1]=[$field['value'],$field['pdo_type']];
  435. $actr++;
  436. $ckeys[]=$key;
  437. }
  438. unset($field);
  439. }
  440. if ($fields) {
  441. $add=$aik='';
  442. if ($this->engine=='pgsql' && !empty($pkeys)) {
  443. $names=array_keys($pkeys);
  444. $aik=end($names);
  445. $add=' RETURNING '.$this->db->quotekey($aik);
  446. }
  447. $lID=$this->db->exec(
  448. (preg_match('/mssql|dblib|sqlsrv/',$this->engine) &&
  449. array_intersect(array_keys($pkeys),$ckeys)?
  450. 'SET IDENTITY_INSERT '.$this->table.' ON;':'').
  451. 'INSERT INTO '.$this->table.' ('.$fields.') '.
  452. 'VALUES ('.$values.')'.$add,$args
  453. );
  454. if ($this->engine=='pgsql' && $lID && $aik)
  455. $this->_id=$lID[0][$aik];
  456. elseif ($this->engine!='oci')
  457. $this->_id=$this->db->lastinsertid();
  458. // Reload to obtain default and auto-increment field values
  459. if ($reload=(($inc && $this->_id) || $filter))
  460. $this->load($inc?
  461. [$inc.'=?',$this->db->value(
  462. $this->fields[$inc]['pdo_type'],$this->_id)]:
  463. [$filter,$nkeys]);
  464. if (isset($this->trigger['afterinsert']))
  465. \Base::instance()->call($this->trigger['afterinsert'],
  466. [$this,$pkeys]);
  467. // reset changed flag after calling afterinsert
  468. if (!$reload)
  469. foreach ($this->fields as $key=>&$field) {
  470. $field['changed']=FALSE;
  471. $field['initial']=$field['value'];
  472. unset($field);
  473. }
  474. }
  475. return $this;
  476. }
  477. /**
  478. * Update current record
  479. * @return static
  480. **/
  481. function update() {
  482. $args=[];
  483. $ctr=0;
  484. $pairs='';
  485. $pkeys=[];
  486. foreach ($this->fields as $key=>$field)
  487. if ($field['pkey'])
  488. $pkeys[$key]=$field['previous'];
  489. if (isset($this->trigger['beforeupdate']) &&
  490. \Base::instance()->call($this->trigger['beforeupdate'],
  491. [$this,$pkeys])===FALSE)
  492. return $this;
  493. foreach ($this->fields as $key=>$field)
  494. if ($field['changed']) {
  495. $pairs.=($pairs?',':'').$this->db->quotekey($key).'=?';
  496. $args[++$ctr]=[$field['value'],$field['pdo_type']];
  497. }
  498. if ($pairs) {
  499. $filter='';
  500. foreach ($this->fields as $key=>$field)
  501. if ($field['pkey']) {
  502. $filter.=($filter?' AND ':' WHERE ').
  503. $this->db->quotekey($key).'=?';
  504. $args[++$ctr]=[$field['previous'],$field['pdo_type']];
  505. }
  506. if (!$filter)
  507. user_error(sprintf(self::E_PKey,$this->source),E_USER_ERROR);
  508. $sql='UPDATE '.$this->table.' SET '.$pairs.$filter;
  509. $this->db->exec($sql,$args);
  510. }
  511. if (isset($this->trigger['afterupdate']))
  512. \Base::instance()->call($this->trigger['afterupdate'],
  513. [$this,$pkeys]);
  514. // reset changed flag after calling afterupdate
  515. foreach ($this->fields as $key=>&$field) {
  516. $field['changed']=FALSE;
  517. $field['initial']=$field['value'];
  518. unset($field);
  519. }
  520. return $this;
  521. }
  522. /**
  523. * batch-update multiple records at once
  524. * @param string|array $filter
  525. * @return int
  526. */
  527. function updateAll($filter=NULL) {
  528. $args=[];
  529. $ctr=$out=0;
  530. $pairs='';
  531. foreach ($this->fields as $key=>$field)
  532. if ($field['changed']) {
  533. $pairs.=($pairs?',':'').$this->db->quotekey($key).'=?';
  534. $args[++$ctr]=[$field['value'],$field['pdo_type']];
  535. }
  536. if ($filter)
  537. if (is_array($filter)) {
  538. $cond=array_shift($filter);
  539. $args=array_merge($args,$filter);
  540. $filter=' WHERE '.$cond;
  541. } else
  542. $filter=' WHERE '.$filter;
  543. if ($pairs) {
  544. $sql='UPDATE '.$this->table.' SET '.$pairs.$filter;
  545. $out = $this->db->exec($sql,$args);
  546. }
  547. // reset changed flag after calling afterupdate
  548. foreach ($this->fields as $key=>&$field) {
  549. $field['changed']=FALSE;
  550. $field['initial']=$field['value'];
  551. unset($field);
  552. }
  553. return $out;
  554. }
  555. /**
  556. * Delete current record
  557. * @return int
  558. * @param $quick bool
  559. * @param $filter string|array
  560. **/
  561. function erase($filter=NULL,$quick=TRUE) {
  562. if (isset($filter)) {
  563. if (!$quick) {
  564. $out=0;
  565. foreach ($this->find($filter) as $mapper)
  566. $out+=$mapper->erase();
  567. return $out;
  568. }
  569. $args=[];
  570. if (is_array($filter)) {
  571. $args=isset($filter[1]) && is_array($filter[1])?
  572. $filter[1]:
  573. array_slice($filter,1,NULL,TRUE);
  574. $args=is_array($args)?$args:[1=>$args];
  575. list($filter)=$filter;
  576. }
  577. return $this->db->
  578. exec('DELETE FROM '.$this->table.
  579. ($filter?' WHERE '.$filter:'').';',$args);
  580. }
  581. $args=[];
  582. $ctr=0;
  583. $filter='';
  584. $pkeys=[];
  585. foreach ($this->fields as $key=>&$field) {
  586. if ($field['pkey']) {
  587. $filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?';
  588. $args[$ctr+1]=[$field['previous'],$field['pdo_type']];
  589. $pkeys[$key]=$field['previous'];
  590. $ctr++;
  591. }
  592. $field['value']=NULL;
  593. $field['changed']=(bool)$field['default'];
  594. if ($field['pkey'])
  595. $field['previous']=NULL;
  596. unset($field);
  597. }
  598. if (!$filter)
  599. user_error(sprintf(self::E_PKey,$this->source),E_USER_ERROR);
  600. foreach ($this->adhoc as &$field) {
  601. $field['value']=NULL;
  602. unset($field);
  603. }
  604. parent::erase();
  605. if (isset($this->trigger['beforeerase']) &&
  606. \Base::instance()->call($this->trigger['beforeerase'],
  607. [$this,$pkeys])===FALSE)
  608. return 0;
  609. $out=$this->db->
  610. exec('DELETE FROM '.$this->table.' WHERE '.$filter.';',$args);
  611. if (isset($this->trigger['aftererase']))
  612. \Base::instance()->call($this->trigger['aftererase'],
  613. [$this,$pkeys]);
  614. return $out;
  615. }
  616. /**
  617. * Reset cursor
  618. * @return NULL
  619. **/
  620. function reset() {
  621. foreach ($this->fields as &$field) {
  622. $field['value']=NULL;
  623. $field['initial']=NULL;
  624. $field['changed']=FALSE;
  625. if ($field['pkey'])
  626. $field['previous']=NULL;
  627. unset($field);
  628. }
  629. foreach ($this->adhoc as &$field) {
  630. $field['value']=NULL;
  631. unset($field);
  632. }
  633. parent::reset();
  634. }
  635. /**
  636. * Hydrate mapper object using hive array variable
  637. * @return NULL
  638. * @param $var array|string
  639. * @param $func callback
  640. **/
  641. function copyfrom($var,$func=NULL) {
  642. if (is_string($var))
  643. $var=\Base::instance()->$var;
  644. if ($func)
  645. $var=call_user_func($func,$var);
  646. foreach ($var as $key=>$val)
  647. if (in_array($key,array_keys($this->fields)))
  648. $this->set($key,$val);
  649. }
  650. /**
  651. * Populate hive array variable with mapper fields
  652. * @return NULL
  653. * @param $key string
  654. **/
  655. function copyto($key) {
  656. $var=&\Base::instance()->ref($key);
  657. foreach ($this->fields+$this->adhoc as $key=>$field)
  658. $var[$key]=$field['value'];
  659. }
  660. /**
  661. * Return schema and, if the first argument is provided, update it
  662. * @return array
  663. * @param $fields NULL|array
  664. **/
  665. function schema($fields=null) {
  666. if ($fields)
  667. $this->fields = $fields;
  668. return $this->fields;
  669. }
  670. /**
  671. * Return field names
  672. * @return array
  673. * @param $adhoc bool
  674. **/
  675. function fields($adhoc=TRUE) {
  676. return array_keys($this->fields+($adhoc?$this->adhoc:[]));
  677. }
  678. /**
  679. * Return TRUE if field is not nullable
  680. * @return bool
  681. * @param $field string
  682. **/
  683. function required($field) {
  684. return isset($this->fields[$field]) &&
  685. !$this->fields[$field]['nullable'];
  686. }
  687. /**
  688. * Retrieve external iterator for fields
  689. * @return object
  690. **/
  691. function getiterator() {
  692. return new \ArrayIterator($this->cast());
  693. }
  694. /**
  695. * Assign alias for table
  696. * @param $alias string
  697. **/
  698. function alias($alias) {
  699. $this->as=$alias;
  700. return $this;
  701. }
  702. /**
  703. * Instantiate class
  704. * @param $db \DB\SQL
  705. * @param $table string
  706. * @param $fields array|string
  707. * @param $ttl int|array
  708. **/
  709. function __construct(\DB\SQL $db,$table,$fields=NULL,$ttl=60) {
  710. $this->db=$db;
  711. $this->engine=$db->driver();
  712. if ($this->engine=='oci')
  713. $table=strtoupper($table);
  714. $this->source=$table;
  715. $this->table=$this->db->quotekey($table);
  716. $this->fields=$db->schema($table,$fields,$ttl);
  717. $this->reset();
  718. }
  719. }