Entity.class.php 40 KB

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