PageRenderTime 51ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/database/cfDatabaseWorker.class.php

https://bitbucket.org/cyberfox/cyberfox-php-framework
PHP | 659 lines | 513 code | 57 blank | 89 comment | 116 complexity | a0aaf124f70628e81457b78ead727046 MD5 | raw file
Possible License(s): LGPL-3.0
  1. <?php
  2. /**
  3. -----------------------------------------------------------------------------
  4. * DATA OBJECT WORKER CLASS
  5. *
  6. *
  7. -----------------------------------------------------------------------------
  8. -----------------------------------------------------------------------------
  9. * @copyright (C) 2011 Cyberfox Software Solutions e.U.
  10. * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License version 3 (LGPLv3)
  11. * @author Christian Graf <christian.graf@cyberfox.at>
  12. -----------------------------------------------------------------------------
  13. -----------------------------------------------------------------------------
  14. * @package redfox
  15. * @subpackage dataset
  16. * @category data
  17. -----------------------------------------------------------------------------
  18. -----------------------------------------------------------------------------
  19. * @version $Id: cfDatabaseWorker.class.php 126 2012-10-15 13:58:44Z cgraf $
  20. * @date $Date: 2012-10-15 15:58:44 +0200 (Mo, 15 Okt 2012) $
  21. * @svnauthor $Author: cgraf $
  22. -----------------------------------------------------------------------------
  23. */
  24. class cfDatabaseWorker implements cfIDataWorker
  25. {
  26. const RETURN_ARRAY = 1;
  27. const RETURN_OBJECTLIST = 2;
  28. const RETURN_COLLECTION = 3;
  29. /**
  30. * put your comment there...
  31. *
  32. * @param cfIDataObject $DataObj
  33. * @param mixed $Filter
  34. * @param mixed $Options
  35. */
  36. public static function Load(cfIDataObject &$DataObj, $Filter = null, array $Options = array())
  37. {
  38. if($DataObj instanceof cfIDataObject)
  39. {
  40. $mapping = cfObjectMapper::GetDatabaseMapping($DataObj);
  41. if($mapping !== false && !$mapping->IsEmpty())
  42. {
  43. $tablename = $mapping->GetTablename();
  44. $fields = $mapping->GetFields();
  45. $stmt = self::GetSelectStatement($tablename, $fields, $Filter, true, $Options);
  46. if($stmt->execute() === true)
  47. {
  48. if($stmt->rowCount() == 1)
  49. {
  50. $ret = $stmt->fetch(PDO::FETCH_ASSOC);
  51. if(empty($ret)) //No data found
  52. return false;
  53. else
  54. return self::FillObject($DataObj, $ret);
  55. }
  56. else
  57. return false; //Should never happenz: To many reults, only one row should be returned!
  58. }
  59. else
  60. {
  61. $err = $stmt->errorInfo();
  62. throw new cfDatabaseException('An error occured while executing a database statement => SQLSTATE: ' .$err[0] .', CODE: ' .$err[1] .', MESSAGE: ' .$err[2] ."\n" .print_r($stmt, true));
  63. }
  64. }
  65. else
  66. return false;
  67. }
  68. else
  69. {
  70. throw new cfArgumentException('Wrong object type for loading.');
  71. }
  72. return false;
  73. }
  74. public static function LoadByPrimaryKey(cfIDataObject &$DataObj, $PrimaryKeyValue, array $Options = array())
  75. {
  76. if($DataObj instanceof cfIDataObject)
  77. {
  78. $mapping = cfObjectMapper::GetDatabaseMapping($DataObj);
  79. if($mapping !== false && !$mapping->IsEmpty())
  80. {
  81. return self::Load($DataObj, array($mapping->GetPrimaryKeyName() => $PrimaryKeyValue), $Options);
  82. }
  83. }
  84. else
  85. {
  86. throw new cfArgumentException('Wrong object type for loading.');
  87. }
  88. return false;
  89. }
  90. public static function LoadBySQL(cfIDataObject &$DataObj, $SQL, array $Options = array())
  91. {
  92. if($DataObj instanceof cfIDataObject)
  93. {
  94. $mapping = cfObjectMapper::GetDatabaseMapping($DataObj);
  95. if($mapping !== false && !$mapping->IsEmpty())
  96. {
  97. $stmt = cfDatabase::Prepare($SQL);
  98. if($stmt->execute() === true)
  99. {
  100. if($stmt->rowCount() == 1)
  101. {
  102. $ret = $stmt->fetch(PDO::FETCH_ASSOC);
  103. if(empty($ret)) //No data found
  104. return false;
  105. else
  106. return self::FillObject($DataObj, $ret);
  107. }
  108. else
  109. return false; //Should never happenz: To many reults, only one row should be returned!
  110. }
  111. else
  112. {
  113. $err = $stmt->errorInfo();
  114. throw new cfDatabaseException('An error occured while executing a database statement => SQLSTATE: ' .$err[0] .', CODE: ' .$err[1] .', MESSAGE: ' .$err[2] ."\n" .print_r($stmt, true));
  115. }
  116. }
  117. else
  118. return false;
  119. }
  120. else
  121. {
  122. throw new cfArgumentException('Wrong object type for loading.');
  123. }
  124. return false;
  125. }
  126. public static function GetTablename(cfIDataObject &$DataObj)
  127. {
  128. if($DataObj instanceof cfIDataObject)
  129. {
  130. $mapping = cfObjectMapper::GetDatabaseMapping($DataObj);
  131. if($mapping !== false && !$mapping->IsEmpty())
  132. {
  133. return $mapping->GetTablename();
  134. }
  135. }
  136. else
  137. {
  138. throw new cfArgumentException('Wrong object type for loading.');
  139. }
  140. return false;
  141. }
  142. public static function GetPrimaryKeyName(cfIDataObject &$DataObj)
  143. {
  144. if($DataObj instanceof cfIDataObject)
  145. {
  146. $mapping = cfObjectMapper::GetDatabaseMapping($DataObj);
  147. if($mapping !== false && !$mapping->IsEmpty())
  148. {
  149. return $mapping->GetPrimaryKeyName();
  150. }
  151. }
  152. else
  153. {
  154. throw new cfArgumentException('Wrong object type for loading.');
  155. }
  156. return false;
  157. }
  158. /**
  159. * put your comment there...
  160. *
  161. * @param cfIDataObject $DataObj
  162. * @param mixed $Data
  163. */
  164. private static function FillObject(cfIDataObject &$DataObj, &$Data)
  165. {
  166. if($DataObj instanceof cfIDataObject)
  167. {
  168. $mapping = cfObjectMapper::GetDatabaseMapping($DataObj);
  169. if($mapping !== false && !$mapping->IsEmpty())
  170. {
  171. $properties = $mapping->GetProperties();
  172. if($mapping->IsNamebased()) //no mapping, property names and db column names are the same
  173. {
  174. foreach($Data as $fieldname => $value)
  175. {
  176. $call = '$DataObj->';
  177. $set = $mapping->GetSetMethodForProperty($fieldname);
  178. if(!empty($set))
  179. $call .= $set .'(' .($value !== null ? cfFilter::QuoteStr($value, true) : 'null') .')';
  180. else
  181. $call .= $fieldname .' = ' .($value !== null ? cfFilter::QuoteStr($value, true) : 'null');
  182. if(eval($call .';') === false)
  183. {
  184. throw new cfApplicationException('One or more values of the given object "' .get_class($DataObj) .'" could not be updated.');
  185. }
  186. }
  187. return true;
  188. }
  189. else
  190. {
  191. throw new cfNotImplementedException('Sorry, in this version an data object can only be filled with the option "namebased"!');
  192. }
  193. }
  194. return false;
  195. }
  196. }
  197. /**
  198. * put your comment there...
  199. *
  200. * @param mixed $Tablename
  201. * @param mixed $Fields
  202. * @param mixed $Filter
  203. * @param mixed $Limit
  204. * @param mixed $Options
  205. */
  206. private static function &GetSelectStatement($Tablename, $Fields, $Filter = null, $Limit = false, $Options = array())
  207. {
  208. $select = $where = $params = array();
  209. $whereSeperator = isset($Options['where_seperator']) && !empty($Options['where_seperator']) ? trim($Options['where_seperator']) : 'AND';
  210. $useLike = $useWildcard_right = $useWildcard_left = false;
  211. if(isset($Options['use_like_wildcards']) && $Options['use_like_wildcards'] === true)
  212. {
  213. $useLike = true;
  214. $useWildcard_right = true;
  215. $useWildcard_left = true;
  216. }
  217. else if(isset($Options['use_like_wildcard_right']) && $Options['use_like_wildcard_right'] === true)
  218. {
  219. $useLike = true;
  220. $useWildcard_right = true;
  221. }
  222. else if(isset($Options['use_like_wildcard_left']) && $Options['use_like_wildcard_left'] === true)
  223. {
  224. $useLike = true;
  225. $useWildcard_left = true;
  226. }
  227. else if(isset($Options['use_like']) && $Options['use_like'] === true)
  228. {
  229. $useLike = true;
  230. }
  231. //WHERE cases
  232. if(!empty($Filter) && is_array($Filter))
  233. {
  234. foreach($Filter as $searchField => $searchValue)
  235. {
  236. if(isset($Fields[$searchField]))
  237. {
  238. $nullable = isset($Fields[$searchField]['nullable']) && $Fields[$searchField]['nullable'] === true ? true : false;
  239. $type = isset($Fields[$searchField]['type']) && !empty($Fields[$searchField]['type']) ? $Fields[$searchField]['type'] : PDO::PARAM_STR;
  240. $length = isset($Fields[$searchField]['length']) && !empty($Fields[$searchField]['length']) ? $Fields[$searchField]['length'] : null;
  241. if(is_null($searchValue) && $nullable === true)
  242. {
  243. $where[] = cfDatabase::QuoteIdentifier($searchField) .' IS NULL';
  244. }
  245. else if(is_array($searchValue))
  246. {
  247. $tmp = array();
  248. foreach($searchValue as $i => $value)
  249. {
  250. $tmp[] = ':' .$searchField .$i;
  251. $params[$searchField .$i] = array('value' => $value, 'type' => $type, 'length' => $length);
  252. }
  253. $where[] = cfDatabase::QuoteIdentifier($searchField) .' IN (' .implode(',', $tmp) .')';
  254. }
  255. else
  256. {
  257. if($useLike)
  258. {
  259. $tmp = $useWildcard_left ? '%' : '';
  260. $tmp .= $searchValue;
  261. $tmp = $useWildcard_right ? '%' : '';
  262. $where[] = cfDatabase::QuoteIdentifier($searchField) .' LIKE :' .$searchField;
  263. $params[$searchField] = array('value' => $searchValue, 'type' => PDO::PARAM_STR, 'length' => $length);
  264. }
  265. else
  266. {
  267. $where[] = cfDatabase::QuoteIdentifier($searchField) .' = :' .$searchField;
  268. $params[$searchField] = array('value' => $searchValue, 'type' => $type, 'length' => $length);
  269. }
  270. }
  271. }
  272. }
  273. }
  274. else if(!empty($Filter) && !is_object($Filter))
  275. {
  276. $where[] = trim($Filter);
  277. }
  278. $query = new cfSimpleSqlQuery(cfSimpleSqlQuery::SELECT);
  279. $query->SetTable(cfDatabase::QuoteIdentifier($Tablename));
  280. //COLUMNS
  281. foreach($Fields as $fieldname => $settings)
  282. {
  283. $query->AddColumn(cfDatabase::QuoteIdentifier($fieldname));
  284. }
  285. //WHERE
  286. if(!empty($where))
  287. $query->SetWhere(implode(" $whereSeperator ", $where));
  288. //ORDER BY
  289. if(isset($Options['orderby']) && is_array($Options['orderby']) && !empty($Options['orderby']))
  290. $query->SetOrderBy(implode(', ', $Options['orderby']));
  291. //LIMIT
  292. if($Limit === true)
  293. $query->SetLimit('1');
  294. else
  295. {
  296. if(cfDatabase::GetDBH()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql')
  297. {
  298. if(isset($Options['limit']) && is_numeric($Options['limit']) && $Options['limit'] > 0)
  299. $query->SetLimit('0, ' .intval($Options['limit']));
  300. }
  301. }
  302. $stmt = null;
  303. $stmt = cfDatabase::Prepare($query->Build());
  304. if(!empty($params))
  305. {
  306. $referenceValues = array();
  307. foreach($params as $paramName => $param) //Needed for reference to bind parameter
  308. $referenceValues[$paramName] = isset($param['value']) ? $param['value'] : null;
  309. foreach($referenceValues as $paramName => &$v)
  310. {
  311. $t = isset($params[$paramName]['type']) ? $params[$paramName]['type'] : PDO::PARAM_STR;
  312. $l = isset($params[$paramName]['length']) ? $params[$paramName]['length'] : null;
  313. $stmt->bindParam($paramName, $v, $t);
  314. }
  315. }
  316. return $stmt;
  317. }
  318. public static function Save(cfIDataObject &$DataObj, array $Options = array())
  319. {
  320. if($DataObj instanceof cfIDataObject)
  321. {
  322. $mapping = cfObjectMapper::GetDatabaseMapping($DataObj);
  323. if($mapping !== false && !$mapping->IsEmpty())
  324. {
  325. $tablename = $mapping->GetTablename();
  326. $fields = $mapping->GetFields();
  327. $properties = $mapping->GetProperties();
  328. $namebased = $mapping->IsNamebased();
  329. $primaryKey = $mapping->GetPrimaryKeyName();
  330. if($primaryKey === false)
  331. throw new cfDatabaseException('Could not save the data object, no primary key is defined.');
  332. $values = self::GetValues($DataObj);
  333. $qt = !isset($values[$primaryKey]['value']) || empty($values[$primaryKey]['value']) ? cfSimpleSqlQuery::INSERT : cfSimpleSqlQuery::UPDATE;
  334. $query = new cfSimpleSqlQuery($qt);
  335. if($qt == cfSimpleSqlQuery::INSERT && isset($Options['delayed']) && $Options['delayed'] === true)
  336. $query->DelayedInsert(true);
  337. $query->SetTable(cfDatabase::QuoteIdentifier($tablename));
  338. //COLUMNS
  339. foreach($fields as $fieldname => $settings)
  340. {
  341. if($fieldname != $primaryKey)
  342. {
  343. $query->AddColumn(cfDatabase::QuoteIdentifier($fieldname));
  344. $query->AddValue(':' .$fieldname);
  345. }
  346. }
  347. if($qt == cfSimpleSqlQuery::UPDATE)
  348. $query->SetWhere(cfDatabase::QuoteIdentifier($primaryKey) .' = :' .$primaryKey);
  349. $stmt = null;
  350. $stmt = cfDatabase::Prepare($query->Build());
  351. //VALUES
  352. $referenceValues = array();
  353. foreach($values as $fieldname => $value) //Needed for reference to bind parameter
  354. $referenceValues[$fieldname] = isset($value['value']) ? $value['value'] : null;
  355. foreach($referenceValues as $fieldname => &$v)
  356. {
  357. if($fieldname != $primaryKey || $qt == cfSimpleSqlQuery::UPDATE)
  358. {
  359. $t = isset($values[$fieldname]['type']) ? $values[$fieldname]['type'] : PDO::PARAM_STR;
  360. $l = isset($values[$fieldname]['length']) ? $values[$fieldname]['length'] : null;
  361. if(!$stmt->bindParam($fieldname, $v, $t))
  362. cfLogger::Log()->Error('Value for "' .$fieldname .'" could not be bind to param. (Type: ' .$t .', Value: ' .print_r($v, true) .')');
  363. }
  364. }
  365. try
  366. {
  367. if($stmt->execute() === false)
  368. {
  369. $err = $stmt->errorInfo();
  370. throw new cfDatabaseException('An error occured while executing a database statement => SQLSTATE: '
  371. .$err[0] .', CODE: ' .$err[1] .', MESSAGE: ' .$err[2] ."\n" .print_r($stmt, true)
  372. ."\n VALUES: " .print_r($referenceValues, true)
  373. );
  374. }
  375. if($qt == cfSimpleSqlQuery::INSERT)
  376. {
  377. $newID = cfDatabase::LastInsertId();
  378. $call = '$DataObj->';
  379. $set = $mapping->GetSetMethodForProperty($primaryKey);
  380. if(!empty($set))
  381. $call .= $set .'(' .cfDatabase::Quote($newID) .')';
  382. else
  383. $call .= $primaryKey .' = ' .cfDatabase::Quote($newID);
  384. if(eval($call .';') === false)
  385. throw new cfApplicationException('One or more values of the given object "' .get_class($DataObj) .'" could not be updated.');
  386. }
  387. }
  388. catch(Exception $ups)
  389. {
  390. throw new cfDatabaseException('An error occured while saving the object => ' .$ups->getMessage() ."\n" .print_r($stmt, true)
  391. //."\n" .print_r($stmt->debugDumpParams(), true) //@Todo: Catch direct output form this method and put it into a variable
  392. ."\n VALUES: " .print_r($referenceValues, true)
  393. );
  394. }
  395. return true;
  396. }
  397. else
  398. return false;
  399. }
  400. else
  401. {
  402. throw new cfArgumentException('Wrong object type for saving.');
  403. }
  404. return false;
  405. }
  406. /**
  407. * put your comment there...
  408. *
  409. * @param mixed $Properties
  410. */
  411. private static function GetValues(cfIDataObject &$DataObj)
  412. {
  413. $mapping = cfObjectMapper::GetDatabaseMapping($DataObj);
  414. if($mapping !== false && !$mapping->IsEmpty())
  415. {
  416. $fields = $mapping->GetFields();
  417. $properties = $mapping->GetProperties();
  418. $values = array();
  419. if($mapping->IsNamebased())
  420. {
  421. foreach($fields as $fieldname => $settings)
  422. {
  423. $v = null;
  424. $call = '$v = $DataObj->';
  425. $get = $mapping->GetGetMethodForProperty($fieldname);
  426. if(!empty($get))
  427. $call .= $get .'()';
  428. else
  429. $call .= $fieldname;
  430. eval($call .';');
  431. //echo $fieldname .' = ' .(is_null($v) ? ' NULL ' : ($v === 0 || $v === '0' ? ' 0 ' : (empty($v) ? ' EMPTY ' : ' OK '))) .' => ' .$v .'<br>';
  432. $t = isset($settings['type']) ? $settings['type'] : PDO::PARAM_STR;
  433. $l = isset($settings['length']) ? $settings['length'] : null;
  434. $n = isset($settings['nullable']) && $settings['nullable'] == true ? true : false;
  435. if(empty($v) && $v !== 0 && $v !== '0' && $n)
  436. {
  437. $v = null;
  438. $t = PDO::PARAM_NULL;
  439. }
  440. else if(is_null($v))
  441. {
  442. $v = null;
  443. $t = PDO::PARAM_NULL;
  444. }
  445. $values[$fieldname] = array('value' => $v, 'type' => $t, 'length' => $l, 'nullable' => $n);
  446. }
  447. return $values;
  448. }
  449. else
  450. return false; //NOT IMPLEMENTED YET
  451. }
  452. else
  453. return false;
  454. }
  455. /**
  456. * put your comment there...
  457. *
  458. * @param cfIDataObject $DataObj
  459. * @param mixed $Options
  460. */
  461. public static function Delete(cfIDataObject &$DataObj, array $Options = array())
  462. {
  463. if($DataObj instanceof cfIDataObject)
  464. {
  465. $mapping = cfObjectMapper::GetDatabaseMapping($DataObj);
  466. if($mapping !== false && !$mapping->IsEmpty())
  467. {
  468. $tablename = $mapping->GetTablename();
  469. $fields = $mapping->GetFields();
  470. $properties = $mapping->GetProperties();
  471. $primaryKey = $mapping->GetPrimaryKeyName();
  472. if($primaryKey === false)
  473. throw new cfDatabaseException('Could not delete the data object, no primary key is defined.');
  474. $values = self::GetValues($DataObj);
  475. if(empty($values[$primaryKey]))
  476. throw new cfDatabaseException('Could not delete the data object, no primary key is defined.');
  477. $query = new cfSimpleSqlQuery(cfSimpleSqlQuery::DELETE);
  478. $query->SetTable(cfDatabase::QuoteIdentifier($tablename));
  479. $query->SetWhere(cfDatabase::QuoteIdentifier($primaryKey) .' = :' .$primaryKey);
  480. $stmt = null;
  481. $stmt = cfDatabase::Prepare($query->Build());
  482. //VALUES
  483. $stmt->bindParam($primaryKey, $values[$primaryKey]['value'], $values[$primaryKey]['type']);
  484. try
  485. {
  486. //print_r($stmt);
  487. if($stmt->execute() === false)
  488. {
  489. $err = $stmt->errorInfo();
  490. throw new cfDatabaseException('An error occured while executing a database statement => SQLSTATE: ' .$err[0] .', CODE: ' .$err[1] .', MESSAGE: ' .$err[2]."\n".print_r($stmt,true));
  491. }
  492. /*
  493. foreach($fields as $fieldname => $settings)
  494. {
  495. $call = '$DataObj->';
  496. $set = $mapping->GetSetMethodForProperty($fieldname);
  497. if(!empty($set))
  498. $call .= $set .'(null)';
  499. else
  500. $call .= $fieldname .' = null';
  501. if(eval($call .';') === false)
  502. {
  503. throw new cfApplicationException('One or more values of the given object "' .get_class($DataObj) .'" could not be updated.');
  504. }
  505. }
  506. */
  507. }
  508. catch(Exception $ups)
  509. {
  510. throw new cfDatabaseException('An error occured while deleting the object => ' .$ups->getMessage()."\n".print_r($stmt,true));
  511. }
  512. return true;
  513. }
  514. else
  515. return false;
  516. }
  517. else
  518. {
  519. throw new cfArgumentException('Wrong object type for saving.');
  520. }
  521. return false;
  522. }
  523. /**
  524. * put your comment there...
  525. *
  526. * @param cfIDataObject $DataObj
  527. * @param mixed $Filter
  528. * @param mixed $Options
  529. */
  530. public static function LoadAndGetAll($DataObjOrString, $Filter = null, $ReturnType = self::RETURN_OBJECTLIST, array $Options = array())
  531. {
  532. if(empty($DataObjOrString))
  533. return false;
  534. $obj = null;
  535. $classname = '';
  536. if(is_object($DataObjOrString))
  537. {
  538. $obj = $DataObjOrString;
  539. $classname = get_class($obj);
  540. }
  541. else
  542. {
  543. $obj = cfHelper::GetObjectFromString($DataObjOrString);
  544. $classname = $DataObjOrString;
  545. }
  546. if($obj instanceof cfIDataObject)
  547. {
  548. $mapping = cfObjectMapper::GetDatabaseMapping($obj);
  549. if($mapping !== false && !$mapping->IsEmpty())
  550. {
  551. $tablename = $mapping->GetTablename();
  552. $fields = $mapping->GetFields();
  553. $stmt = self::GetSelectStatement($tablename, $fields, $Filter, false, $Options);
  554. if($stmt->execute() === true)
  555. {
  556. $primaryKeyName = $mapping->GetPrimaryKeyName();
  557. $retData = array();
  558. for($i = 0; $ret = $stmt->fetch(PDO::FETCH_ASSOC); $i++)
  559. {
  560. switch($ReturnType)
  561. {
  562. case self::RETURN_ARRAY:
  563. {
  564. //$retData[$ret[$primaryKeyName]] = $ret;
  565. $retData[] = $ret;
  566. }
  567. break;
  568. case self::RETURN_OBJECTLIST:
  569. default:
  570. {
  571. $tmpObj = cfHelper::GetObjectFromString($classname);
  572. if(self::FillObject($tmpObj, $ret))
  573. {
  574. if(isset($ret[$primaryKeyName]))
  575. $rowIdx = $ret[$primaryKeyName];
  576. else
  577. $rowIdx = '_' .$i;
  578. $retData[$rowIdx] = $tmpObj;
  579. }
  580. }
  581. break;
  582. }
  583. }
  584. return $retData;
  585. }
  586. else
  587. {
  588. $err = $stmt->errorInfo();
  589. throw new cfDatabaseException('An error occured while executing a database statement => SQLSTATE: ' .$err[0] .', CODE: ' .$err[1] .', MESSAGE: ' .$err[2] ."\n" .print_r($stmt, true));
  590. }
  591. }
  592. else
  593. return false;
  594. }
  595. else
  596. {
  597. throw new cfArgumentException('Wrong object type for loading.');
  598. }
  599. return false;
  600. }
  601. }
  602. ?>