Singleton with multiple databases - php

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.

Related

Constant not defined error

I am trying to define my own function in one of the php login libraries.
There some constants are defined which works perfectly fine until I call the databaseConnection() function on my own.
private function databaseConnection()
{
// if connection already exists
if ($this->db_connection != null) {
return true;
} else {
try {
// Generate a database connection, using the PDO connector
// #see http://net.tutsplus.com/tutorials/php/why-you-should-be-using-phps-pdo-for-database-access/
// Also important: We include the charset, as leaving it out seems to be a security issue:
// #see http://wiki.hashphp.org/PDO_Tutorial_for_MySQL_Developers#Connecting_to_MySQL says:
// "Adding the charset to the DSN is very important for security reasons,
// most examples you'll see around leave it out. MAKE SURE TO INCLUDE THE CHARSET!"
$this->db_connection = new PDO('mysql:host='. DB_HOST .';dbname='. DB_NAME . ';charset=utf8', DB_USER, DB_PASS);
return true;
} catch (PDOException $e) {
$this->errors[] = MESSAGE_DATABASE_ERROR . $e->getMessage();
}
}
// default return
return false;
}
this is the function I defined..
private function updateLastLoginDate($user_name)
{
if($this->databaseConnection())
{
$sth = $this->db_connection->prepare('UPDATE users '
. 'SET last_login_date = UTC_TIMESTAMP()'
. 'WHERE user_email =:user_name OR user_name =:user_name');
$sth->execute(array(':user_name' => $user_name));
}
}
when I call this function the error says that all the constants in the databaseConntion functions are not defined.. but they work perfectly fine other than the call I make to my defined function...
I am not good with pdo though..

Database php class causes fatal error

I'm trying to create a php class that will connect me to the database more easily, but it seems to not work.
This is my function from the class:
class Database {
// host, username, password and db are defined
public function GetMySqlConnection()
{
return mysqli_connect($this->host, $this->username, $this->password, $this->db);
}
}
And I use it in another page like this:
$db = Database()->GetMySqlConnection();
$result = $db->query("SELECT name FROM users WHERE name='" . mysqli_real_escape_string($_POST['agname']) . "' AND password='" . mysqli_real_escape_string($_POST['agpass']) . "'");
For some reason it causes some error I can't manage to find. What am I doing wrong?
Database is class here, so you cannot use $db = Database()->GetMySqlConnection();
You should use:
$db = new Database();
$db = $db->GetMySqlConnection();
instead

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.

do i need to instanciate this class multiple times?

When a user clicks a button, my script instantiates this class. so if there are fifty users on my website that click this button then there will be 50 of these classes that have been instantiated. is this the right thing to do? or do i need to check if this class has already been instantiated before and not do anything if it has been.
I connect to my db here.
there is more to this class, this is just a snippet.
class Database{
private $host = "localhost";
private $user = "rt";
private $pass = "";
private $dbname = "db";
public function __construct(){
// Set DSN
$dsn = 'mysql:host=' . $this->host . ';dbname=' . $this->dbname;
// Set options
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
// Create a new PDO instanace
try{
$this->dbh = new PDO($dsn, $this->user, $this->pass, $options);
}
// Catch any errors
catch(PDOException $e){
$this->error = $e->getMessage();
}
}
}
is this the right thing to do?
Yes.
That's just how PHP works. 50 PHP instances runs, 50 connects to database established, etc.
do i need to check if this class has already been instantiated before
You can, but it would be pretty useless, as other user's click would be handled by a completely separate PHP process and you will have no access to it anyway.
Here is slightly improved version of constructor
public function __construct(){
// Set DSN
$dsn = 'mysql:host=' . $this->host . ';dbname=' . $this->dbname;
// Set options
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
// Create a new PDO instanace
$this->dbh = new PDO($dsn, $this->user, $this->pass, $options);
}
Your PHP script needs to be run everytime a user loads the page, there is no way around instantiating a class everytime someone loads the page.
This is an inherent property of HTTP-based communications: statelessness; and PHP is run off the HTTP protocol, so it is subjected to it's limitations.
You can simulate states by using databases and sessions (which are basically just files storing information using a unique key as filename), but the PHP script itself will have to be subjected to HTTP's limitations of not having a state.
Hence, there is no way to keep your class in memory over several different connections.
You need to instantiate the class everytime you use different credentials (parameters). So, if every users creates new connection with different credentinals, then you need to instantiate everytime. Otherwise, you have to do proper checks and continue using same connection

Multiple Databases using PDO

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.

Categories