PageRenderTime 55ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/bors/storage/pdo.php

https://bitbucket.org/Balancer/bors-core
PHP | 572 lines | 455 code | 89 blank | 28 comment | 37 complexity | 8716a920073cb8a1658716f689683305 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-3.0
  1. <?php
  2. /**
  3. Бэкенд, использующий для инициализации объектов PHP PDO
  4. */
  5. class bors_storage_pdo extends bors_storage implements Iterator
  6. {
  7. function __construct($object = NULL)
  8. {
  9. if($object)
  10. {
  11. $this->__object = $object;
  12. $this->__db_name = $object->db_name();
  13. $this->__table_name = $object->table_name();
  14. }
  15. }
  16. function db()
  17. {
  18. if($this->__dbh)
  19. return $this->__dbh;
  20. $db_driver_name = $this->_db_driver_name();
  21. return $this->__dbh = new $db_driver_name($this->__db_name);
  22. }
  23. function load($object)
  24. {
  25. $object->storage()->storage_create();
  26. $select = array();
  27. $post_functions = array();
  28. foreach(bors_lib_orm::main_fields($object) as $f)
  29. {
  30. $x = $f['name'];
  31. if($f['name'] != $f['property'])
  32. $x .= " AS `{$f['property']}`";
  33. $select[] = $x;
  34. if(!empty($f['post_function']))
  35. $post_functions[$f['property']] = $f['post_function'];
  36. }
  37. $where = array('`'.$object->id_field().'`=' => $object->id());
  38. $dummy = array();
  39. self::__join('inner', $object, $select, $where, $post_functions, $dummy);
  40. self::__join('left', $object, $select, $where, $post_functions, $dummy);
  41. $db_name = $object->db_name();
  42. $db_driver_name = $this->_db_driver_name();
  43. $dbh = new $db_driver_name($db_name);
  44. $data = $dbh->select($object->table_name(), join(',', $select), $where);
  45. if(!$data)
  46. return $object->set_is_loaded(false);
  47. $object->data = $data;
  48. if(!empty($post_functions))
  49. self::post_functions_do($object, $post_functions);
  50. $object->set_is_loaded(true);
  51. // print_d($data);
  52. return true;
  53. }
  54. function load_array($object, $where)
  55. {
  56. $object->storage()->storage_create();
  57. if(is_null($object))
  58. {
  59. $db_name = $where['*db'];
  60. $table_name = $where['*table'];
  61. unset($where['*db'], $where['*table']);
  62. $select = array('*');
  63. $class_name = 'base_object_db';
  64. $object = new base_object_db(NULL);
  65. }
  66. else
  67. {
  68. $db_name = $object->db_name();
  69. $table_name = $object->table_name();
  70. $class_name = $object->class_name();
  71. list($select, $where) = self::__query_data_prepare($object, $where);
  72. }
  73. $db_driver_name = $this->_db_driver_name();
  74. $dbh = new $db_driver_name($db_name);
  75. $datas = $dbh->select_array($table_name, join(',', $select), $where, $class_name);
  76. $objects = array();
  77. foreach($datas as $data)
  78. {
  79. $object->set_id($data['id']);
  80. $object->data = $data;
  81. $object->set_is_loaded(true);
  82. $objects[] = $object;
  83. $object = new $class_name(NULL);
  84. }
  85. return $objects;
  86. }
  87. function count($object, $where)
  88. {
  89. if(is_null($object))
  90. {
  91. $db_name = $where['*db'];
  92. $table_name = $where['*table'];
  93. unset($where['*db'], $where['*table']);
  94. $select = array('*');
  95. $class_name = 'base_object_db';
  96. $object = new base_object_db(NULL);
  97. }
  98. else
  99. {
  100. $db_name = $object->db_name();
  101. $table_name = $object->table_name();
  102. $class_name = $object->class_name();
  103. list($select, $where) = self::__query_data_prepare($object, $where);
  104. }
  105. $dbh = new driver_pdo($db_name);
  106. $count = $dbh->select($table_name, 'COUNT(*)', $where, $class_name);
  107. if(!empty($where['group']))
  108. $count = intval($dbh->get('SELECT FOUND_ROWS()'));
  109. return $count;
  110. }
  111. static private function __query_data_prepare($object, $where)
  112. {
  113. $select = array();
  114. $post_functions = array();
  115. $db_driver_name = $object->storage()->_db_driver_name();
  116. foreach(bors_lib_orm::main_fields($object) as $f)
  117. {
  118. $x = $f['name'];
  119. if($load = call_user_func(array($db_driver_name, 'load_sql_function'), $f['type']))
  120. {
  121. $x = sprintf($load, $f['name'])." AS {$f['property']}";
  122. }
  123. elseif($f['name'] != $f['property'])
  124. {
  125. $x .= " AS `{$f['property']}`";
  126. if(array_key_exists($f['property'], $where))
  127. {
  128. $where[$f['name']] = $where[$f['property']];
  129. unset($where[$f['property']]);
  130. }
  131. }
  132. $select[] = $x;
  133. if(!empty($f['post_function']))
  134. $post_functions[$f['property']] = $f['post_function'];
  135. }
  136. $dummy = array();
  137. self::__join('inner', $object, $select, $where, $post_functions, $dummy);
  138. self::__join('left', $object, $select, $where, $post_functions, $dummy);
  139. // $dbh = new driver_pdo($object->db_name());
  140. return array($select, $where, $post_functions);
  141. }
  142. static private function __update_data_prepare($object, $where, $dbh = NULL)
  143. {
  144. $update = array();
  145. $db_name = $object->db_name();
  146. $table_name = $object->table_name();
  147. $db_driver_name = $object->storage()->_db_driver_name();
  148. foreach(bors_lib_orm::main_fields($object) as $f)
  149. {
  150. $x = $f['name'];
  151. if($save = call_user_func(array($db_driver_name, 'save_sql_function'), $f['type']))
  152. $direct_sql = sprintf($save, $object->get($f['property']));
  153. else
  154. $direct_sql = false;
  155. // Сюда сунуть обратное преобразование
  156. // if(!empty($f['post_function']))
  157. // $post_functions[$f['property']] = $f['post_function'];
  158. if(preg_match('/^(\w+)\(([\w`]+)\)$/', $f['name'], $m))
  159. {
  160. $f['name'] = $m[2];
  161. $sql = $_back_functions[$m[1]];
  162. }
  163. else
  164. $sql = false;
  165. if(array_key_exists($f['property'], $object->changed_fields))
  166. {
  167. if($direct_sql)
  168. $update[$db_name][$table_name]['raw '.$f['name']] = $direct_sql;
  169. elseif($sql)
  170. $update[$db_name][$table_name][$f['name']] = $sql.'('.$object->get($f['property']).')';
  171. else
  172. $update[$db_name][$table_name][$f['name']] = $object->get($f['property']);
  173. }
  174. }
  175. $select = array(); // dummy
  176. self::__join('inner', $object, $select, $where, $post_functions, $update);
  177. self::__join('left', $object, $select, $where, $post_functions, $update);
  178. if(empty($update[$db_name][$table_name][$object->id_field()]))
  179. $update[$db_name][$table_name][$object->id_field()] = $object->id();
  180. // $dbh = new driver_pdo($object->db_name());
  181. return array($update, $where);
  182. }
  183. function save($object)
  184. {
  185. $where = array($object->id_field() => $object->id());
  186. list($update, $where) = self::__update_data_prepare($object, $where);
  187. $update_plain = array();
  188. foreach($update as $db_name => $tables)
  189. {
  190. foreach($tables as $table_name => $fields)
  191. {
  192. unset($fields['*id_field']);
  193. $update_plain = array_merge($update_plain, $fields);
  194. }
  195. }
  196. unset($update_plain[$object->id_field()]);
  197. if(!$update_plain)
  198. return;
  199. $db_driver_name = $object->storage()->_db_driver_name();
  200. $dbh = new $db_driver_name($object->db_name());
  201. $dbh->update($object->table_name(), $where, $update_plain);
  202. }
  203. private $data;
  204. private $dbi;
  205. private $object;
  206. private $__class_name;
  207. static function each($class_name, $where)
  208. {
  209. $object = new $class_name(NULL);
  210. list($select, $where) = self::__query_data_prepare($object, $where);
  211. $db_name = $object->db_name();
  212. $table_name = $object->table_name();
  213. $iterator = new bors_storage_pdo();
  214. $iterator->object = $object;
  215. $iterator->__class_name = $class_name;
  216. $storage = $object->storage();
  217. $db_driver_name = $storage->_db_driver_name();
  218. $dbh = new $db_driver_name($db_name);
  219. $iterator->dbi = $dbh->each($table_name, join(',', $select), $where);
  220. return $iterator;
  221. }
  222. public function key() { } // Not implemented
  223. public function current()
  224. {
  225. return $this->object;
  226. }
  227. public function next()
  228. {
  229. $this->data = $this->dbi->next();
  230. return $this->__init_object();
  231. }
  232. public function rewind()
  233. {
  234. if(!empty($this->dbi))
  235. $this->data = $this->dbi->rewind();
  236. return $this->__init_object();
  237. }
  238. public function valid() { return $this->data != false; }
  239. private function __init_object()
  240. {
  241. $data = $this->data;
  242. $class_name = $this->__class_name;
  243. if(!class_exists($class_name))
  244. bors_throw("Can't find class name '$class_name'");
  245. $object = new $class_name($data['id']);
  246. // $object->set_id($data['id']);
  247. $object->data = $data;
  248. $object->set_is_loaded(true);
  249. return $this->object = $object;
  250. }
  251. static private function __join($type, $object, &$select, &$where, &$post_functions, &$update)
  252. {
  253. $where['*class_name'] = $object->class_name();
  254. $main_db = $object->db_name();
  255. $main_table = $object->table_name();
  256. $main_id_field = $object->id_field();
  257. $join = $object->get("{$type}_join_fields");
  258. if($join)
  259. {
  260. foreach($join as $db_name => $tables)
  261. {
  262. foreach($tables as $table_name => $fields)
  263. {
  264. if(!preg_match('/^(\w+)\((\w+)\)$/', $table_name, $m))
  265. throw new Exception('Unknown field format '.$table_name.' for class '.$object->class_name());
  266. $id_field = $m[2];
  267. $table_name = $m[1];
  268. $t = '';
  269. if($db_name != $main_db)
  270. $t = "`$db_name`.";
  271. $t .= "`$table_name`";
  272. $j = "$t ON `$main_table`.`$main_id_field` = $t.`$id_field`";
  273. $where[$type.'_join'][] = $j;
  274. $update[$db_name][$table_name]['*id_field'] = $id_field;
  275. foreach($fields as $property => $field)
  276. {
  277. $field = bors_lib_orm::field($property, $field);
  278. $x = "$t.`{$field['name']}`";//FIXME: предусмотреть возможность подключать FUNC(`field`)
  279. if($field['name'] != $field['property'])
  280. $x .= " AS `{$field['property']}`";
  281. $select[] = $x;
  282. if(!empty($field['post_function']))
  283. $post_functions[$field['property']] = $field['post_function'];
  284. if(!empty($object->changed_fields) && array_key_exists($field['property'], $object->changed_fields))
  285. $update[$db_name][$table_name][$field['name']] = $object->get($field['property']);
  286. }
  287. }
  288. }
  289. }
  290. }
  291. function _db_driver_name() { return 'driver_pdo'; }
  292. function create($object)
  293. {
  294. $where = array();
  295. list($data, $where) = self::__update_data_prepare($object, $where);
  296. if(!$data)
  297. return;
  298. $db_driver_name = $this->_db_driver_name();
  299. $main_table = true;
  300. $new_id = NULL;
  301. foreach($data as $db_name => $tables)
  302. {
  303. $dbh = new $db_driver_name($this->pdo_dsn($db_name));
  304. foreach($tables as $table_name => $fields)
  305. {
  306. if(!$main_table)
  307. {
  308. $id_field = $fields['*id_field'];
  309. unset($fields['*id_field']);
  310. $fields[$id_field] = $new_id;
  311. }
  312. // bors_debug::syslog("inserts", "insert $table_name, ".print_r($fields, true));
  313. $object->storage()->storage_create();
  314. if($object->replace_on_new_instance() || $object->attr('__replace_on_new_instance'))
  315. $dbh->replace($table_name, $fields);
  316. elseif($object->ignore_on_new_instance())
  317. $dbh->insert_ignore($table_name, $fields);
  318. else
  319. {
  320. if($object->get('insert_delayed_on_new_instance'))
  321. $fields['*DELAYED'] = true;
  322. $dbh->insert($table_name, $fields);
  323. }
  324. // Закомментировано, так как не позволяет аплоадить изображения с ignore.
  325. if($main_table && !$object->get('insert_delayed_on_new_instance')/* && !$object->ignore_on_new_instance()*/)
  326. {
  327. $main_table = false;
  328. $new_id = $dbh->last_id();
  329. if(!$new_id)
  330. $new_id = $object->id();
  331. if(!$new_id && ($idf = $object->id_field()))
  332. $new_id = $object->get($idf);
  333. if(!$new_id && !$object->ignore_on_new_instance())
  334. bors_debug::syslog('_orm_error', "Can't get new id on new instance for ".$object->debug_title()."; data=".print_r($object->data, true));
  335. }
  336. }
  337. }
  338. $object->set_id($new_id);
  339. }
  340. function delete($object)
  341. {
  342. $object->on_delete();
  343. $update = array();
  344. $where = array();
  345. $select = array();
  346. $post_functions = array();
  347. self::__join('inner', $object, $select, $where, $post_functions, $update);
  348. self::__join('left', $object, $select, $where, $post_functions, $update);
  349. foreach($update as $db_name => $tables)
  350. {
  351. $dbh = new driver_pdo($db_name);
  352. foreach($tables as $table_name => $fields)
  353. $dbh->delete($table_name, array($fields['*id_field'] => $object->id()));
  354. }
  355. $dbh = new driver_pdo($object->db_name());
  356. $dbh->delete($object->table_name(), array($object->id_field() => $object->id()));
  357. }
  358. function _fields_types()
  359. {
  360. return bors_throw("Undefined PDO fields map (dsn=$dsn) for class '$class_name'");
  361. }
  362. function create_table($class_name = NULL)
  363. {
  364. /* case 'mysql':
  365. $map = array(
  366. 'string' => 'VARCHAR(255)',
  367. 'text' => 'TEXT',
  368. 'int' => 'INT',
  369. 'uint' => 'INT UNSIGNED',
  370. 'bool' => 'TINYINT(1) UNSIGNED',
  371. 'float' => 'FLOAT',
  372. 'enum' => 'ENUM(%)',
  373. '*autoinc' => 'AUTO_INCREMENT',
  374. '*primary_in_field' => '',
  375. '*primary_post_field' => 'PRIMARY KEY (%FIELD%)',
  376. );
  377. break; */
  378. $map = $this->_fields_types();
  379. if($class_name)
  380. {
  381. $object = new $class_name(NULL);
  382. $db_name = $object->db_name();
  383. $table_name = $object->table_name();
  384. $db_driver_name = $this->_db_driver_name();
  385. $db = new $db_driver_name($db_name);
  386. }
  387. else
  388. {
  389. $object = $this->__object;
  390. $db_name = $this->__db_name;
  391. $table_name = $this->__table_name;
  392. $db =$this->__dbh;
  393. }
  394. $db_fields = array();
  395. $primary = false;
  396. $fields = bors_lib_orm::main_fields($object);
  397. foreach($fields as $field)
  398. {
  399. $db_field = ''.$field['name'].' '.$map[$field['type']];
  400. if($field['property'] == 'id')
  401. {
  402. if($decl = $map['*id_field_declaration'])
  403. $db_field = sprintf($decl, $field['name']);
  404. //$map['*primary_in_field'].' '.$map['*autoinc'];
  405. $primary = $field['name'];
  406. }
  407. $db_fields[$db_field] = $db_field;
  408. }
  409. if(empty($primary))
  410. return bors_throw(ec("Не найден первичный индекс для ").print_r($object_fields, true));
  411. if($map['*primary_post_field'])
  412. $db_fields[] = str_replace('%FIELD%', $primary, $map['*primary_post_field']);
  413. $query = "CREATE TABLE IF NOT EXISTS $table_name (".join(', ', array_values($db_fields)).");";
  414. $db_driver_name = $this->_db_driver_name();
  415. $db = new $db_driver_name($this->pdo_dsn($db_name));
  416. $db->exec($query);
  417. // $db->close();
  418. }
  419. static function drop_table($class_name)
  420. {
  421. if(!config('can-drop-tables'))
  422. return bors_throw(ec('Удаление таблиц запрещено'));
  423. $foo = new $class_name(NULL);
  424. $foo_storage = $foo->storage();
  425. foreach($foo->fields() as $db_name => $tables)
  426. {
  427. $db = new driver_pdo($foo_storage->pdo_dsn($db_name));
  428. foreach($tables as $table_name => $fields)
  429. {
  430. if(preg_match('/^(\w+)\((\w+)\)$/', $table_name, $m))
  431. $table_name = $m[1];
  432. $db->query("DROP TABLE IF EXISTS $table_name");
  433. // $db->close();
  434. }
  435. }
  436. }
  437. function storage_create()
  438. {
  439. if(config('pdo_tables_autocreate', true) && !$this->storage_exists())
  440. $this->create_table();
  441. }
  442. function storage_exists()
  443. {
  444. static $exists_map = array();
  445. $table = $this->__table_name;
  446. if(array_key_exists($table, $exists_map))
  447. return $exists_map[$table];
  448. $db = $this->db();
  449. //FIXME: осторожно! Нужно придумать универсальный способ pdo-escape для имён, не значений!
  450. try
  451. {
  452. $db->get("SELECT 1 FROM $table LIMIT 1");
  453. $exists_map[$table] = true;
  454. }
  455. catch(Exception $e)
  456. {
  457. $exists_map[$table] = false;
  458. }
  459. return $exists_map[$table];
  460. }
  461. }