Multiple Databases using PDO - php

I'm pretty new to using PDO and I would like to set it up so I can have multiple databases as and when I need them. So I've created a function that allows you to pass a database name to be used as and when.
It does work to a certain extent, as in it selects the database you pass in but even if the database is omitted or incorrect it still allows you to select tables and rows from a database which seems to be selected at random based on the MySQL user.
This isn't a major issue I suppose but I would like to get it to where it won't select any data unless a database has been passed to through my function.
My code is below and I would appreciate your thoughts on how I may better approach this. Thanks.
index.php
require 'app/cream.php';
try {
$db = new Cream_Model();
$db = $db->selectDb( 'cream' );
$data = $db->query('SELECT * FROM users');
foreach( $data as $row ) {
print_r( $row );
}
} catch( PDOException $e ) {
echo 'An error has occurrred: ' . $e->getMessage() . '<br />';
}
Model.php
class Model {
public $connection;
public function connect() {
try {
$connection = new PDO( DB_DSN . ':' . DB_HOST, DB_USERNAME, DB_PASSWORD, array( PDO::ATTR_PERSISTENT => true ) );
$connection->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$connection->setAttribute( PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC );
} catch( PDOException $e ) {
echo 'An error has occurred: ' . $e->getMessage() . '<br />';
die();
}
return $connection;
}
}
Cream_Model.php
class Cream_Model extends Model {
public $conn;
public function selectDb( $db ) {
try {
$conn = $this->connect();
$conn->exec( "USE $db" );
} catch( PDOException $e ) {
echo 'An error has occurred: ' . $e->getMessage() . '<br />';
}
return $conn;
}
}

For PDO, you should NOT exec USE dbname directly.
I think what is happening that you have multiple instances of PHP script, and when each one executes USE dbname without PDO being aware of it happening, and this causes whole mess.
Instead, you should specify dbname in your PDO connection string like 'mysql:host=localhost;dbname=testdb'. That means you cannot really switch between databases after creating your Model class. You should know your database name upfront and use it in Model constructor.
Read more in PDO documentation.

Related

Problems connecting to MySQL using PDO

I've been trying to convert my application from using the depreciated mysql syntax to PDO for connecting to the database and performing queries, and it's been a pain so far.
Right now I have a class, db_functions.php, in which I'm trying to create a PDO connection to the database, as well as perform all the CRUD operations inside of.
Here is a sampling of the code:
db_functions.php
<?php
class DB_Functions {
private $db;
// constructor
function __construct() {
require_once 'config.php';
// connecting to mysql
try {
$this->$db = new PDO('mysql:host=localhost;dbname=gcm', DB_USER, DB_PASSWORD);
}
catch (PDOException $e) {
$output = 'Unable to connect to database server.' .
$e->getMessage();
exit();
}
}
// destructor
function __destruct() {
}
public function getAllUsers() {
try {
$sql = "select * FROM gcm_users";
//$result = mysql_query("select * FROM gcm_users");
$result = $this->$db->query($sql);
return $result;
}
catch (PDOException $e) {
$error = 'Error getting all users: ' . $e->getMessage();
}
}
With that code, i'm getting the following error:
Notice: Undefined variable: db in C:\xampp\htdocs\gcm\db_functions.php on line 12
Fatal error: Cannot access empty property in C:\xampp\htdocs\gcm\db_functions.php on line 12
Line 12 is:
$this->$db = new PDO('mysql:host=localhost;dbname=gcm', DB_USER, DB_PASSWORD);
How could I fix this so that I have a proper instance of a PDO connection to my database that I can use to create queries in other methods in db_functions, such as getAllUsers()
I used the answer found at How do I create a connection class with dependency injection and interfaces? to no avail.
TYPO
//$this->$db =
$this->db =
same here
//$this->$db->query($sql);
$this->db->query($sql);
and i also would use 127.0.0.1 instead of localhost to improve the performance otherwise making a connection will take very long... a couple of seconds just for connection...

PDOException not triggering / possible scope related?

In anticipation of mysql_query being deprecated PHP 5.5.0, I have been working on a class to handle all my DB queries :
class DataBaseClass {
//.....some other function and variables declared here....
function GetConnection() {
try {
$this->conn = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASS);
$this->conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
}
catch(PDOException $e) {
echo $e->getMessage();
}
return $this->conn;
}
function Query($str_sql, $arr_parameters = array()) {
try {
$this->str_mysql_error = $this->int_num_rows = $this->int_num_affected_rows = $this->int_mysql_insert_id = '';
if (count($arr_parameters) > 0) {
$obj_result = $this->conn->prepare($str_sql);
$obj_result->execute($arr_parameters);
} else {
$obj_result = $this->conn->query($str_sql);
}
}
catch(PDOException $e) {
$this->str_mysql_error = $e->getMessage() . $str_sql;
}
}
}
Then I have another class to create new user:
class AddNewUser {
//.....some other function and variables declared here....
function InsertUser() {
$str_sql = "INSERT INTO (uname, name, email, pass, user_regdate, theme) VALUES )";
$_SESSION['db_connection']->Query($str_sql, '');
}
}
Now on my main user creation page I have :
$_SESSION['db_connection'] = new DataBaseClass;
//Reason I used $_SESSION to store my DB object, is so that it can be accessible everywhere.
//Did not want to use "global" everywhere. Not sure if this is he best way???
$cls_new_user = new AddNewUser ();
$cls_new_user->InsertUser(); //Does not raise PDOExecption although SQL cleary wrong inside this method
if ( $_SESSION['db_connection']->str_mysql_error) {
//show error in error div
}
$str_sql = "SELECT some wrong SQL statment";
$_SESSION['db_connection']->Query($str_sql); // This does raise PDOExecption
if ( $_SESSION['db_connection']->str_mysql_error) {
//show error in error div
}
I'm not sure why the DB class function "Query" would not raise an exception on clearly wrong SQL when called from another class. But same function called from main page code (not inside function / class) raises and exception error.
Also, the "InsertUser" function does not execute / insert anything into DB even if SQL correct.
Could it be scope related, or the fact that I'm trying to enforce global scope of my DB object by putting it in $_SESSION ??
Am I going about this the wrong way? Reason for going class route to encapsulate all my DB calls was to avoid any deprecation issues in future - only having to update class.
Make your function this way.
function Query($str_sql, $arr_parameters = array()) {
$stmt = $this->conn->prepare($str_sql);
$stmt->execute($arr_parameters);
}
I am pretty sure that exception would be thrown
The only issue can be with catching exceptions, not throwing. And it could be caused by Namespace, not scope. To be certain, you can always prepend all PDO calls with a slash:
\PDO::FETCH_ASSOC
\PDOException
etc.

PHP, MySQL, FastCGI - best way to handle many queries

I am trying to figure out the best way to handle db communication in PHP, MySQL setup through FastCGI and usig PHP-FPM; This is for a relatively heavy use site where there is anywhere from 100 to 1,000 SQL queries a second so I would like to make things as efficient as possible.
I am rewriting parts of the website and in the new code I am utilizing PDO and have the below class to handle DB queries and connections by doing database::insertEmployee($name, $SIN, $DOB, $position). My concern is that with every query a new PDO connection is established. Should I be trying to set up a persistent connection???
class database
{
protected $dbh;
protected static $instance;
private function __construct()
{
try {
// building data source name from config
$dsn = 'mysql:=' . DB_Config::read('db.host') .
';dbname=' . DB_Config::read('db.name');
$user = DB_Config::read('db.user');
$password = DB_Config::read('db.password');
$this->dbh = new PDO($dsn, $user, $password);
$this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
//#TODO-KP: log and alert
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
}
public static function getInstance()
{
if (!isset(self::$instance)) {
$object = __CLASS__;
self::$instance = new $object;
}
return self::$instance;
}
public static function insertEmployee($name, $position, $SIN, $DOB)
{
$dbi = self::getInstance();
try {
$sthEmployee = $dbi->dbh->prepare('INSERT INTO employees SET
name = :name
, position = :position
, SIN = :SIN
, DOB = :DOB'
);
$sthEmployee->bindParam(':name', $name);
$sthEmployee->bindParam(':position', $position);
$sthEmployee->bindParam(':SIN', $SIN);
$sthEmployee->bindParam(':DOB', date('Y-m-d G:i:s', $DOB));
return $sthEmployee->execute();
} catch (PDOException $e) {
//#FIXME-KP: log and alert
print "Error!: " . $e->getMessage() . "-- name [$name]";
return '';
}
}
}
Any thoughts on most efficient approach would be very, very appreciated!
Kathryn.
My concern is that with every query a new PDO connection is established.
Well, verify your concern, because I would say this is likely not the case.
For performance reasons, take care you're using the mysql native driver under the hood as this allows extended metrics of the interaction from PHP with the database.
Also get a professional support plan from Oracle for Mysql, they have nice monitoring tools and very good support that should help you to get your database and PHP code ready for the traffic to handle.

Singleton with multiple databases

example app, having employee information and being accessed by different applications like payroll and pos. i have employee data in one database, payroll data and pos in separate databases each.
i have a database connection class like below, so everytime i want to get a connection to a db i just do $conn = Database::getInstance(db1).
works great, but is super slow basically. makes the app run really slow. Any tips on why that is so or better yet alternative ideas to do this?
any help will be greatly appreciated
<?php
class Database {
private $db;
static $db_type;
static $_instance;
private function __construct($db){
switch($db) {
case "db1":
try{
$this->db = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASSWORD);
}
catch(PDOException $e){
print "Error!: " . $e->getMessage() . "<br />";
die();
}
break;
case "db2":
try{
$this->db = new PDO("mysql:host=" . DB_HOST_2 . ";dbname=" . DB_NAME_2, DB_USER_2, DB_PASSWORD_2);
}
catch(PDOException $e){
print "Error!: " . $e->getMessage() . "<br />";
die();
}
break;
}
self::$db_type = $db;
}
private function __clone(){}
static function getInstance($db_type){
if(!(self::$_instance) || $db != self::$db_type){
self::$_instance = new self($db_type);
}
return self::$_instance;
}
}
?>
With this design. If you change databases then it destroys the connection to the previous database.
Make separate objects for each connection then switch between the connection objects.
Also, this is not thread safe for the same reason. If multiple functions are hitting this as the same time, one can disconnect the other before its done loading.
You really should just create a new connection object for each function and not share it between functions or other objects.
Do not create new object constantly. What is happening is that everytime you request another database type, you are recreating it via the new keyword (although hard to confirm without seeing code that uses this).
$_instance is a static member, so you are constantly overwriting it when you change database type. so is $db_type for that matter
While this is overkill for what you are doing (why not just have two variables for each DB?), you could try something more like this:
<?php
class Database {
private $db;
static $db_types;
private function __construct($db){
switch($db) {
case "db1":
try{
$db_types[$db] = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASSWORD);
}
catch(PDOException $e){
print "Error!: " . $e->getMessage() . "<br />";
die();
}
break;
case "db2":
try{
$db_types[$db] = new PDO("mysql:host=" . DB_HOST_2 . ";dbname=" . DB_NAME_2, DB_USER_2, DB_PASSWORD_2);
}
catch(PDOException $e){
print "Error!: " . $e->getMessage() . "<br />";
die();
}
break;
}
}
private function __clone(){}
static function getInstance($db_type){
if(!inarray($db_types[$db_type]){
$db_types[$db_type] = new self($db_type);
}
return $db_types[$db_type];
}
}
?>
NOTE: syntax is likely off. Just wanted to demonstrate the pattern.
I dont see why that would be making things slow other than the fact that youre constantly switching conncections. The only thing i can suggest here is to allow multiple connections instead of switching them:
class Database {
protected static $connections;
protected $activeConnections = array();
protected static $instance;
protected function __construct() {
}
public static loadConnections(array $connections) {
self::$connections = $connections;
}
public function getConnection($name)
{
if(!isset($this->activeConnections[$name]) {
if(!isset(self::$connections[$name]) {
throw new Exception('Connection "' . $name . '" is not configured.');
}
$this->activeConnections[$name] = new PDO(
self::$connections[$name]['dsn'],
self::$connections[$name]['username'],
self::$connections[$name]['password']
);
}
return $this->activeConnections[$name];
}
}
// usage
Database::loadConnections(array(
'db1' => array(
'dsn' => "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME,
'user' => DB_USER,
'password' => DB_PASSWORD,
),
'db2' => array(
'dsn' => "mysql:host=" . DB_HOST2 . ";dbname=" . DB_NAME2,
'user' => DB_USER2,
'password' => DB_PASSWORD2,
)));
$conn1 = Database::getInstance()->getConnection('db1');
$conn2 = Database::getInstance()->getConnection('db2');
Using something like this you can actually manange several open connections at a time, and they are lazy loaded - meaning you dont actually instantiate a PDO connection until you ask for it with Database::getConnection Likewise you can inject additional DSN's and credentials in at anytime. Personally i would load thes form configuration right on to the class instead of hard coding them with constants int he class. then you could so something like:
// gives us an array
$config = Config::load('path/to/db/config.yml');
Database::loadConnections($config);
How about changing it to also use lazy load. You do not need to connect to the databases in the contractor. Only connect when the database is first required. That way, if the page only uses one of the connection, it does not need to wait for the other databases.
Check the value of DB_HOST and DB_HOST_2. Previously I've found MySQL extremely slow to connect using "127.0.0.1", but connecting instantly using "localhost".
It depends on how your server is setup, but just thought it might help.

PDO: MySQL server has gone away

I have a script that does a lot of legwork nightly.
It uses a PDO prepared statement that executes in a loop.
The first few are running fine, but then I get to a point where they all fail with the error:
"MySQL server has gone away".
We run MySQL 5.0.77.
PHP Version 5.2.12
The rest of the site runs fine.
Most likely you sent a packet to the server that is longer than the maximum allowed packet.
When you try to insert a BLOB that exceeds your server's maximum packet size, even on a local server you will see the following error message on clientside:
MySQL server has gone away
And the following error message in the server log: (if error logging is enabled)
Error 1153 Got a packet bigger than 'max_allowed_packet' bytes
To fix this, you need to decide what is the size of the largest BLOB that you will ever insert, and set max_allowed_packet in my.ini accordingly, for example:
[mysqld]
...
max_allowed_packet = 200M
...
The B.5.2.9. MySQL server has gone away section of the MySQL manual has a list of possible causes for this error.
Maybe you are in one of those situations ? -- Especially considering you are running a long operation, the point about wait_timeout might be interesting...
I had the same problem where the hosting server administration kills connection if there is a timeout.
Since I have used the query in major part I wrote a code which instead of using PDO class we can include the below class and replace the classname to "ConnectionManagerPDO". I just wrapped the PDO class.
final class ConnectionManagerPDO
{
private $dsn;
private $username;
private $passwd;
private $options;
private $db;
private $shouldReconnect;
const RETRY_ATTEMPTS = 3;
public function __construct($dsn, $username, $passwd, $options = array())
{
$this->dsn = $dsn;
$this->username = $username;
$this->passwd = $passwd;
$this->options = $options;
$this->shouldReconnect = true;
try {
$this->connect();
} catch (PDOException $e) {
throw $e;
}
}
/**
* #param $method
* #param $args
* #return mixed
* #throws Exception
* #throws PDOException
*/
public function __call($method, $args)
{
$has_gone_away = false;
$retry_attempt = 0;
try_again:
try {
if (is_callable(array($this->db, $method))) {
return call_user_func_array(array($this->db, $method), $args);
} else {
trigger_error("Call to undefined method '{$method}'");
/*
* or
*
* throw new Exception("Call to undefined method.");
*
*/
}
} catch (\PDOException $e) {
$exception_message = $e->getMessage();
if (
($this->shouldReconnect)
&& strpos($exception_message, 'server has gone away') !== false
&& $retry_attempt <= self::RETRY_ATTEMPTS
) {
$has_gone_away = true;
} else {
/*
* What are you going to do with it... Throw it back.. FIRE IN THE HOLE
*/
throw $e;
}
}
if ($has_gone_away) {
$retry_attempt++;
$this->reconnect();
goto try_again;
}
}
/**
* Connects to DB
*/
private function connect()
{
$this->db = new PDO($this->dsn, $this->username, $this->passwd, $this->options);
/*
* I am manually setting to catch error as exception so that the connection lost can be handled.
*/
$this->db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
}
/**
* Reconnects to DB
*/
private function reconnect()
{
$this->db = null;
$this->connect();
}
}
Then use can start using the above class as you do in PDO.
try {
$db = new ConnectionManagerPDO("mysql:host=localhost;dbname=dummy_test", "root", "");
$query = $db->query("select * from test");
$query->setFetchMode(PDO::FETCH_ASSOC);
}
catch(PDOException $e){
/*
handle the exception throw in ConnectionManagerPDO
*/
}
Try using PDO::setAttribute(PDO::ATTR_EMULATE_PREPARES, true) on your pod instance(s). Dont know that it will help but with no log data its all i got.
It's likely that either your connection has been killed (e.g. by wait_timeout or another thread issuing a KILL command), the server has crashed or you've violated the mysql protocol in some way.
The latter is likely to be a bug in PDO, which is extremely likely if you're using server-side prepared statements or multi-results (hint: Don't)
A server crash will need to be investigated; look at the server logs.
If you still don't know what's going on, use a network packet dumper (e.g. tcpdump) to dump out the contents of the connection.
You can also enable the general query log - but do it very carefully in production.
Nathan H, below is php class for pdo reconnection + code usage sample.
Screenshot is attached.
<?php
# set errors reporting level
error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);
# set pdo connection
include('db.connection.pdo.php');
/* # this is "db.connection.pdo.php" content
define('DB_HOST', 'localhost');
define('DB_NAME', '');
define('DB_USER', '');
define('DB_PWD', '');
define('DB_PREFIX', '');
define('DB_SHOW_ERRORS', 1);
# connect to db
try {
$dbh = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME, DB_USER, DB_PWD);
$dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, TRUE);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $e) {
# echo $e->getMessage()."<br />";
# exit;
exit("Site is temporary unavailable."); #
}
*/
$reconnection = new PDOReconnection($dbh);
$reconnection->getTimeout();
echo $dbh->query('select 1')->fetchColumn();
echo PHP_EOL;
echo 'sleep 10 seconds..'.PHP_EOL;
sleep(10);
$dbh = $reconnection->checkConnection();
echo $dbh->query('select 1')->fetchColumn();
echo PHP_EOL;
echo 'sleep 35 seconds..'.PHP_EOL;
sleep(35);
$dbh = $reconnection->checkConnection();
echo $dbh->query('select 1')->fetchColumn();
echo PHP_EOL;
echo 'sleep 55 seconds..'.PHP_EOL;
sleep(55);
$dbh = $reconnection->checkConnection();
echo $dbh->query('select 1')->fetchColumn();
echo PHP_EOL;
echo 'sleep 300 seconds..'.PHP_EOL;
sleep(300);
$dbh = $reconnection->checkConnection();
echo $dbh->query('select 1')->fetchColumn();
echo PHP_EOL;
# *************************************************************************************************
# Class for PDO reconnection
class PDOReconnection
{
private $dbh;
# constructor
public function __construct($dbh)
{
$this->dbh = $dbh;
}
# *************************************************************************************************
# get mysql variable "wait_timeout" value
public function getTimeout()
{
$timeout = $this->dbh->query('show variables like "wait_timeout"')->fetch(); # print_r($timeout);
echo '========================'.PHP_EOL.'mysql variable "wait_timeout": '.$timeout['Value'].' seconds.'.PHP_EOL.'========================'.PHP_EOL;
}
# *************************************************************************************************
# check mysql connection
public function checkConnection()
{
try {
$this->dbh->query('select 1')->fetchColumn();
echo 'old connection works..'.PHP_EOL.'========================'.PHP_EOL;
} catch (PDOException $Exception) {
# echo 'there is no connection.'.PHP_EOL;
$this->dbh = $this->reconnect();
echo 'connection was lost, reconnect..'.PHP_EOL.'========================'.PHP_EOL;
}
return $this->dbh;
}
# *************************************************************************************************
# reconnect to mysql
public function reconnect()
{
$dbh = new PDO('mysql:host=' . DB_HOST . ';dbname=' . DB_NAME, DB_USER, DB_PWD);
$dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, TRUE);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $dbh;
}
}
# /Class for PDO reconnection
# *************************************************************************************************
I got this same error this morning after changing my DB properties in Laravel. I'd commented out the old settings and pasted in new ones. The problem was that the new settings where missing the DB_CONNECTION variable:
DB_CONNECTION=pgsql
Obviously you need to add whatever connection type you are using: sqlite, mysql, ...
I had the exact same problem.
I resolved this issue by doing unset on the PDO object instead of seting it to NULL.
For example:
function connectdb($dsn,$username,$password,$driver_options) {
try {
$dbh = new PDO($dsn,$username,$password,$driver_options);
return $dbh;
}
catch(PDOException $e)
{
print "DB Error: ".$e->getMessage()."<br />";
die();
}
}
function closedb(&$dbh) {
unset($dbh); // use this line instead of $dbh = NULL;
}
Also, it is highly recommended to unset all your PDO objects. That includes variables that contain prepared statements.
$pdo = new PDO(
$dsn,
$config['username'],
$config['password'],
array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
)
);
try this. It may work

Categories