Entity.class.php 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055
  1. <?php
  2. require_once __DIR__.'/../constant.php';
  3. require_once(__ROOT__.'class'.SLASH.'Database.class.php');
  4. require_once(__ROOT__.'connector'.SLASH.'Oracle.class.php');
  5. require_once(__ROOT__.'connector'.SLASH.'Mysql.class.php');
  6. require_once(__ROOT__.'connector'.SLASH.'Sqlite.class.php');
  7. require_once(__ROOT__.'connector'.SLASH.'SqlServer.class.php');
  8. /**
  9. @version 2
  10. **/
  11. class Entity
  12. {
  13. public $debug = false,$pdo = null,$baseUid= 'local';
  14. public $created,$updated,$creator,$updater,$joins;
  15. public $foreignColumns = array();
  16. public $fieldMapping = array();
  17. public static $lastError = '';
  18. public static $lastResult = '';
  19. public static $lastQuery = '';
  20. const ACTIVE = 'published';
  21. const INACTIVE = 'deleted';
  22. public function __construct()
  23. {
  24. if (!isset($this->TABLE_NAME)) {
  25. $this->TABLE_NAME = strtolower(get_called_class());
  26. }
  27. $this->connect();
  28. $this->fields['created'] = 'datetime';
  29. $this->fields['updated'] = 'datetime';
  30. $this->fields['updater'] = 'string';
  31. $this->fields['creator'] = 'string';
  32. $this->joins = array();
  33. $this->created = time();
  34. foreach ($this->fields as $field => $type) {
  35. if(!is_array($type)) $type = array('type'=>$type,'column'=>$field);
  36. if(!isset( $type['column'] )) $type['column'] = $field;
  37. $this->fieldMapping[$field] = $type;
  38. }
  39. global $myUser;
  40. if(is_object($myUser) && $myUser->login!='') $this->creator = $myUser->login;
  41. }
  42. public function connect()
  43. {
  44. $this->pdo = Database::instance($this->baseUid);
  45. global $databases_credentials;
  46. $this->connector = $databases_credentials[$this->baseUid]['connector'];
  47. }
  48. public function __toString()
  49. {
  50. foreach ($this->toArray() as $key => $value) {
  51. echo $key.' : '.$value.','.PHP_EOL;
  52. }
  53. }
  54. public static function debug()
  55. {
  56. return array(self::$lastQuery, self::$lastError, self::$lastResult);
  57. }
  58. public function __sleep()
  59. {
  60. return array_keys($this->toArray());
  61. }
  62. public function __wakeup()
  63. {
  64. $this->connect();
  65. }
  66. //Comparaison de deux instances d'une même entité, retourne les champs ayant changés uniquement
  67. public static function compare($obj1,$obj2,$ignore=array()){
  68. $class = get_called_class();
  69. $instance = new $class();
  70. $compare = array();
  71. foreach ($obj1->fields as $field => $type) {
  72. if($field == 'updated' || $field == 'updater' || in_array($field, $ignore)) continue;
  73. if($obj1->$field != $obj2->$field){
  74. if($type=='int' && (($obj1->$field==0 && $obj2->$field =='') || ($obj2->$field=='' && $obj1->$field ==0)) ) continue;
  75. $compare[] = array('field'=>$field,'value1'=>$obj1->$field,'value2'=>$obj2->$field);
  76. }
  77. }
  78. return $compare;
  79. }
  80. public function toArray($decoded=false) {
  81. $fields = array();
  82. foreach ($this->fields as $field => $type) {
  83. $fields[$field] = $decoded ? html_entity_decode($this->$field) : $this->$field;
  84. }
  85. return $fields;
  86. }
  87. public function toText() {
  88. $text = array();
  89. foreach ($this->fields as $field => $type) {
  90. $value = is_object($this->$field) ? '[object]' : $this->$field;
  91. $text[]= $field.' = '.$value;
  92. }
  93. return implode(', ',$text);
  94. }
  95. public function fromArray($array) {
  96. foreach ($array as $field => $value) {
  97. $this->$field = $value;
  98. }
  99. }
  100. public function closeDatabase() {
  101. // $this->close();
  102. }
  103. //Génère un slug unique pour l'element de liste utilisable dans le code
  104. // $label => string du label à slugifier
  105. // $column => la colonne en base où checker les slugs existants
  106. public static function generateSlug($label, $column){
  107. $slug = slugify($label);
  108. $class = get_called_class();
  109. $item = new $class();
  110. if(!array_key_exists($column, $item->fields)) return;
  111. $params = array_key_exists('state', $item->fields) ? array('state'=>$class::ACTIVE) : array();
  112. $i='';
  113. while($class::rowCount(array_merge(array($column=>$slug.$i), $params)) > 0 ) $i++;
  114. return $i==''?$slug:$slug.'-'.$i;
  115. }
  116. public static function tableName($escapeName = false)
  117. {
  118. $class = get_called_class();
  119. $instance = new $class();
  120. $connector = $instance->connector;
  121. return $escapeName ? $connector::table_escape.ENTITY_PREFIX.$instance->TABLE_NAME.$connector::table_escape : ENTITY_PREFIX.$instance->TABLE_NAME;
  122. }
  123. // GESTION SQL
  124. /**
  125. * Verifie l'existence de la table en base de donnée.
  126. *
  127. * @author Valentin CARRUESCO
  128. *
  129. * @category manipulation SQL
  130. *
  131. * @param <String> créé la table si elle n'existe pas
  132. *
  133. * @return true si la table existe, false dans le cas contraire
  134. */
  135. public static function checkTable($autocreate = false)
  136. {
  137. $class = get_called_class();
  138. $instance = new $class();
  139. $query = 'SELECT count(*) as numRows FROM sqlite_master WHERE type="table" AND name=?';
  140. $statement = $instance->customQuery($query, array($instance->tableName()));
  141. if ($statement != false) {
  142. $statement = $statement->fetchArray();
  143. if ($statement['numRows'] == 1) {
  144. $return = true;
  145. }
  146. }
  147. if ($autocreate && !$return) self::create();
  148. return $return;
  149. }
  150. public static function install($classDirectory)
  151. {
  152. foreach (glob($classDirectory.SLASH.'*.class.php') as $file) {
  153. $infos = explode('.', basename($file));
  154. $class = array_shift($infos);
  155. require_once($classDirectory.SLASH.$class.'.class.php');
  156. $reflection = new ReflectionClass($class);
  157. if (!class_exists($class) || !method_exists($class, 'create') || $class == get_class() || $reflection->isAbstract()) {
  158. continue;
  159. }
  160. $class::create();
  161. }
  162. }
  163. public static function uninstall($classDirectory)
  164. {
  165. foreach (glob($classDirectory.SLASH.'*.class.php') as $file) {
  166. $infos = explode('.', basename($file));
  167. $class = array_shift($infos);
  168. require_once($classDirectory.SLASH.$class.'.class.php');
  169. $reflection = new ReflectionClass($class);
  170. if (!class_exists($class) || !method_exists($class, 'drop') || $class == get_class() || $reflection->isAbstract()) continue;
  171. $class::drop();
  172. }
  173. }
  174. /**
  175. * Methode de vidage de l'entité.
  176. *
  177. * @author Valentin CARRUESCO
  178. * @category manipulation SQL
  179. * @return Aucun retour
  180. */
  181. public static function truncate()
  182. {
  183. $class = get_called_class();
  184. $instance = new $class();
  185. $connector = $instance->connector;
  186. $sql = $connector::truncate();
  187. $query = Entity::render($sql,array(
  188. 'table' => $instance->tableName(),
  189. 'fieldMapping' => $instance->fieldMapping
  190. ));
  191. $instance->customExecute($query);
  192. }
  193. /**
  194. * Methode de creation de l'entité.
  195. *
  196. * @author Valentin CARRUESCO
  197. * @category manipulation SQL
  198. *
  199. * @return Aucun retour
  200. */
  201. public static function create()
  202. {
  203. $class = get_called_class();
  204. $instance = new $class();
  205. $fields = array();
  206. $connector = $instance->connector;
  207. $types = $connector::types();
  208. foreach ($instance->fields as $field => $type)
  209. $fields[$field] = isset($types[$type]) ? $types[$type] : $types['default'];
  210. $sql = $connector::create();
  211. $query = Entity::render($sql,array(
  212. 'table' => $instance->tableName(),
  213. 'fields' => $fields,
  214. 'fieldMapping' => $instance->fieldMapping
  215. ));
  216. $instance->customExecute($query);
  217. if(isset($instance->indexes)) $instance->index($instance->indexes);
  218. }
  219. public static function drop()
  220. {
  221. $class = get_called_class();
  222. $instance = new $class();
  223. $connector = $instance->connector;
  224. $sql = $connector::drop();
  225. $query = Entity::render($sql,array(
  226. 'table' => $instance->tableName(),
  227. 'fieldMapping' => $instance->fieldMapping
  228. ));
  229. $instance->customExecute($query);
  230. if(isset($instance->indexes)) $instance->index($instance->indexes,false);
  231. }
  232. /**
  233. * Methode d'insertion ou de modifications d'elements de l'entité.
  234. *
  235. * @author Valentin CARRUESCO
  236. *
  237. * @category manipulation SQL
  238. *
  239. * @param Aucun
  240. *
  241. * @return Aucun retour
  242. */
  243. public function save()
  244. {
  245. $this->updated = time();
  246. global $myUser;
  247. if(is_object($myUser) && $myUser->login!='') $this->updater = $myUser->login;
  248. $data = array();
  249. $numericType = array('object', 'timestamp', 'datetime', 'date', 'int', 'float', 'decimal');
  250. $stringType = array('string', 'longstring', 'default');
  251. $connector = $this->connector;
  252. if (isset($this->id) && $this->id > 0) {
  253. $fields = array();
  254. $i = 0;
  255. foreach ($this->fields as $field => $type) {
  256. if ($type == 'key') continue;
  257. $data[':'.$i] = $this->{$field};
  258. if($type=='boolean') $data[':'.$i] = $data[':'.$i] ? 1:0;
  259. //if(in_array($type, $numericType) && !$data[':'.$i]) $data[':'.$i] = 0;
  260. //if(in_array($type, $stringType) && !$data[':'.$i]) $data[':'.$i] = '';
  261. $fields[$field] = ':'.$i;
  262. $i++;
  263. }
  264. $data[':id'] = $this->id;
  265. $sql = $connector::update();
  266. $query = self::render($sql,array(
  267. 'table' => $this->tableName(),
  268. 'fields' => $fields,
  269. 'fieldMapping' => $this->fieldMapping,
  270. 'filters' => array('id'=>':id'),
  271. ));
  272. } else {
  273. $fields = array();
  274. $i = 0;
  275. foreach ($this->fields as $field => $type) {
  276. if ($type == 'key') continue;
  277. $data[':'.$i] = $this->{$field};
  278. if($type=='boolean') $data[':'.$i] = $data[':'.$i] ? 1:0;
  279. //if(in_array($type, $numericType) && !$data[':'.$i]) $data[':'.$i] = 0;
  280. //if(in_array($type, $stringType) && !$data[':'.$i]) $data[':'.$i] = '';
  281. $fields[$field] = ':'.$i;
  282. $i++;
  283. }
  284. $sql = $connector::insert();
  285. $query = self::render($sql,array(
  286. 'table' => $this->tableName(),
  287. 'fields' => $fields,
  288. 'fieldMapping' => $this->fieldMapping
  289. ));
  290. }
  291. $this->customExecute($query, $data);
  292. $this->id = !isset($this->id) || !is_numeric($this->id) ? $this->pdo->lastInsertId() : $this->id;
  293. }
  294. /**
  295. * Méthode de modification d'éléments de l'entité.
  296. *
  297. * @author Valentin CARRUESCO
  298. *
  299. * @category manipulation SQL
  300. *
  301. * @param <Array> $colonnes=>$valeurs
  302. * @param <Array> $colonnes (WHERE) =>$valeurs (WHERE)
  303. * @param <String> $operation="=" definis le type d'operateur pour la requete select
  304. *
  305. * @return Aucun retour
  306. */
  307. public static function change($columns, $columns2 = array(), $operation = '=') {
  308. $class = get_called_class();
  309. $instance = new $class();
  310. $fields = array();
  311. $i = 0;
  312. foreach ($columns as $field => $value) {
  313. $data[':'.$i] = $value;
  314. $fields[$field] = ':'.$i;
  315. $i++;
  316. }
  317. $filters = array();
  318. $i = 0;
  319. foreach ($columns2 as $field => $value) {
  320. //Gestion du IN
  321. if(strlen($field)>=3 && substr($field,-3) == ':IN'){
  322. $filters[$field] = array();
  323. foreach (explode(',',$value) as $v2) {
  324. $tag = ':_'.$i;
  325. $filters[$field][] = $tag;
  326. $data[$tag] = $v2;
  327. $i++;
  328. }
  329. $filters[$field] = implode(',',$filters[$field]);
  330. //Gestion des opérateurs classiques
  331. } else {
  332. $tag = ':_'.$i;
  333. $filters[$field] = $tag;
  334. $data[$tag] = $value;
  335. $i++;
  336. }
  337. }
  338. $connector = $instance->connector;
  339. $sql = $connector::update();
  340. $query = Entity::render($sql,array(
  341. 'table' => $instance->tableName(),
  342. 'fields' => $fields,
  343. 'filters' => $filters,
  344. 'fieldMapping' => $instance->fieldMapping
  345. ));
  346. $instance->customExecute($query, $data);
  347. }
  348. /**
  349. * Méthode de selection de tous les elements de l'entité.
  350. *
  351. * @author Valentin CARRUESCO
  352. *
  353. * @category manipulation SQL
  354. *
  355. * @param <String> $ordre=null
  356. * @param <String> $limite=null
  357. *
  358. * @return <Array<Entity>> $Entity
  359. */
  360. public static function populate($order = null, $limit = null,$selColumn = array('*'),$joins = 0)
  361. {
  362. $results = self::loadAll(array(), $order, $limit,$selColumn,$joins);
  363. return $results;
  364. }
  365. /**
  366. * Méthode de selection multiple d'elements de l'entité.
  367. *
  368. * @author Valentin CARRUESCO
  369. *
  370. * @category manipulation SQL
  371. *
  372. * @param <Array> $colonnes (WHERE)
  373. * @param <Array> $valeurs (WHERE)
  374. * @param <String> $ordre=null
  375. * @param <String> $limite=null
  376. * @param <String> $operation="=" definis le type d'operateur pour la requete select
  377. *
  378. * @return <Array<Entity>> $Entity
  379. */
  380. public static function loadAll($columns = array(), $order = null, $limit = null, $selColumn = array('*'), $joins = 0)
  381. {
  382. $class = get_called_class();
  383. $instance = new $class();
  384. $connector = $instance->connector;
  385. $values = array();
  386. $i=0;
  387. $filters = array();
  388. foreach($columns as $key=>$value){
  389. $filter = array(
  390. 'operator' => '=',
  391. 'field' => $key,
  392. 'postoperator' => ''
  393. );
  394. if(strpos($key,':')!==false){
  395. $infos = explode(':',$key);
  396. $filter['operator'] = $infos[1];
  397. $filter['field'] = $infos[0];
  398. }
  399. $fieldInfos = $instance->fieldMapping[$filter['field']];
  400. $filter['type'] = $fieldInfos['type'];
  401. $filter['column'] = $fieldInfos['column'];
  402. $connector::processField($filter,$value,$values,$i);
  403. $filters[] = $filter;
  404. }
  405. if(!empty($order)){
  406. foreach ($order as $key=>$clause) {
  407. foreach ($instance->fieldMapping as $attribute => $infos) {
  408. $order[$key] = str_replace( $attribute,$infos['column'],$order[$key]);
  409. }
  410. }
  411. }
  412. $data = array(
  413. 'table' => $instance->tableName(),
  414. 'selected' => $selColumn,
  415. 'limit' => !isset($limit) || count($limit) == 0 ? null: $limit,
  416. 'orderby' => !isset($order) || count($order) == 0 ? null: $order,
  417. 'filter' => !isset($filters) || count($filters) == 0 ? null: $filters,
  418. 'fieldMapping' => $instance->fieldMapping
  419. );
  420. $data['joins'] = array();
  421. if($joins!=0){
  422. foreach ($data['selected'] as $k=>$column) {
  423. $data['selected'][$k] = $instance->tableName().'.'.$column;
  424. }
  425. $data = self::recursiveJoining($instance,$data,$joins);
  426. }
  427. $sql = $connector::select();
  428. $sql = Entity::render($sql,$data);
  429. return $instance->customQuery($sql, $values, true, $joins);
  430. }
  431. /**
  432. * Méthode privée de gestion du join récursif sur les objets liés
  433. *
  434. * @author Valentin CARRUESCO
  435. *
  436. * @category manipulation SQL
  437. *
  438. * @param <Object> $instance $instance de départ
  439. * @param <Array> $data Tableau de construction de la requete via render()
  440. * @param <Int> $iterations Nombre d'iteration réecurive maximum
  441. *
  442. * @return <Array> $data
  443. */
  444. private static function recursiveJoining($instance,$data,$iterations){
  445. if($iterations==0) return $data;
  446. $iterations--;
  447. if(isset($instance->links)){
  448. foreach ($instance->links as $field => $className) {
  449. $field2 = 'id';
  450. $classField = explode('.',$className);
  451. if(isset($classField[1]))
  452. list($className,$field2) = $classField;
  453. $joinInstance = new $className();
  454. foreach ($joinInstance->fields as $key=>$type) {
  455. $data['selected'][] = $className::tableName().'.'.$key.' as '.$className::tableName().'_join_'.$key;
  456. }
  457. $data['joins'][] = array(
  458. 'jointable1' => $instance::tableName(),
  459. 'jointable2' => $className::tableName(),
  460. 'field1' => $field,
  461. 'field2' => $field2
  462. );
  463. $data = self::recursiveJoining($joinInstance,$data,$iterations);
  464. }
  465. }
  466. return $data;
  467. }
  468. /**
  469. * Methode de comptage des éléments de l'entité.
  470. *
  471. * @author Valentin CARRUESCO
  472. *
  473. * @category manipulation SQL
  474. * @return<Integer> nombre de ligne dans l'entité'
  475. */
  476. public static function rowCount($columns = array()) {
  477. $values = array();
  478. $class = get_called_class();
  479. $instance = new $class();
  480. $connector = $instance->connector;
  481. $i=0;
  482. $values = array();
  483. $filters = array();
  484. foreach($columns as $key=>$value){
  485. $filter = array(
  486. 'operator' => '=',
  487. 'field' => $key,
  488. 'postoperator' => ''
  489. );
  490. if(strpos($key,':')!==false){
  491. $infos = explode(':',$key);
  492. $filter['operator'] = $infos[1];
  493. $filter['field'] = $infos[0];
  494. }
  495. $fieldInfos = $instance->fieldMapping[$filter['field']];
  496. $filter['type'] = $fieldInfos['type'];
  497. $filter['column'] = $fieldInfos['column'];
  498. $connector::processField($filter,$value,$values,$i);
  499. $filters[] = $filter;
  500. }
  501. $data = array(
  502. 'table' => $class::tableName(),
  503. 'selected' => 'id' ,
  504. 'filter' => count($filters) == 0 ? null: $filters,
  505. 'fieldMapping' => $instance->fieldMapping
  506. );
  507. $sql = $connector::count();
  508. $execQuery = $instance->customQuery(Entity::render($sql,$data), $values);
  509. $row = $execQuery->fetch();
  510. return $row['number'];
  511. }
  512. public static function loadAllOnlyColumn($selColumn, $columns, $order = null, $limit = null)
  513. {
  514. $objects = self::loadAll($columns, $order, $limit, $operation, $selColumn);
  515. if (count($objects) == 0) {
  516. $objects = array();
  517. }
  518. return $objects;
  519. }
  520. /**
  521. * Méthode de selection unique d'élements de l'entité.
  522. *
  523. * @author Valentin CARRUESCO
  524. *
  525. * @category manipulation SQL
  526. *
  527. * @param <Array> $colonnes (WHERE)
  528. * @param <Array> $valeurs (WHERE)
  529. * @param <String> $operation="=" definis le type d'operateur pour la requete select
  530. *
  531. * @return <Entity> $Entity ou false si aucun objet n'est trouvé en base
  532. */
  533. public static function load($columns,$joins =0) {
  534. $objects = self::loadAll($columns, null, array('1'),array('*'),$joins);
  535. if (!isset($objects[0])) $objects[0] = false;
  536. return $objects[0];
  537. }
  538. /**
  539. * Méthode de selection unique d'élements de l'entité.
  540. *
  541. * @author Valentin CARRUESCO
  542. *
  543. * @category manipulation SQL
  544. *
  545. * @param <Array> $colonnes (WHERE)
  546. * @param <Array> $valeurs (WHERE)
  547. * @param <String> $operation="=" definis le type d'operateur pour la requete select
  548. *
  549. * @return <Entity> $Entity ou false si aucun objet n'est trouvé en base
  550. */
  551. public static function getById($id,$joins =0 ) {
  552. return self::load(array('id' => $id),$joins);
  553. }
  554. public static function render($sql,$data=array()) {
  555. //loop
  556. $sql = preg_replace_callback('/{{\:([^\/\:\?}]*)}}(.*?){{\/\:[^\/\:\?}]*}}/',function($matches) use ($data) {
  557. $tag = $matches[1];
  558. $sqlTpl = $matches[2];
  559. $sql = '';
  560. if(isset($data[$tag])){
  561. $i = 0;
  562. $values = $data[$tag];
  563. if($tag =='joins'){
  564. //joins
  565. foreach($values as $join){
  566. $occurence = $sqlTpl;
  567. foreach($join as $key=>$value){
  568. $occurence = str_replace(array('{{'.$key.'}}'),array($value),$occurence);
  569. }
  570. $sql.= $occurence;
  571. }
  572. }else if($tag =='filter'){
  573. //filters
  574. foreach($values as $key=>$value){
  575. $i++;
  576. $last = $i == count($values);
  577. $operator = $value['operator'];
  578. $postoperator = $value['postoperator'];
  579. $key = $value['column'];
  580. $occurence = str_replace(array('{{key}}','{{value}}','{{operator}}','{{postoperator}}'),array($key,
  581. $value['tag'],
  582. $operator,
  583. $postoperator),
  584. $sqlTpl);
  585. $occurence = preg_replace_callback('/{{\;}}(.*?){{\/\;}}/',function($matches) use ($last){
  586. return $last? '': $matches[1];
  587. },$occurence);
  588. $sql.= $occurence;
  589. }
  590. } else {
  591. //Autre boucles
  592. foreach($values as $key=>$value){
  593. $i++;
  594. $last = $i == count($values);
  595. $operator = isset($data['operator']) ? $data['operator'][0] : '=';
  596. $postoperator = isset($data['postoperator']) ? $data['postoperator'][0] : '';
  597. if(strpos($key,':')!==false){
  598. $infos = explode(':',$key);
  599. $key = $infos[0];
  600. $operator = $infos[1];
  601. if($operator=='IN'){
  602. $operator = 'IN(';
  603. $postoperator = ')';
  604. }
  605. }
  606. $occurence = str_replace(array('{{key}}','{{value}}','{{operator}}','{{postoperator}}'),array($key,$value,$operator,$postoperator),$sqlTpl);
  607. $occurence = preg_replace_callback('/{{\;}}(.*?){{\/\;}}/',function($matches) use ($last){
  608. return $last? '': $matches[1];
  609. },$occurence);
  610. $sql.= $occurence;
  611. }
  612. }
  613. return $sql;
  614. }
  615. return '';
  616. },$sql);
  617. //conditions
  618. $sql = preg_replace_callback('/{{\?([^\/\:\?}]*)}}(.*?){{\/\?[^\/\:\?}]*}}/',function($matches) use ($data) {
  619. $key = $matches[1];
  620. $sql = $matches[2];
  621. return !isset($data[$key]) || (is_array($data[$key]) && count($data[$key])==0) ?'':$sql;
  622. },$sql);
  623. //simple vars
  624. $sql = preg_replace_callback('/{{([^\/\:\;\?}]*)}}/',function($matches) use ($data) {
  625. $key = $matches[1];
  626. return isset($data[$key])?$data[$key]:'';
  627. },$sql);
  628. return $sql;
  629. }
  630. /**
  631. * Methode de définition de l'éxistence d'un moins un des éléments spécifiés en base.
  632. *
  633. * @author Valentin CARRUESCO
  634. *
  635. * @category manipulation SQL
  636. * @return<boolean> existe (true) ou non (false)
  637. */
  638. public static function exist($columns = array()) {
  639. $result = self::rowCount($columns);
  640. return $result != 0;
  641. }
  642. public static function deleteById($id) {
  643. self::delete(array('id' => $id));
  644. }
  645. /**
  646. * Méthode de Suppression d'elements de l'entité.
  647. *
  648. * @author Valentin CARRUESCO
  649. *
  650. * @category manipulation SQL
  651. *
  652. * @param <Array> $colonnes (WHERE)
  653. * @param <Array> $valeurs (WHERE)
  654. * @param <String> $operation="=" definis le type d'operateur pour la requete select
  655. *
  656. * @return Aucun retour
  657. */
  658. public static function delete($columns, $limit = array()) {
  659. $values = array();
  660. $class = get_called_class();
  661. $instance = new $class();
  662. $connector = $instance->connector;
  663. $i=0;
  664. $values = array();
  665. $filters = array();
  666. foreach($columns as $key=>$value){
  667. $filter = array(
  668. 'operator' => '=',
  669. 'field' => $key,
  670. 'postoperator' => ''
  671. );
  672. if(strpos($key,':')!==false){
  673. $infos = explode(':',$key);
  674. $filter['operator'] = $infos[1];
  675. $filter['field'] = $infos[0];
  676. }
  677. $fieldInfos = $instance->fieldMapping[$filter['field']];
  678. $filter['type'] = $fieldInfos['type'];
  679. $filter['column'] = $fieldInfos['column'];
  680. $connector::processField($filter,$value,$values,$i);
  681. $filters[] = $filter;
  682. }
  683. $data = array(
  684. 'table' => $class::tableName(),
  685. 'limit' => count($limit) == 0 ? null: $limit,
  686. 'filter' => count($filters) == 0 ? null: $filters,
  687. 'fieldMapping' => $instance->fieldMapping
  688. );
  689. $sql = $connector::delete();
  690. return $instance->customExecute(Entity::render($sql,$data), $values);
  691. }
  692. /**
  693. * Méthode d'indexation de la ou les colonnes ciblées
  694. * nb : il est possible d'appeller automatiquement cette méthode sur les classes entity lors du create
  695. * si la classe contient l'attribut $this->indexes = array(...);
  696. * @author Valentin CARRUESCO
  697. *
  698. * @category manipulation SQL
  699. *
  700. * @param <Array> | <String> $colonne(s)
  701. * @param <Boolean> Mode (true : ajout, false : suppression)
  702. *
  703. * @return Aucun retour
  704. */
  705. public static function index($columns,$mode = true){
  706. if(!is_array($columns)) $columns = array($columns);
  707. $columns = array_filter($columns);
  708. $class = get_called_class();
  709. $instance = new $class();
  710. $connector = $instance->connector;
  711. $class = get_called_class();
  712. $instance = new $class();
  713. foreach($columns as $column){
  714. if(!is_array($column)) $column = array($column);
  715. $data = array(
  716. 'table' => $class::tableName(),
  717. 'column' => '`'.implode('`,`',$column).'`',
  718. 'index_name' => $class::tableName().'_'.implode('_',$column),
  719. 'fieldMapping' => $instance->fieldMapping
  720. );
  721. $results = $class::staticQuery(Entity::render($connector::count_index(),$data));
  722. $exists = $results->fetch();
  723. if($mode){
  724. if($exists['exists'] != 1) $class::staticQuery(Entity::render($connector::create_index(),$data));
  725. }else{
  726. if($exists['exists'] > 0) $class::staticQuery(Entity::render($connector::drop_index(),$data));
  727. }
  728. }
  729. }
  730. public static function paginate($itemPerPage,$currentPage,&$query,$data,$alias=''){
  731. global $_;
  732. $class = get_called_class();
  733. $obj = new $class();
  734. $keys = array_keys($obj->fields, 'key');
  735. $key = count($keys) == 1 ? $keys[0] : 'id';
  736. $queryNumber = $query;
  737. $queryNumber = preg_replace("/(?<!\([^(\)])(SELECT.+[\n|\t]*FROM[\s\t\r\n])({{table}}|`?".$obj->tableName()."`?)(?![^(\)]*\))/iU", 'SELECT DISTINCT '.(!empty($alias) ? $alias : $obj->tableName()).'.'.$key.' FROM $2',$queryNumber);
  738. $queryNumber = $class::staticQuery('SELECT COUNT(*) FROM ('.$queryNumber.') number',$data)->fetch();
  739. $number = $queryNumber[0];
  740. $pageNumber = $number / $itemPerPage;
  741. if($currentPage > $pageNumber) $currentPage = 0;
  742. $limit = ' LIMIT '.($currentPage*$itemPerPage).','.$itemPerPage;
  743. $query .= $limit;
  744. return array(
  745. 'pages' => $pageNumber,
  746. 'current' => $currentPage,
  747. 'total' => $number
  748. );
  749. }
  750. public function customExecute($query, $data = array()) {
  751. self::$lastQuery = $query;
  752. try {
  753. if(BASE_DEBUG) self::logFile($query.' :: '.json_encode($data, JSON_UNESCAPED_UNICODE));
  754. $stm = $this->pdo->prepare($query);
  755. $stm->execute($data);
  756. } catch (Exception $e) {
  757. self::$lastError = $this->pdo->errorInfo();
  758. //Log l'erreur uniquement si elle ne proviens pas elle même de Log (evite les mysql gone away)
  759. if(get_class($this) !='Log')
  760. Log::put("[SQL ERROR] - ".$e->getMessage().' - '.$e->getLine().' - Requete : '.$query.' - Données : '.json_encode($data,JSON_UNESCAPED_UNICODE));
  761. if(BASE_DEBUG) self::logFile( "Erreur : ".$e->getMessage());
  762. throw new Exception($e->getMessage().' - '.$e->getLine().' : '.$query.' - '.json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
  763. }
  764. }
  765. public static function provide($parameter = 'id',$join=0){
  766. global $_;
  767. $class = get_called_class();
  768. return !empty($_[$parameter]) ? $class::getById($_[$parameter],$join) : new $class();
  769. }
  770. // L'alias utilisé pour la colonne du join doit avoir la syntaxe [table]_join_[field]
  771. // Ex : address_join_street
  772. public static function staticQuery($query, $data = array(), $fill = false,$joins = 0) {
  773. $class = get_called_class();
  774. $instance = new $class();
  775. return $instance->customQuery($query, $data, $fill,$joins);
  776. }
  777. public function customQuery($query, $data = array(), $fill = false,$joins = 0) {
  778. $query = str_replace('{{table}}', $this->tableName(true), $query);
  779. $mapping = $this->fieldMapping;
  780. $query = preg_replace_callback('/{{([^}]*)}}/si', function($match) use ($mapping){
  781. return isset($mapping[$match[1]]) && isset($mapping[$match[1]]['column']) ? $mapping[$match[1]]['column'] : $match[0];
  782. }, $query);
  783. self::$lastQuery = $query;
  784. try{
  785. if(BASE_DEBUG) self::logFile($query.' :: '.json_encode($data, JSON_UNESCAPED_UNICODE));
  786. $results = $this->pdo->prepare($query);
  787. $results->execute($data);
  788. if (!$results) throw new Exception(json_encode($this->pdo->errorInfo()));
  789. }catch(Exception $e){
  790. self::$lastError = $e->getMessage();
  791. //echo "[SQL ERROR] - Erreur : ".$e->getMessage().' - Requete : '.$query.' - Données : '.json_encode($data);
  792. Log::put("[SQL ERROR] - Erreur : ".$e->getMessage().' - Requete : '.$query.' - Données : '.json_encode($data, JSON_UNESCAPED_UNICODE));
  793. if(BASE_DEBUG) self::logFile( "Erreur : ".$e->getMessage());
  794. throw $e;
  795. }
  796. if (!$fill) return $results;
  797. $class = get_class($this);
  798. $objects = array();
  799. $results = $results->fetchAll(PDO::FETCH_ASSOC);
  800. self::$lastResult = $results;
  801. foreach ($results as $queryReturn) {
  802. $object = new $class();
  803. foreach ($this->fields as $field => $type) {
  804. $dbField = $field;
  805. if(is_array($type)){
  806. if(isset($type['column'])) $dbField = $type['column'];
  807. $type = $type['type'];
  808. }
  809. //if($this->tableName()=='WORKORDER') var_dump($queryReturn);
  810. if (isset($queryReturn[$dbField])) {
  811. $object->{$field} = $queryReturn[$dbField];
  812. unset($queryReturn[$dbField]);
  813. }
  814. }
  815. if($joins>0) $object = self::recursiveJoiningFill($object,$queryReturn,$joins);
  816. foreach ($queryReturn as $key => $value) {
  817. if(!is_numeric($key)) $object->foreignColumns[$key] = $value;
  818. }
  819. $objects[] = $object;
  820. unset($object);
  821. }
  822. return $objects == null ? array() : $objects;
  823. }
  824. //Récuperation d'une/plusieurs colonne non référencée dans l'objet mais récuperée dans une requete static query
  825. public function foreign($key=null){
  826. if(!isset($key)) return $this->foreignColumns;
  827. return isset($this->foreignColumns[$key]) ? $this->foreignColumns[$key] : '';
  828. }
  829. private static function recursiveJoiningFill($object,$queryReturn,$iterations){
  830. if($iterations == 0) return $object;
  831. $iterations--;
  832. if(isset($object->links)){
  833. foreach ($object->links as $link=>$classLink) {
  834. $classField = explode('.',$classLink);
  835. if(isset($classField[1]))
  836. $classLink = $classField[0];
  837. $instanceLink = new $classLink();
  838. foreach ($instanceLink->fields as $field => $type) {
  839. if (isset($queryReturn[$classLink::tableName().'_join_'.$field]))
  840. $instanceLink->{$field} = $queryReturn[$classLink::tableName().'_join_'.$field];
  841. }
  842. $instanceLink = self::recursiveJoiningFill($instanceLink,$queryReturn,$iterations);
  843. $object->joins[$link] = $instanceLink;
  844. }
  845. }
  846. return $object;
  847. }
  848. /**
  849. * Récupere l'objet join ex : $contact->join("adress")->street; --> récupere l'attribut street de la class Adress dont l'id est spécifié dans la colonne adress de la class Contact
  850. * Nb : cette méthode ne fonctionne que si vous avez placé le parametre joins > 0 dans la méthode LoadALl
  851. * Nb : cette méthode ne fonctionne que si vous avez précisé le lien entre Contact et Adress dans la classe Contact via :
  852. protected $links = array(
  853. 'address' => 'Address'
  854. );
  855. *
  856. * @author Valentin CARRUESCO
  857. * @category manipulation SQL
  858. *
  859. * @param <Array> $colonnes (WHERE)
  860. * @param <Array> $valeurs (WHERE)
  861. * @param <String> $operation="=" definis le type d'operateur pour la requete select
  862. *
  863. * @return Aucun retour
  864. */
  865. public function join($field){
  866. return isset($this->joins[$field])?$this->joins[$field]:'';
  867. }
  868. public static function logFile($msg){
  869. file_put_contents(__DIR__.SLASH.'..'.SLASH.'sql.debug.sql', date('H:i:s').' | '.$msg.PHP_EOL,FILE_APPEND);
  870. }
  871. /*
  872. public function __get($name) {
  873. $pos = strpos($name, '_object');
  874. if ($pos !== false) {
  875. $field = strtolower(substr($name, 0, $pos));
  876. if (array_key_exists($field, $this->fields)) {
  877. $class = ucfirst($field);
  878. return $class::getById($this->{$field});
  879. }
  880. }
  881. throw new Exception('Attribut '.get_class($this)."->$name non existant");
  882. }
  883. */
  884. public static function log_executed_query($string, $data) {
  885. $indexed=$data==array_values($data);
  886. foreach($data as $k=>$v) {
  887. if(is_string($v)) $v="'$v'";
  888. if($indexed) $string=preg_replace('/\?/',$v,$string,1);
  889. else $string=str_replace(":$k",$v,$string);
  890. }
  891. self::logFile($string);
  892. }
  893. }