array('table_name1'=>array('table_field1', 'table_field2', ...), 'table_name2'=>array(...)), * 'types'=>array('field1'=>'field_type1', 'field2'=>'field_type2', ...) * 'constraints'=>array('table_name1'=>'table_contraint1', 'table_name2'=>'table_contraint2', ...), * 'cascading_delete'=>array('table_name1'=>array('linked_table1', 'linked_table2', ...), 'table_name2'=>...)) * @var Array */ public function __construct($sDbServer, $sLogin, $sPass, $sDatabase, $asOptions, $sEncoding='utf8mb4') { parent::__construct(__CLASS__, Settings::DEBUG); $this->sDatabase = $sDatabase; $this->asOptions = $asOptions; //$this->oConnection = mysql_connect(self::DB_SERVER, self::DB_LOGIN, self::DB_PASS); $this->oConnection = new mysqli($sDbServer, $sLogin, $sPass); $this->syncPhpParams($sEncoding); /* $dsn = 'mysql:dbname='.$this->sDatabase.';host='.self::DB_SERVER; try {$dbh = new PDO($dsn, self::DB_LOGIN, self::DB_PASS);} catch (PDOException $e) {$this->addError('Connexion échouée : ' . $e->getMessage());} */ $this->setTrace(false); //if(!$this->oConnection) if($this->oConnection->connect_error) { //$this->addError('bug connection'); $this->addError('bug connection : '.$this->oConnection->connect_error); $this->sDbState = self::DB_NO_CONN; } else { //if(!mysql_select_db($this->sDatabase, $this->oConnection)) if(!$this->oConnection->select_db($this->sDatabase)) { $this->addError('bug selecting database. Installing...'); $this->sDbState = self::DB_NO_DATA; } } } private function syncPhpParams($sEncoding) { //Characters encoding $this->oConnection->set_charset($sEncoding); //SET NAMES //Time zone $oNow = new DateTime(); $iMins = $oNow->getOffset() / 60; $iSign = ($iMins < 0)?-1:1; $iMins = abs($iMins); $iHours = floor($iMins / 60); $iMins -= $iHours * 60; $sOffset = sprintf('%+d:%02d', $iHours*$iSign, $iMins); $this->setQuery("SET time_zone='{$sOffset}';"); } public function __destruct() { parent::__destruct(); //mysql_close($this->oConnection); $this->oConnection->close(); } public function setTrace($bAction) { $this->bTrace = $bAction; } public function getTables() { /* $oReflect = new ReflectionClass(__CLASS__); $asConstants = $oReflect->getConstants(); $sTableTag = '_TABLE'; $asTables = array(); foreach($asConstants as $sConstant=>$sConstantValue) { if(mb_strpos($sConstant, $sTableTag)!==false && mb_strpos($sConstant, $sTableTag)==(mb_strlen($sConstant) - mb_strlen($sTableTag))) { $asTables[] = $sConstantValue; } } return $asTables; */ return array_keys($this->asOptions['tables']); } public function install() { //Create Database $this->setQuery("DROP DATABASE IF EXISTS ".$this->sDatabase); $this->setQuery("CREATE DATABASE ".$this->sDatabase." DEFAULT CHARACTER SET ".Settings::DB_ENC." DEFAULT COLLATE ".Settings::DB_ENC."_general_ci"); //mysql_select_db($this->sDatabase, $this->oConnection); $this->oConnection->select_db($this->sDatabase); //Create tables @array_walk($this->getInstallQueries(), array($this, 'setQuery')); } //For debug purposes public function getFullInstallQuery() { $asInstallQueries = $this->getInstallQueries(); return str_replace("\n", "
", implode(";\n\n", $asInstallQueries))."\n\n"; } private function getInstallQueries() { $asTables = $this->getTables(); $asInstallQueries = array_map(array($this, 'getInstallQuery'), $asTables); $asAlterQueries = $this->getForeignKeyQueries($asTables); return array_merge($asInstallQueries, $asAlterQueries); } private function getInstallQuery($sTableName) { $asTableColumns = $this->getTableColumns($sTableName); $sQuery = "\n".$this->implodeAll($asTableColumns, "` ", "\n", "`", ",")."\n".implode(", \n", $this->getTableConstraints($sTableName)); return "CREATE TABLE `{$sTableName}` ({$sQuery})"; } private function getForeignKeyQueries($asTableNames) { $asForeignKeyQueries = array(); foreach($asTableNames as $sTableName) { $asTableColumns = array_keys($this->getTablecolumns($sTableName)); foreach($asTableColumns as $sColumnName) { if($this->isId($sColumnName) && $sColumnName!=self::getId($sTableName)) { $asForeignKeyQueries[] = "ALTER TABLE ".$sTableName." ADD INDEX(`".$sColumnName."`)"; $asForeignKeyQueries[] = "ALTER TABLE ".$sTableName." ADD FOREIGN KEY (`".$sColumnName."`) REFERENCES ".self::getTable($sColumnName)."(`".$sColumnName."`)"; } } } return $asForeignKeyQueries; } private function setQuery($sQuery, $sTypeQuery=__FUNCTION__) { $this->getQuery($sQuery, $sTypeQuery); //return (mysql_affected_rows()!=0); return ($this->oConnection->affected_rows!=0); } private function getQuery($sQuery, $sTypeQuery=__FUNCTION__) { $sQuery = str_replace(array("\n", "\t"), array(" ", ""), $sQuery); //$oResult = mysql_query($sQuery, $this->oConnection); if($this->bTrace) { $this->bDebug = true; $this->addNotice($sQuery); } if(!($oResult = $this->oConnection->query($sQuery))) { $this->addError("\nErreur SQL : \n".str_replace("\t", "", $sQuery)."\n\n".str_replace(array("\t", "\n"), "", $this->oConnection->error)); } return $oResult; } public function getLastError() { } public function getArrayQuery($sQuery, $bStringOnly=false, $sGroupBy='', $sTypeQuery=__FUNCTION__) { $iIndex = 0; $iColumnCount = 0; $asResult = array(); $oResult = $this->getQuery($sQuery, true, $sTypeQuery); if($oResult!==false) { //while($asCurrentRow = mysql_fetch_array($oResult)) while($asCurrentRow = $oResult->fetch_array()) { if($bStringOnly) $asCurrentRow = $this->arrayKeyFilter($asCurrentRow, 'is_string'); //Add table reel keys if($sGroupBy!='' && array_key_exists($sGroupBy, $asCurrentRow)) { $iRowKey = $asCurrentRow[$sGroupBy]; unset($asCurrentRow[$sGroupBy]); } else $iRowKey = $iIndex; //For first loop, check table width if($iIndex==0) $iColumnCount = count($asCurrentRow); //One column case : collapse a level if($iColumnCount==1) $asCurrentRow = array_shift($asCurrentRow); $asResult[$iRowKey] = $asCurrentRow; $iIndex++; } } return $asResult; } private function getMaxIncrementedValue($sTable) { return $this->selectValue($sTable, "MAX(".$this->getId($sTable).")"); } public static function getId($sTableName, $bFull=false) { $sColumnName = self::ID_TAG.self::getText($sTableName); return $bFull?self::getFullColumnName($sTableName, $sColumnName):$sColumnName; } public static function getText($sTableName, $bFull=false) { $sColumnName = mb_substr(str_replace('`', '', $sTableName), 0, -1); $sColumnName = mb_substr($sColumnName, -2)=='ie'?mb_substr($sColumnName, 0, -2).'y':$sColumnName; return $bFull?self::getFullColumnName($sTableName, $sColumnName):$sColumnName; } public static function getFullColumnName($sTableName, $sColumnName) { return $sTableName.".".$sColumnName; } private function isId($sColumnName, $sTableName='') { $asTables = ($sTableName=='')?$this->getTables():array($sTableName); $asTableIds = array_map(array('self', 'getId'), $asTables); return in_array($sColumnName, $asTableIds); } private function getTable($sTableId) { $asTables = $this->getTables(); $asTableIds = array_map(array('self', 'getId'), $asTables); if(in_array($sTableId, $asTableIds)) return $asTables[array_search($sTableId, $asTableIds)]; else { $this->addError('Id '.$sTableId.' présent dans aucune table'); return false; } } public function getTablecolumns($sTableName) { if(!array_key_exists($sTableName, $this->asOptions['tables'])) return false; $asTableColumns = array(self::getId($sTableName)); foreach($this->asOptions['tables'][$sTableName] as $sFieldName) $asTableColumns[] = $sFieldName; $asTableColumns[] = 'led'; $asTableName = array_fill(0, count($asTableColumns), $sTableName); return array_combine($asTableColumns, array_map(array('self', 'getColumnType'), $asTableColumns, $asTableName)); } private function getColumnType($sColumnName, $sTableName) { $sColumnType = ''; switch($sColumnName) { case array_key_exists($sColumnName, $this->asOptions['types']): $sColumnType = $this->asOptions['types'][$sColumnName]; break; case 'led': $sColumnType = "TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP"; break; case $this->isId($sColumnName, $sTableName): $sColumnType = "int(10) UNSIGNED auto_increment"; break; case $this->isId($sColumnName): $sColumnType = "int(10) UNSIGNED"; break; } return $sColumnType; } private function getTableConstraints($sTableName) { //Primary key $asTableConstraints = array('PRIMARY' => "PRIMARY KEY (`".self::getId($sTableName)."`)"); //Foreign keys: applied using ALTER TABLE syntax at the end to prevent scheduling CREATE TABLE queries //Other constraints if(array_key_exists($sTableName, $this->asOptions['constraints'])) $asTableConstraints[] = $this->asOptions['constraints'][$sTableName]; return $asTableConstraints; } private function addQuotes($oData) { //TODO remake $asTrustedFunc = array('CURDATE()', 'NOW()'); $sChar = "'"; if(is_array($oData)) { $asChar = array_fill(1, count($oData), $sChar); return array_combine(array_keys($oData), array_map(array($this, 'addQuotes'), $oData, $asChar)); } else { if(in_array($oData, $asTrustedFunc)) return $oData; else return $sChar.$oData.$sChar; } } private function getLastId() { return $this->oConnection->insert_id; } public function insertRow($sTableName, $asData) { $this->cleanSql($sTableName); $this->cleanSql($asData); $asQueryValues = $this->addQuotes($asData); $sQuery = "INSERT INTO ".$sTableName." (`".implode("`, `", array_keys($asQueryValues))."`) VALUES (".implode(", ", $asQueryValues).")"; return $this->setQuery($sQuery)?$this->getLastId():0; } public function updateRow($sTableName, $asConstraints, $asData) { return $this->updateRows($sTableName, $asConstraints, $asData, 1); } public function updateRows($sTableName, $asConstraints, $asData, $iLimit=0) { if(!is_array($asConstraints)) { $asConstraints = array($this->getId($sTableName)=>$asConstraints); } $this->cleanSql($sTableName); $this->cleanSql($iTableId); $this->cleanSql($asData); $this->cleanSql($asConstraints); $asQueryValues = $this->addQuotes($asData); $asConstraintsValues = $this->addQuotes($asConstraints); $this->addColumnSelectors($asQueryValues); $this->addColumnSelectors($asConstraintsValues); $sLimit = $iLimit>0?" LIMIT $iLimit":""; $sQuery = "UPDATE {$sTableName} ". "SET ".$this->implodeAll($asQueryValues, " = ", ", ")." ". "WHERE ".$this->implodeAll($asConstraintsValues, " = ", " AND ").$sLimit; $iResult = false; if($this->setQuery($sQuery)) { $iResult = ($iLimit==1)?$this->selectValue($sTableName, $this->getId($sTableName), $asConstraints):true; } return $iResult; } public function insertUpdateRow($sTableName, $asData, $asKeys=array(), $bUpdate=true) { $sTableIdName = self::getId($sTableName); //check for data in the db if($asKeys==array()) { $asKeys[] = $sTableIdName; } $asValues = array_intersect_key($asData, array_flip($asKeys)); $iTableId = $this->selectValue($sTableName, $sTableIdName, $asValues); //insert if(!$iTableId) { $iTableId = $this->insertRow($sTableName, $asData); } //Update elseif($bUpdate) { if(array_key_exists($sTableIdName, $asData)) { unset($asData[$sTableIdName]); } $iTableId = $this->updateRow($sTableName, $iTableId, $asData); } return $iTableId; } public function selectInsert($sTableName, $asData, $asKeys=array()) { return $this->insertUpdateRow($sTableName, $asData, $asKeys, false); } public function deleteRow($sTableName, $iTableId) { $this->cleanSql($sTableName); $this->cleanSql($iTableId); //linked tables switch($sTableName) { case array_key_exists($sTableName, $this->asOptions['cascading_delete']) : $asTables = array_filter(is_array($sTableName)?$sTableName:array($sTableName) + $this->asOptions['cascading_delete'][$sTableName]); break; case is_string($sTableName) : $asTables = array($sTableName); break; case is_array($sTableName): $asTables = $sTableName; break; default: $asTables = array(); } foreach($asTables as $sTable) { $this->setQuery("DELETE FROM ".$sTable." WHERE ".$this->getId($sTableName)." = ".$iTableId); } } public function emptyTable($sTableName) { $this->cleanSql($sTableName); return $this->setQuery("TRUNCATE ".$sTableName); } public function selectRows($asInfo, $bStringOnly=true, $sGroupBy='') { $asAttributes = array('select'=>"SELECT", 'from'=>"FROM", 'join'=>"LEFT JOIN", 'joinOn'=>"LEFT JOIN", 'constraint'=>"WHERE", 'groupBy'=>"GROUP BY", 'orderBy'=>"ORDER BY", 'limit'=>'LIMIT'); $asRowSeparators = array('select'=>", ", 'from'=>"", 'join'=>" LEFT JOIN ", 'joinOn'=>" LEFT JOIN ", 'constraint'=>" AND ", 'groupBy'=>", ", 'orderBy'=>", ", 'limit'=>""); $asOperators = array('constraint'=>" = ", 'orderBy'=>" ", 'join'=>" USING(", 'joinOn'=>" ON "); $asEndOfStatement = array('constraint'=>"", 'orderBy'=>"", 'join'=>")", 'joinOn'=>""); $sQuery = ""; foreach($asAttributes as $sStatement => $sKeyWord) { $asSelection = array_key_exists($sStatement, $asInfo)?$asInfo[$sStatement]:array(); if(!is_array($asSelection)) { $asSelection = array($asSelection); } //if provided values if(!empty($asSelection)) { $this->cleanSql($asSelection); if($sStatement=='constraint' && !array_key_exists('constVar', $asInfo)) { $asSelection = $this->addQuotes($asSelection); } $this->addColumnSelectors($asSelection); $sQuery .= " ".$sKeyWord." "; //in case of double value input if(array_key_exists($sStatement, $asOperators)) { if($sStatement=='constraint' && array_key_exists('constOpe', $asInfo)) { $asOperators[$sStatement] = $asInfo['constOpe']; } $sQuery .= $this->implodeAll($asSelection, $asOperators[$sStatement], $asRowSeparators[$sStatement], "", $asEndOfStatement[$sStatement]); } else { $sQuery .= implode($asRowSeparators[$sStatement], $asSelection); } } //default value for select elseif($sStatement=='select') { $sQuery .= " ".$sKeyWord." * "; } } return $this->getArrayQuery($sQuery, $bStringOnly, $sGroupBy); } private function addColumnSelectors(&$asSelection) { //FIXME get rid of this $sSqlWord = 'option'; $sKey = array_search($sSqlWord, $asSelection); if($sKey!==false) { $asSelection[$sKey] = "`".$asSelection[$sKey]."`"; } elseif(array_key_exists($sSqlWord, $asSelection)) { $asSelection["`".$sSqlWord."`"] = $asSelection[$sSqlWord]; unset($asSelection[$sSqlWord]); } } public function selectRow($sTableName, $asConstraints=array(), $sColumnName='*') { if(!is_array($asConstraints)) { $asConstraints = array($this->getId($sTableName)=>$asConstraints); } $asResult = $this->selectRows(array('select'=>$sColumnName, 'from'=>$sTableName, 'constraint'=>$asConstraints)); $iCountNb = count($asResult); switch($iCountNb) { case 0 : return false; case $iCountNb > 1 : $this->addError('Trop de résultats pour un selectRow() : '.$iCountNb.' lignes. Table: '.$sTableName.', contrainte: '.self::implodeAll($asConstraints, '=', ' ').', colonne: '.$sColumnName); break; } return array_shift($asResult); } public function selectValue($sTableName, $sColumnName, $oConstraints=array()) { if(!is_array($oConstraints)) { $oConstraints = array($this->getId($sTableName)=>$oConstraints); } return $this->selectRow($sTableName, $oConstraints, $sColumnName); } public function pingValue($sTableName, $oConstraints) { return $this->selectValue($sTableName, 'COUNT(1)', $oConstraints); } public function cleanSql(&$oData) { //self::cleanData($oData, 'mysql_real_escape_string'); //$oData = self::cleanData($oData, 'mysql_real_escape_string'); $this->cleanData($oData); $oData = $this->cleanData($oData); } //TODO déplacer dans ToolBox::implodeALl public static function implodeAll($asText, $asKeyValueSeparator='', $sRowSeparator='', $sKeyPre='', $sValuePost=false) { if($sValuePost===false) { $sValuePost = $sKeyPre; } $asCombinedText = array(); //if unique value for key value separator if(!is_array($asKeyValueSeparator) && !empty($asText)) { $asKeyValueSeparator = array_combine(array_keys($asText), array_fill(0, count($asText), $asKeyValueSeparator)); } foreach($asText as $sKey=>$sValue) { $asCombinedText[] = $sKeyPre.$sKey.$asKeyValueSeparator[$sKey].(is_array($sValue)?implode($sValue):$sValue).$sValuePost; } return implode($sRowSeparator, $asCombinedText); } public static function arrayKeyFilter($asArray, $sCallBack) { $asValidKeys = array_flip(array_filter(array_keys($asArray), $sCallBack)); return array_intersect_key($asArray, $asValidKeys); } public function cleanData($oData) { if(!is_array($oData)) { return $this->oConnection->real_escape_string($oData); } elseif(count($oData)>0) { $asKeys = array_map(array($this, 'cleanData'), array_keys($oData)); $asValues = array_map(array($this, 'cleanData'), $oData); return array_combine($asKeys, $asValues); } } } ?>