Fruml CMS Community: Access to several databases. - Fruml CMS Community

Jump to content

Page 1 of 1
  • You cannot start a new topic
  • You cannot reply to this topic

Access to several databases.

#1 User is offline   Far Icon

  • Member
  • PipPip
  • Group: Members
  • Posts: 24
  • Joined: 04-February 10

Posted 16 February 2010 - 08:20 AM

Do not punish me much for updating your code, Alex.)

Sometimes there is a reason to keep someinformation in different tables in db, so single connect options are not enough to create components to access different tables, so i modified code of DB class to allow multiple DBs connection. One config file is required, so you can create it in fruml/data or in fruml/config folders. Code for config file 'database.php':

<?php

$databases = array();

$databases['default']['server'] = DB_SERVER;
$databases['default']['port'] = DB_PORT;
$databases['default']['username'] = DB_USERNAME;
$databases['default']['password'] = DB_PASSWORD;
$databases['default']['table'] = DB_DATABASE;
$databases['default']['prefix'] = DB_TABLE_PREFIX;

$databases['test']['server'] = 'localhost';
$databases['test']['port'] = '3306';
$databases['test']['username'] = 'root';
$databases['test']['password'] = '';
$databases['test']['table'] = 'fruml';
$databases['test']['prefix'] = 'fruml_';


And modified DB class:

<?php

/**
 * Database class, handles PDO connections and prepared statement generation. 
 * @author alex, gijs
 * @package DB
 */
class db {
    
    /**
     * 
     * @var unknown_type
     */
    private static $_singleton;
    private $_connected = array();
    private $db;
	private static $active_connection = 'default';
	private static $databases;
    public static $tp;
    public static $querylog = array();

    private function __construct() 
	{
		$db_config = APP_PATH.'config/database.php';
		if(file_exists($db_config))
		{
			require($db_config);
			if(isset($databases))
			{
				self::$databases = $databases;
			}
		}
    }

    public static function tp() 
	{
        return '`'.DB_DATABASE.'`.'.DB_TABLE_PREFIX;
    }

    public static function getInstance($db = 'default') 
	{
		if(isset(self::$databases[$db]))
		{
			self::$active_connection = $db;
			self::$tp = '`'.self::$databases[self::$active_connection]['table'].'`.'.self::$databases[self::$active_connection]['prefix'];
			
		}
        
        if(is_null(self::$_singleton)) {
            self::$_singleton = new DB();
        }
        return self::$_singleton;
    }
    
    public function connect() {
        try {
            $this->db = new PDO('mysql:host='.self::$databases[self::$active_connection]['server'].';port='.self::$databases[self::$active_connection]['port'].';dbname='.self::$databases[self::$active_connection]['table'], self::$databases[self::$active_connection]['username'], self::$databases[self::$active_connection]['password']); 
            $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
			$this->db->exec("set names utf8");
            $this->_connected[self::$active_connection] = true;
        } catch (PDOException $e) {
            die('PDO cannot connect: '.$e->getMessage());
        }
    }
    
    public function query($sql) {
        if(!isset($this->_connected[self::$active_connection])) $this->connect();

        $sql = str_replace('%tp%', self::$tp, $sql);
        
        $stmt = $this->db->prepare($sql);
        $stmt->setFetchMode(PDO::FETCH_ASSOC);
        $args = array();
        if(func_num_args() > 1) {
            $args = func_get_args();
            array_shift($args);
            $i = 1;
            foreach($args as $argument) {
                $stmt->bindValue($i, $argument);
                $i++;
            }
        }

        $start = microtime(true);
        try {
            $stmt->execute();
        } catch(PDOException $e) {
            Error::raise($e->getMessage());
        }
        $duration = microtime(true) - $start;
        
        if($stmt->errorCode() !== '00000') {
            $err = $stmt->errorInfo();
            Error::raise('PDO error: '.$err[2]);
        }

        if(DEBUG) {
            $log = array();
            $log['query'] = $sql;
            $log['duration'] = sprintf("%2.4f s", $duration);
            $log['raw'] = $duration;
            $trace = debug_backtrace();
            $trace = $trace[0];
            $log['caller'] = array(
                'file' => (!empty($trace['file']) ? str_replace(ROOT_PATH, '', Library::formatPath($trace['file'])) : 'unknown'),
                'line' => (!empty($trace['line']) ? $trace['line'] : 'unknown'),
            );
            self::$querylog[] = $log;
        }
        
        return new ResultSet($stmt, $args);
    }
    
    public function begin() {
        if(!isset($this->_connected[self::$active_connection])) $this->connect();
        try {
            $this->db->beginTransaction();
        } catch(PDOException $e) {
            Error::raise('PDO Exception: '.$e->getMessage());
        }
    }

    public function commit() {
        if(!isset($this->_connected[self::$active_connection])) $this->connect();
        try {
            $this->db->commit();
        } catch(PDOException $e) {
            Error::raise('PDO Exception: '.$e->getMessage());
        }
    }

    public function rollback() {
        if(!isset($this->_connected[self::$active_connection])) $this->connect();
        try {
            $this->db->rollBack();
        } catch(PDOException $e) {
            Error::raise('PDO Exception: '.$e->getMessage());
        }
    }

    public function insertId() {
        return $this->db->lastInsertId();
    }

    public static function getDebug() {
        $output = '';
        $total = 0;
        foreach(self::$querylog as $key => $log) {
            $output .= Library::stringPad($key+1, 2, ' ').") ".$log['duration']. " @ " .$log['caller']['file']. " : " .$log['caller']['line']."n";
            $output .= "t".$log['query']."n";
            $total += $log['raw'];
        }
        $output .= "nTotal query time: ".sprintf("%2.4f s", $total);
        return $output;
    }
    
    public function count($table, $where = '') {
        $query = 'SELECT COUNT(*) AS counted FROM %tp%'.$table;
        if(!empty($where)) {
            $query .= ' WHERE '.$where;
        }
        $rs = $this->query($query)->fetch();
        return $rs['counted'];
    }
}

class ResultSet implements Iterator,ArrayAccess {

    private $_statement = null;
    private $_params = array ();
    private $_currentRowObj = null;
    private $_currentRowIndex = 0;

    public function __construct(PDOStatement $statement, array $params) {
        $this->_statement = $statement;
        $this->_params = $params;
    }

    public function refresh() {
        $this->_statement->execute($this->_params);
        if ($this->_statement->errorCode() !== '00000') {
            throw new Exception($this->_statement->errorInfo());
        }
    }

    public function fetch() {
        return $this->next();
    }

    public function fetchAll() {
        $this->_currentRowIndex = $this->count() - 1;
        return $this->_statement->fetchAll(PDO::FETCH_ASSOC);
    }

    public function current() {
        return $this->_currentRowObj;
    }

    public function key() {
        return $this->_currentRowIndex;
    }

    public function next() {
        $this->_currentRowObj = $this->_statement->fetch(PDO::FETCH_ASSOC);
        if ($this->_statement->errorCode() !== '00000') {
            throw new Exception($this->_statement->errorInfo());
        }
        $this->_currentRowIndex ++;
        return $this->_currentRowObj;
    }

    public function rewind() {
        $this->refresh();
        $this->_currentRowIndex = 0;
        $this->_currentRowObj = $this->_statement->fetchObject();
        if ($this->_statement->errorCode() !== '00000') {
            throw new Exception($this->_statement->errorInfo());
        }
    }

    public function valid() {
        return $this->_currentRowObj !== false;
    }

    public function count() {
        return $this->_statement->rowCount();
    }
    
    public function rowCount() {
        return $this->_statement->rowCount();
    }
    
    public function __destruct() {
        $this->_statement->closeCursor();
    }
    
    public function offsetSet($offset, $value) {
        throw new Exception('Cannot modify ResultSet.');
    }
    
    public function offsetExists($offset) {
        return $offset < $this->_statement->rowCount();
    }
    
    public function offsetUnset($offset) {
        throw new Exception('Cannot modify ResultSet.');
    }
    
    public function offsetGet($offset) {
        for($i=0; $i<$offset; $i++) {
            $this->next();
        }
        return $this->current();
    }
}



0

#2 User is offline   Far Icon

  • Member
  • PipPip
  • Group: Members
  • Posts: 24
  • Joined: 04-February 10

Posted 16 February 2010 - 08:25 AM

And one more important thing - how to use it.

By defaut it uses default fruml settings and there is no engine code modification required. If you want to use other settings just use query like this:

$test = DB::getInstance('test')->query('SELECT * FROM %tp%component')->fetchAll();

0

#3 User is offline   Alex Icon

  • Author
  • Icon
  • Group: Administrators
  • Posts: 55
  • Joined: 17-January 10

Posted 16 February 2010 - 09:27 AM

I will update that in Mercurial. Just need to figure out some way of having storing the configuration of more database connections. (probably just in config.inc.php, using an array stored in $GLOBAL scope instead of in definitions).

If you have two databases on the same server with the same login credentials, you can simply access the other database in SQL:

SELECT * FROM other_database.other_table WHERE blah = 1


Of course, that won't work if you need different credentials.



0

#4 User is offline   Far Icon

  • Member
  • PipPip
  • Group: Members
  • Posts: 24
  • Joined: 04-February 10

Posted 16 February 2010 - 10:07 AM

Function tp()  in DB shall also be reconfigured.
0

#5 User is offline   Alex Icon

  • Author
  • Icon
  • Group: Administrators
  • Posts: 55
  • Joined: 17-January 10

Posted 16 February 2010 - 11:39 AM

Yeah, I saw that too.
Won't be a problem though :)
0

#6 User is offline   Alex Icon

  • Author
  • Icon
  • Group: Administrators
  • Posts: 55
  • Joined: 17-January 10

Posted 16 February 2010 - 07:15 PM

OK, I've implemented this now and will commit it to Mercurial shortly.

A few pointers:
1 - if the old style DB config is still in place (with the DB_SERVER etc) then those will be used as default
2 - $db->tp no longer works, must use $db->tp() instead (or %tp% in your SQL statement)
3 - extra dbs can be configured in config.inc.php:

<?php
// database configuration date
$fruml_db_configs = array();

$fruml_db_configs['default']['server'] = 'localhost';
$fruml_db_configs['default']['port'] = 3306;
$fruml_db_configs['default']['username'] = '[username]';
$fruml_db_configs['default']['password'] = '[password]';
$fruml_db_configs['default']['schema'] = 'fruml';
$fruml_db_configs['default']['prefix'] = 'fruml_';

$fruml_db_configs['other_db']['server'] = 'localhost';
$fruml_db_configs['other_db']['port'] = '3306';
$fruml_db_configs['other_db']['username'] = '[username]';
$fruml_db_configs['other_db']['password'] = '[password]';
$fruml_db_configs['other_db']['schema'] = 'other_db';
$fruml_db_configs['other_db']['prefix'] = '';

// Installation ID (used for in-memory caching)
define('INSTALL_ID',        'dev');

// xcache admin functions login data
$_SERVER["PHP_AUTH_USER"]   = "[username]";
$_SERVER["PHP_AUTH_PW"]     = "[password]";

// temporary stuff
define('STYLE',             'default');

?>


The code still needs a bit of cleaning up here and there, but it works fine. Very nice, good suggestion, Far!

0

#7 User is offline   Far Icon

  • Member
  • PipPip
  • Group: Members
  • Posts: 24
  • Joined: 04-February 10

Posted 17 February 2010 - 12:21 PM

Thanks. One more feature here:

To see the content of the agruments, passed to DB::query function, modify line 103 of class.db.php
<?php $log['query'] = $sql;

to
<?php $log['query'] = $sql.'(Arguments:'.implode(';', (is_array($args) ? $args : array())).')';


I find it useful when debugging.
0

#8 User is offline   Alex Icon

  • Author
  • Icon
  • Group: Administrators
  • Posts: 55
  • Joined: 17-January 10

Posted 17 February 2010 - 01:53 PM

Done!
Very handy suggestion ;)

http://bitbucket.org...t/c9744fcb1b96/
0

#9 User is offline   Far Icon

  • Member
  • PipPip
  • Group: Members
  • Posts: 24
  • Joined: 04-February 10

Posted 17 February 2010 - 01:57 PM

Also i came across another problem:

In models i have function like this:

<?php public function getLanguageEntry($lang = null, $controller = false, $name = false)
	{
		if(!$lang) return false;
		$where = 'lang = ? ';
		if($controller) $where .= 'AND controller = ? ';
		if($name) $where .= 'AND name = ? ';
		
		$data = DB::getInstance()->query('SELECT lang, controller, name, value
										   FROM %tp%language_data 
										   WHERE '.$where.';', $lang, $controller, $name)->fetchAll();
		
		$rs = array();
		if($data)
		{
			foreach($data as $item)
			{
				if($lang && $controller && $name) $rs = $item;
				else $rs[$item['controller']][$item['name']] = $item['value'];
			}
		} 
		else 
		{
			return false;
		}
		return $rs;
	}


If there is one argument passed to query, i get error "Invalid parameter number: number of bound variables does not match number of tokens";
Probably it's not a popular way to make queries, but to make query function omit the arguments, not required in sql, change this code in class.db.php:

<?php
public function query($sql) {
        if(!$this->_connected) $this->connect();

        $sql = str_replace('%tp%', $this->tp(), $sql);
        
        $stmt = $this->db->prepare($sql);
        $stmt->setFetchMode(PDO::FETCH_ASSOC);
        $args = array();
        if(func_num_args() > 1) {
            $args = func_get_args();
            array_shift($args);
            $i = 1;
            foreach($args as $argument) {
                $stmt->bindValue($i, $argument);
                $i++;
            }
        }


to this one:

<?php
public function query($sql) {
        if(!$this->_connected) $this->connect();

        $sql = str_replace('%tp%', $this->tp(), $sql);
		$sql_args = substr_count($sql, '?');
		
        $stmt = $this->db->prepare($sql);
        $stmt->setFetchMode(PDO::FETCH_ASSOC);
        $args = array();
        if(func_num_args() > 1) {
            $args = func_get_args();
            array_shift($args);
			if(count($args) > $sql_args && $sql_args > 0)
			{
				for($i = 1; $i <= $sql_args; $i++) 
				{
					$stmt->bindValue($i, $args[$i-1]);
				}
			} 
			else
			{
				$i = 1;
				foreach($args as $argument) 
				{
					$stmt->bindValue($i, $argument);
					$i++;
				}
			}
        }


And the arguments those are not mentioned in sql will be omitted and will not raise error.
0

#10 User is offline   Far Icon

  • Member
  • PipPip
  • Group: Members
  • Posts: 24
  • Joined: 04-February 10

Posted 17 February 2010 - 02:18 PM

Oh, i think that is no the best way to avoid errors. But probably it will help someone.
0

#11 User is offline   Alex Icon

  • Author
  • Icon
  • Group: Administrators
  • Posts: 55
  • Joined: 17-January 10

Posted 17 February 2010 - 02:52 PM

Even better idea, suggested by gwk is to use allow the passing of arguments in an array, e.g.:

    $where = 'lang = ? ';
    $args = array($lang);
    if($controller) {
        $where .= 'AND controller = ? ';
        $args[] = $controller;
    }
    if($name) {
        $where .= 'AND name = ? ';
        $args[] = $name;
    }
    $data = DB::getInstance()->query('SELECT lang, controller, name, value FROM %tp%language_data  WHERE '.$where, $args)->fetchAll();


Silently failing when there are an incorrect number of arguments or counting question marks is not the best way. Especially counting question marks can cause problems.

Which... reminds me that %tp% is probably not the best abbreviation of table_prefix (think of LIKE queries). Maybe --tp-- or ;tp; are better...

http://bitbucket.org...t/c224adf5cc76/
0

Page 1 of 1
  • You cannot start a new topic
  • You cannot reply to this topic

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users