AbstractRow.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. <?php
  2. /**
  3. *
  4. * Cube Framework $Id$ nA+FcfJEqlljKnQskh69zN7RJom7AO+CWjAOWAIoYA4=
  5. *
  6. * @link http://codecu.be/framework
  7. * @copyright Copyright (c) 2017 CodeCube SRL
  8. * @license http://codecu.be/framework/license Commercial License
  9. *
  10. * @version 1.9 [rev.1.9.02]
  11. */
  12. namespace Cube\Db\Table\Row;
  13. use Cube\Db\Table\AbstractTable,
  14. Cube\Db\Select,
  15. Cube\Db\Expr,
  16. Cube\Controller\Front,
  17. Cube\Translate\Adapter\AbstractAdapter as TranslateAdapter,
  18. Cube\Translate;
  19. class AbstractRow implements \Countable, \ArrayAccess, \IteratorAggregate
  20. {
  21. const FQN_SEP = '\\';
  22. /**
  23. *
  24. * table row data (column_name => value)
  25. *
  26. *
  27. * @var array
  28. */
  29. protected $_data = array();
  30. /**
  31. *
  32. * table class the row belongs to
  33. *
  34. * @var \Cube\Db\Table\AbstractTable
  35. */
  36. protected $_table = null;
  37. /**
  38. *
  39. * primary key(s)
  40. *
  41. * @var array
  42. */
  43. protected $_primary;
  44. /**
  45. *
  46. * true if we do not have a deserialized object, false otherwise
  47. *
  48. * @var bool
  49. */
  50. protected $_connected = true;
  51. /**
  52. *
  53. * serializable fields
  54. *
  55. * @var array
  56. */
  57. protected $_serializable = array();
  58. /**
  59. *
  60. * translate adapter
  61. *
  62. * @var \Cube\Translate\Adapter\AbstractAdapter
  63. */
  64. protected $_translate;
  65. /**
  66. *
  67. * class constructor
  68. *
  69. * @param array $data
  70. */
  71. public function __construct($data = array())
  72. {
  73. if (isset($data['table']) && $data['table'] instanceof AbstractTable) {
  74. $this->_table = $data['table'];
  75. }
  76. else {
  77. throw new \InvalidArgumentException("The 'table' key must be set when creating a Row object");
  78. }
  79. if (isset($data['data'])) {
  80. if (!is_array($data['data'])) {
  81. throw new \InvalidArgumentException(
  82. sprintf('The "data" key must be an array, %s given.', gettype($data['data'])));
  83. }
  84. $this->setData($data['data']);
  85. }
  86. if (!$this->_primary) {
  87. $this->_primary = $this->_table->info('primary');
  88. }
  89. }
  90. /**
  91. *
  92. * set row data
  93. *
  94. * @param array $data
  95. *
  96. * @return $this
  97. */
  98. public function setData(array $data)
  99. {
  100. $this->_data = array();
  101. foreach ($data as $name => $value) {
  102. $this->addData($name, $value);
  103. }
  104. return $this;
  105. }
  106. /**
  107. *
  108. * set (update) the value of a column in the row.
  109. * also accepts serialized fields, if the column names are defined in the $_serialized array
  110. *
  111. * @param string $key
  112. * @param mixed $value
  113. *
  114. * @return $this
  115. */
  116. public function addData($key, $value)
  117. {
  118. $array = null;
  119. if (in_array($key, $this->_serializable)) {
  120. $array = @unserialize($value);
  121. }
  122. if (is_array($array)) {
  123. $this->_data[$key] = $array;
  124. foreach ($array as $k => $v) {
  125. if (is_string($k)) {
  126. $this->_data[$k] = $v;
  127. }
  128. }
  129. }
  130. else {
  131. $this->_data[$key] = $value;
  132. }
  133. return $this;
  134. }
  135. /**
  136. *
  137. * get row data
  138. *
  139. * @1.9 the $null value is only returned if the key doesn't exist
  140. *
  141. * @param string $name key name
  142. * @param mixed $null null value
  143. *
  144. * @return array|string|null
  145. */
  146. public function getData($name = null, $null = null)
  147. {
  148. if ($name !== null) {
  149. if (isset($this->_data[$name])) {
  150. return $this->_data[$name];
  151. }
  152. return $null;
  153. }
  154. return $this->_data;
  155. }
  156. /**
  157. *
  158. * set translate adapter
  159. *
  160. * @param \Cube\Translate\Adapter\AbstractAdapter $translate
  161. *
  162. * @return $this
  163. */
  164. public function setTranslate(TranslateAdapter $translate)
  165. {
  166. $this->_translate = $translate;
  167. return $this;
  168. }
  169. /**
  170. *
  171. * get translate adapter
  172. *
  173. * @return \Cube\Translate\Adapter\AbstractAdapter
  174. */
  175. public function getTranslate()
  176. {
  177. if (!$this->_translate instanceof TranslateAdapter) {
  178. $translate = Front::getInstance()->getBootstrap()->getResource('translate');
  179. if ($translate instanceof Translate) {
  180. $this->setTranslate(
  181. $translate->getAdapter());
  182. }
  183. }
  184. return $this->_translate;
  185. }
  186. /**
  187. *
  188. * save the updated row in the table
  189. * also update the data array from the object with the most recent values
  190. *
  191. * @param array $data partial data to be saved
  192. * the complete row is saved if this parameter is null
  193. *
  194. * @throws \RuntimeException
  195. * @return $this
  196. */
  197. public function save(array $data = null)
  198. {
  199. if ($data === null) {
  200. $data = $this->_data;
  201. }
  202. else {
  203. foreach ($data as $key => $value) {
  204. $this->_data[$key] = $value;
  205. }
  206. }
  207. if ($this->_connected === true) {
  208. $this->_table->update($data, $this->_getWhereQuery());
  209. }
  210. else {
  211. throw new \RuntimeException("Cannot save a row in unconnected state.");
  212. }
  213. return $this;
  214. }
  215. /**
  216. *
  217. * get table
  218. *
  219. * @return \Cube\Db\Table\AbstractTable
  220. */
  221. public function getTable()
  222. {
  223. return $this->_table;
  224. }
  225. /**
  226. *
  227. * find a dependent rowset
  228. *
  229. * @param string|\Cube\Db\Table\AbstractTable $dependentTable
  230. * @param string $ruleKey
  231. * @param \Cube\Db\Select $select
  232. *
  233. * @return \Cube\Db\Table\Rowset\AbstractRowset
  234. * @throws \RuntimeException
  235. */
  236. public function findDependentRowset($dependentTable, $ruleKey = null, Select $select = null)
  237. {
  238. if (is_string($dependentTable)) {
  239. $dependentTable = $this->_getTableFromString($dependentTable);
  240. }
  241. if (!$dependentTable instanceof AbstractTable) {
  242. throw new \RuntimeException(
  243. sprintf('The table must be an instance of \Cube\Db\Table\AbstractTable, %s given',
  244. gettype($dependentTable)));
  245. }
  246. if ($select === null) {
  247. $select = $dependentTable->select();
  248. }
  249. else {
  250. $select->reset(Select::COLUMNS);
  251. $select->reset(Select::FROM);
  252. $select->from($dependentTable->getName(), '*');
  253. }
  254. $map = $this->_prepareReference($dependentTable, $this->_table, $ruleKey);
  255. for ($i = 0; $i < count($map[AbstractTable::COLUMNS]); $i++) {
  256. $value = $this->_data[$map[AbstractTable::REF_COLUMNS][$i]];
  257. $column = $dependentTable->getAdapter()->quoteIdentifier(
  258. $map[AbstractTable::COLUMNS][$i]);
  259. $select->where("$column = ?", $value);
  260. }
  261. return $dependentTable->fetchAll($select);
  262. }
  263. /**
  264. *
  265. * count the rows in a dependent rowset
  266. *
  267. * @param string|\Cube\Db\Table\AbstractTable $dependentTable
  268. * @param string $ruleKey
  269. * @param \Cube\Db\Select $select
  270. *
  271. * @return int
  272. * @throws \RuntimeException
  273. */
  274. public function countDependentRowset($dependentTable, $ruleKey = null, Select $select = null)
  275. {
  276. if (is_string($dependentTable)) {
  277. $dependentTable = $this->_getTableFromString($dependentTable);
  278. }
  279. if (!$dependentTable instanceof AbstractTable) {
  280. throw new \RuntimeException(
  281. sprintf('The table must be an instance of \Cube\Db\Table\AbstractTable, %s given',
  282. gettype($dependentTable)));
  283. }
  284. if ($select === null) {
  285. $select = $dependentTable->select();
  286. }
  287. else {
  288. $select->reset(Select::FROM);
  289. $select->from($dependentTable->getName(), '*');
  290. }
  291. $select->reset(Select::COLUMNS)
  292. ->reset(Select::ORDER);
  293. $select->columns(array('nb_rows' => new Expr('count(*)')));
  294. $map = $this->_prepareReference($dependentTable, $this->_table, $ruleKey);
  295. for ($i = 0; $i < count($map[AbstractTable::COLUMNS]); $i++) {
  296. $value = $this->_data[$map[AbstractTable::REF_COLUMNS][$i]];
  297. $column = $dependentTable->getAdapter()->quoteIdentifier(
  298. $map[AbstractTable::COLUMNS][$i]);
  299. $select->where("$column = ?", $value);
  300. }
  301. $stmt = $select->query();
  302. return (integer)$stmt->fetchColumn('nb_rows');
  303. }
  304. /**
  305. *
  306. * find the matching parent row
  307. *
  308. * @param string|\Cube\Db\Table\AbstractTable $parentTable
  309. * @param string $ruleKey
  310. * @param \Cube\Db\Select $select
  311. *
  312. * @return \Cube\Db\Table\Row\AbstractRow
  313. * @throws \RuntimeException
  314. */
  315. public function findParentRow($parentTable, $ruleKey = null, Select $select = null)
  316. {
  317. if (is_string($parentTable)) {
  318. $parentTable = $this->_getTableFromString($parentTable);
  319. }
  320. if (!$parentTable instanceof AbstractTable) {
  321. throw new \RuntimeException(
  322. sprintf('The table must be an instance of \Cube\Db\Table\AbstractTable, %s given',
  323. gettype($parentTable)));
  324. }
  325. if ($select === null) {
  326. $select = $parentTable->select();
  327. }
  328. else {
  329. $select->reset(Select::COLUMNS);
  330. $select->reset(Select::FROM);
  331. $select->from($parentTable->getName(), '*');
  332. }
  333. $map = $this->_prepareReference($this->_table, $parentTable, $ruleKey);
  334. for ($i = 0; $i < count($map[AbstractTable::COLUMNS]); $i++) {
  335. $value = $this->_data[$map[AbstractTable::COLUMNS][$i]];
  336. $column = $parentTable->getAdapter()->quoteIdentifier(
  337. $map[AbstractTable::REF_COLUMNS][$i]);
  338. if ($value) {
  339. $select->where("$column = ?", $value);
  340. }
  341. else {
  342. $select->where("$column is null");
  343. }
  344. }
  345. return $parentTable->fetchRow($select);
  346. }
  347. /**
  348. *
  349. * delete the row from the table
  350. *
  351. * @return int
  352. */
  353. public function delete()
  354. {
  355. $result = $this->_table->delete($this->_getWhereQuery());
  356. $this->_data = array();
  357. return $result;
  358. }
  359. /**
  360. *
  361. * retrieve field value
  362. * proxy to getData($name) method
  363. *
  364. * @param string $name column name
  365. *
  366. * @return string|null return field value or null if field doesnt exist
  367. */
  368. public function __get($name)
  369. {
  370. return $this->getData($name);
  371. }
  372. /**
  373. *
  374. * proxy to addData($name, $value) method
  375. *
  376. * @param string $name
  377. * @param mixed $value
  378. *
  379. * @return $this
  380. */
  381. public function __set($name, $value)
  382. {
  383. return $this->addData($name, $value);
  384. }
  385. /**
  386. *
  387. * unset column from row
  388. *
  389. * @param string $name column name
  390. *
  391. * @throws \InvalidArgumentException
  392. * @return $this
  393. */
  394. public function __unset($name)
  395. {
  396. if (!array_key_exists($name, $this->_data)) {
  397. throw new \InvalidArgumentException(
  398. sprintf("Column name '%s' is not in the row.", $name));
  399. }
  400. unset($this->_data[$name]);
  401. return $this;
  402. }
  403. /**
  404. *
  405. * data to be added in the serialized object
  406. *
  407. * @return array
  408. */
  409. public function __sleep()
  410. {
  411. return array('_primary', '_data');
  412. }
  413. /**
  414. *
  415. * when deserializing an object, return it in unconnected state
  416. */
  417. public function __wakeup()
  418. {
  419. $this->_connected = false;
  420. }
  421. /**
  422. *
  423. * return data as an array
  424. *
  425. * @return array
  426. */
  427. public function toArray()
  428. {
  429. return (array)$this->_data;
  430. }
  431. /**
  432. *
  433. * check if column name exists in row
  434. *
  435. * @param string $name column name
  436. *
  437. * @return bool
  438. */
  439. public function __isset($name)
  440. {
  441. return array_key_exists($name, $this->_data);
  442. }
  443. /**
  444. *
  445. * retrieves an associative array of primary keys.
  446. *
  447. * @throws \RuntimeException
  448. * @return array
  449. */
  450. protected function _getPrimaryKey()
  451. {
  452. if (!is_array($this->_primary)) {
  453. throw new \RuntimeException("The primary key must be set as an array");
  454. }
  455. $primary = array_flip($this->_primary);
  456. $array = array_intersect_key($this->_data, $primary);
  457. if (count($primary) != count($array)) {
  458. throw new \RuntimeException(
  459. sprintf("Table '%s' does not have the same primary key as the row.", get_class($this->_table)));
  460. }
  461. return $array;
  462. }
  463. /**
  464. *
  465. * Constructs where statement for retrieving row(s).
  466. *
  467. * @return array
  468. */
  469. protected function _getWhereQuery()
  470. {
  471. $where = array();
  472. $db = $this->_table->getAdapter();
  473. $primaryKey = $this->_getPrimaryKey();
  474. $info = $this->_table->info();
  475. $metadata = $info[AbstractTable::METADATA];
  476. // retrieve recently updated row using primary keys
  477. foreach ($primaryKey as $column => $value) {
  478. $type = $metadata[$column]['DATA_TYPE'];
  479. $columnName = $db->quoteIdentifier($column, true);
  480. $where[] = $db->quoteInto("{$columnName} = ?", $value, $type);
  481. }
  482. return $where;
  483. }
  484. /**
  485. *
  486. * create a new table object
  487. *
  488. * @param string $tableName
  489. *
  490. * @return \Cube\Db\Table\AbstractTable
  491. * @throws \RuntimeException
  492. */
  493. protected function _getTableFromString($tableName)
  494. {
  495. if (class_exists($tableName)) {
  496. return new $tableName();
  497. }
  498. throw new \RuntimeException(
  499. sprintf("Table '%s' does not exist.", $tableName));
  500. }
  501. /**
  502. *
  503. * prepare table reference
  504. *
  505. * @param \Cube\Db\Table\AbstractTable $dependentTable
  506. * @param \Cube\Db\Table\AbstractTable $parentTable
  507. * @param string $ruleKey
  508. *
  509. * @return array
  510. */
  511. protected function _prepareReference(AbstractTable $dependentTable, AbstractTable $parentTable, $ruleKey)
  512. {
  513. $parentTableName = self::FQN_SEP . get_class($parentTable);
  514. $map = $dependentTable->getReference($parentTableName, $ruleKey);
  515. if (!isset($map[AbstractTable::REF_COLUMNS])) {
  516. $parentInfo = $parentTable->info();
  517. $map[AbstractTable::REF_COLUMNS] = array_values((array)$parentInfo['primary']);
  518. }
  519. $map[AbstractTable::COLUMNS] = (array)$map[AbstractTable::COLUMNS];
  520. $map[AbstractTable::REF_COLUMNS] = (array)$map[AbstractTable::REF_COLUMNS];
  521. return $map;
  522. }
  523. /*
  524. * methods needed to implement the ArrayAccess and IteratorAggregate interfaces
  525. */
  526. /**
  527. * check whether a offset exists
  528. *
  529. * @param mixed $offset
  530. *
  531. * @return bool
  532. */
  533. public function offsetExists($offset)
  534. {
  535. return $this->__isset($offset);
  536. }
  537. /**
  538. *
  539. * get offset
  540. *
  541. * @param mixed $offset
  542. *
  543. * @return mixed|null|string
  544. */
  545. public function offsetGet($offset)
  546. {
  547. return $this->__get($offset);
  548. }
  549. /**
  550. *
  551. * set offset
  552. *
  553. * @param mixed $offset
  554. * @param mixed $value
  555. */
  556. public function offsetSet($offset, $value)
  557. {
  558. $this->__set($offset, $value);
  559. }
  560. /**
  561. *
  562. * unset offset
  563. *
  564. * @param mixed $offset
  565. *
  566. * @return $this
  567. */
  568. public function offsetUnset($offset)
  569. {
  570. return $this->__unset($offset);
  571. }
  572. /**
  573. *
  574. * get iterator
  575. *
  576. * @return \ArrayIterator|\Traversable
  577. */
  578. public function getIterator()
  579. {
  580. return new \ArrayIterator((array)$this->_data);
  581. }
  582. /**
  583. *
  584. * count elements of an object
  585. *
  586. * @return int
  587. */
  588. public function count()
  589. {
  590. return count((array)$this->_data);
  591. }
  592. }