A legacy website is exhibiting unexpected behavior with it's database connections. The application connects to several MySQL databases on the same server and the original developer created a "singleton" class that holds connection objects for each of them.
Lately we have been encountering a strange behavior with the class: when a connection to www is created after creating extra, getting the instance of extra returns a connection that has the correct parameters when viewed with var_dump() but is actually connected to the www database.
What could cause this? The code has worked before at some stage. Creating a new connection on each call to getInstance() fixes the problem but I'd like to solve this the right way if possible.
<?php
class DBConnection
{
private static $default;
private static $extra;
private static $intra;
private static $www;
public static function getInstance($dbname = "default")
{
global $db; // This is an array containing database connection parameters
if(!($dbname == "default" || $dbname == "extra" || $dbname == "www" || $dbname == "intra"))
{
$dbname = "default";
}
if (empty(self::$$dbname)) // Making this pass every time fixes the problem
{
try
{
self::$$dbname = ADONewConnection('mysqlt');
if(isset($db[$dbname]))
{
self::$$dbname->connect(DBHOSTNAME, $db[$dbname]["dbusername"], $db[$dbname]["dbpassword"], $db[$dbname]["dbname"]);
}
else
{
// fallback
self::$$dbname->connect(DBHOSTNAME, DBUSERNAME, DBPASSWORD, DBNAME);
}
self::$$dbname->SetFetchMode(ADODB_FETCH_ASSOC);
self::$$dbname->execute("SET NAMES utf8");
return self::$$dbname;
}
catch(Exception $e)
{
exit("DB connection failed");
}
}
else
{
return self::$$dbname;
}
}
}
Here's a simplified example of the class misbehaving:
$cn = DBConnection::getInstance("extra");
$cn->debug = true;
$rs = $cn->execute("SELECT * FROM messages WHERE id = ".$this->id);
The last line fails with the error message "Table www.messages does not exist".
1: You don't need to establish connection in getInstance().
getInstance must not do anything, but return instance of DB class.
2: when you do self::$$dbname->connect(, if (empty(self::$$dbname)) will return you false next time you call it.
Singleton is described here: http://en.wikipedia.org/wiki/Singleton_pattern
What you have - it's just a static method.
Related
I'm coming from Java programming and I'm trying to apply my knowledge in OOP style programming in PHP.
So, I tried to create a utility class to connect to database just like how I usually do it in Java where I create a static method to get the database connection.
However, after spending hours I still can't fix the error.
DBHelper.php
<?php
class DBHelper
{
protected $db_name = 'myDb';
protected $db_user = 'root';
protected $db_pass = '';
protected $db_host = 'localhost';
public function obtainConnection()
{
$mysqli_instance = new mysqli($this->db_host, $this->db_user, $this->db_pass, $this->db_name);
/* check connection */
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
return $mysqli_instance;
}
}
?>
There are no errors in this file
Then I tried to use it on another file called login.php
login.php
<?php
if (isset($_POST['submit'])) {
include "/DBUtility/DBHelper.php";
$username = $_POST['username']; //s means string
$password = $_POST['password']; // s means string
echo "<br/> Username value: " . $username;
echo "<br />Password value: " . $password;
}
if (empty($username) || empty($password) ) {
echo "Fill out the fields!";
} else {
//PREPARE THE PreparedStatment or Stored Procedure
$dbHelper = new DBHelper();
$connection = $dbHelper->obtainConnection();
$preparedStatement = $connection->prepare('CALL getUserRoleByLogin(?, ?)'); //getUserRoleByLogin() is the name of stored proc in mysql db
$preparedStatement->bind_param('ss', $username, $password); //assign arguments to ? ?
$preparedStatement->execute();//execute the stored procedure. This will return a result
$userRole = $preparedStatement->store_result();
$countOfRows = $preparedStatement->num_rows;
?>
I read every related question about the Fatal error: Cannot redeclare class CLASSNAME error. I tried following the instructions given by many which is to use require_once("DBHelper.php"); instead of include("DBHelper.php");
but still can't get rid of the error.
I tried making the obtainConnection() static and called it via DBHelper::obtainConnection(); but with no luck. Same error message.
I get the error on opening brace of class DBHelper {
I hope you can help me with this.
Thank you.
A couple tips you should do when doing OOP in PHP:
1) I would maybe rethink about not baking the db credentials into your class directly, it makes it harder/more cumbersome to modify them via UI if you wanted to implement a UI control mechanism down the line. Instead, try making a define or maybe a json pref file or a dynamically-created php file that contains an array, something like that. I will do a define because it's the easiest to demonstrate:
/config.php
# You can create a series of defines including the database
define('DB_HOST','localhost');
define('DB_NAME','dbname');
define('DB_USER','root');
define('DB_PASS','dbpassword');
# To maximize compatibility it's helpful to define fwd/back slash
define('DS',DIRECTORY_SEPARATOR);
# It is helpful to create path defines for easy file inclusion
define('ROOT_DIR',__DIR__);
define('CLASSES',ROOT_DIR.DS.'classes');
# Start session
session_start();
2) Create a class autoloader in the config.php file which then allows you to not have to manually include/require classes in pages. It will automatically include them:
spl_autoload_register(function($class) {
if(class_exists($class))
return;
# This will turn a namespace/class into a path so should turn:
# $db = new \DBUtility\DBHelper();
# into:
# /var/www/domain/httpdocs/classes/DBUtility/DBHelper.php
$path = str_replace(DS.DS,DS,CLASSES.DS.str_replace('\\',DS,$class).'.php');
# If the class file is located in the class folder, it will include it
if(is_file($path))
include_once($path);
});
3) I am going to create a static connection so you don't create a new connection every time (also I will use PDO):
/classes/DBUtility/DBHelper.php
<?php
namespace DBUtility;
class DBHelper
{
protected $query;
private static $con;
public function connection()
{
# This will send back the connection without making a new one
if(self::$con instanceof \PDO)
return self::$con;
# I like to catch any pdo exceptions on connection, just incase.
try {
# Assign the connection
self::$con = new \PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME,DB_USER,DB_PASS);
}
catch(\PDOException $e) {
# Here you can just die with a more user-friendly error.
# It would be helpful to save the actual error to a log file
$msg = $e->getMessage();
# I would put your log outside the root or in a protected folder
$txt = realpath(ROOT_DIR.DS.'..').DS.'errors'.DS.'sql.txt';
# Make a directory if none set
if(!is_dir(pathinfo($txt,PATHINFO_DIRNAME))) {
# Make the directory
if(mkdir(pathinfo($txt,PATHINFO_DIRNAME),0744,true)) {
# Save to log file
file_put_contents($txt,$msg.PHP_EOL);
}
}
else {
# Save to log file
file_put_contents($txt,$msg.PHP_EOL);
}
die("Site is under maintenance.");
}
}
# It would be helpful to create a query that will bind and not bind
public function query($sql,$bind = false)
{
if(is_array($bind)) {
foreach($bind as $key => $value) {
$sKey = ":{$key}";
$bindArr[$sKey] = $value;
}
$this->query = $this->connection()->prepare($sql);
$this->query->execute($bindArr);
}
else {
# The second "query" on this is the method from PDO, not the
# "query" method from this class
$this->query = $this->connection()->query($sql);
}
return $this;
}
public function getResults()
{
if(empty($this->query))
return false;
while($result = $this->query->fetch(\PDO::FETCH_ASSOC)) {
$row[] = $result;
}
return (isset($row))? $row : false;
}
}
# If your page ends with a php tag, you should just remove it. It will
# protect against empty spaces that may cause "header already sent" errors
3a) I use something similar to this to autoload functions:
/classes/Helper.php
class Helper
{
public static function autoload($function)
{
if(function_exists($function))
return;
$path = ROOT_DIR.DS.'functions'.DS.$function.'.php';
if(is_file($path))
include_once($path);
}
}
4) Create useful/reusable functions or class/methods
/functions/getUserRole.php
function getUserRole($username,$password,\DBUtility\DBHelper $DBHelper)
{
return $DBHelper->query('CALL getUserRoleByLogin(:0, :1)',array($username,$password))->getResults();
}
/index.php
# Include the config file
require_once(__DIR__.DIRECTORY_SEPARATOR.'config.php');
if (isset($_POST['submit'])) {
# No need for this line ->> include "/DBUtility/DBHelper.php";
# Use trim to remove empty spaces on the left and right
$username = trim($_POST['username']);
$password = trim($_POST['password']);
}
if (empty($username) || empty($password) ) {
echo "Fill out the fields!";
} else {
# User our function autoloader to include this function
Helper::autoload('getUserRole');
# Use the function and inject the DB class
$userRoles = getUserRole($username,$password,new \DBUtility\DBHelper());
$count = count($userRoles);
echo "Count: {$count}";
echo '<pre>';
print_r($userRoles);
echo '</pre>';
}
So the following code is in its infantile stages, and has a long way to go - but I am working on a database platform that is aware of multiple databases ( slaves, etc ), that is meant to attempt to open a connection to one, and upon failure, continue on to another. The relevant methods in the 'DatabaseConnection' class are as follows:
class DatabaseConnection extends mysqli
{
public function __construct($ConnectAutomatically=false,$Name="database",$Host="127.0.0.1",$Username="root",$Password="",$Catalog="",$Port=3306)
{
$this->CurrentHost = $_SERVER['SERVER_NAME'];
$this->Name = $Name;
$this->Host = $Host;
$this->Username = $Username;
$this->Password = $Password;
$this->Port = $Port;
$this->Catalog = $Catalog;
if( $ConnectAutomatically )
parent::__construct($this->Host, $this->Username, $this->Password, $this->Catalog, $this->Port);
else
parent::init();
return;
}
public function Open()
{
try
{
if(!parent::real_connect($this->Host,$this->Username,$this->Password,$this->Catalog,$this->Port))
{
return false;
}
else
return true;
}
catch( Exception $e )
{
return false;
}
}
}
The idea is that the database could be queried at any time, without having been initialized - therefore, the Query method checks for connectivity, and upon a lack thereof, runs the 'Open' method.
I have intentionally put a bad character in the password to force it to fail to connect, to see if it would connect to the second server in the list - but on the 'parent::real_connect' line, I am getting the obvious response that I cannot connect due to an invalid password.
I am wondering if there is a different approach I can take in this 'Open' method to NOT stop execution upon failure in the real_connect operation. I've taken a few swings at this and come up short, so I had to ask. It seems from my perspective that mysqli won't allow program execution to continue if the connect fails...
I have custom error catching already in place, which seems to catch the fault - but without interpreting the error and re-executing the DatabaseHandler connect method with the next in the list from Within my error handler, I don't see many other approaches at the moment.
I'll be glad to share more if need be.
This question already has an answer here:
PHP: mysql_connect not returning FALSE
(1 answer)
Closed 8 years ago.
I'm very new to OOP and am trying to learn it. So please excuse my noobness. I'm trying to connect to mysql and to test whether the connection is successful or not, I'm using if-else conditions.
Surprisingly, the mysql_connect is always returning true even on passing wrong login credentials. Now I'm trying to figure out why it does and after spending about 20 minutes, I gave up. Hence, I came here to seek the help of the community. Here is my code:
class test
{
private $host = 'localhost';
private $username = 'root2'; // using wrong username on purpose
private $password = '';
private $db = 'dummy';
private $myConn;
public function __construct()
{
$conn = mysql_connect($this->host, $this->username, $this->password);
if(!$conn)
{
die('Connection failed'); // this doesn't execute
}
else
{
$this->myConn = $conn;
$dbhandle = mysql_select_db($this->db, $this->myConn);
if(! $dbhandle)
{
die('Connection successful, but database not found'); // but this gets printed instead
}
}
}
}
$test = new test();
Please don't use the mysql_* functions, there are many, many reasons why - which are well documented online. They are also deprecated and due to be removed.
You'd be much better off using PDO!
Also I'd strongly advise abstracting this database code into a dedicated database class, which can be injected where necessary.
On-topic:
That code snippet seems to work for me, have you tried var_dumping $conn? Does that user have correct rights?
I also hope that you don't have a production server which allows root login without a password!
Ignoring the fact that you're using mysql_* functions rather than mysqli or pdo functions, you should utilise exceptions in OOP code rather than die(). Other than that, I can't replicate your problem - it may be that your mysql server is set up to accept passwordless logins.
class test
{
private $host = 'localhost';
private $username = 'root2'; // using wrong username on purpose
private $password = '';
private $db = 'dummy';
private $myConn;
public function __construct()
{
// returns false on failure
$conn = mysql_connect($this->host, $this->username, $this->password);
if(!$conn)
{
throw new RuntimeException('Connection failed'); // this doesn't execute
}
else
{
$this->myConn = $conn;
$dbhandle = mysql_select_db($this->db, $this->myConn);
if (!$dbhandle)
{
throw new RuntimeException('Connection successful, but database not found'); // but this gets printed instead
}
}
}
}
try {
$test = new test();
} catch (RuntimeException $ex) {
die($ex->getMessage());
}
Hey guys I have a connection class I found for pdo. I am calling the connection method on the page that the file is included on. The problem is that within functions the $conn variable is not defined even though I stated the method was public, and I was wondering if anyone had an elegant solution other then using global in every function. Any suggestions are greatly appreciated.
CONNECTION
class PDOConnectionFactory{
// receives the connection
public $con = null;
// swich database?
public $dbType = "mysql";
// connection parameters
// when it will not be necessary leaves blank only with the double quotations marks ""
public $host = "localhost";
public $user = "user";
public $senha = "password";
public $db = "database";
// arrow the persistence of the connection
public $persistent = false;
// new PDOConnectionFactory( true ) <--- persistent connection
// new PDOConnectionFactory() <--- no persistent connection
public function PDOConnectionFactory( $persistent=false ){
// it verifies the persistence of the connection
if( $persistent != false){ $this->persistent = true; }
}
public function getConnection(){
try{
// it carries through the connection
$this->con = new PDO($this->dbType.":host=".$this->host.";dbname=".$this->db, $this->user, $this->senha,
array( PDO::ATTR_PERSISTENT => $this->persistent ) );
// carried through successfully, it returns connected
return $this->con;
// in case that an error occurs, it returns the error;
}catch ( PDOException $ex ){ echo "We are currently experiencing technical difficulties. We have a bunch of monkies working really hard to fix the problem. Check back soon: ".$ex->getMessage(); }
}
// close connection
public function Close(){
if( $this->con != null )
$this->con = null;
}
}
PAGE USED ON
include("includes/connection.php");
$db = new PDOConnectionFactory();
$conn = $db->getConnection();
function test(){
try{
$sql = 'SELECT * FROM topic';
$stmt = $conn->prepare($sql);
$result=$stmt->execute();
}
catch(PDOException $e){ echo $e->getMessage(); }
}
test();
You can declarate database class where you carrying conn pdo class, then you don't must duplicates instaces of this. And all database operations you can doing by this class. I mean my answer is what you searching.
But i see, you using only PDO hadle class in Product Factory pattern. You can use normal full database support class under PDO (includes queryies execution from one function) and without this design pattern when you don't want to use many database connectors engines.
I built this class to work with PDO, to make SQL queries 'easier' and less to worry about.
Here are my thoughts
Should it be more like class DB extends PDO?
Is the query method too big? Should it be split into private methods which are called.. is this what is known as loose coupling?
Is my way for detecting a SELECT query too ugly for it's own good?
What other problems are evident? As I am sort of learning-as-I-go, I'm sure I could have overlooked a lot of potential problems.
Thank you
`
class Db
{
private static $_instance = NULL;
private function __construct() {
// can not call me
}
private function __clone() {
// no!
}
public static function getInstance() {
if (!self::$_instance)
{
try {
self::$_instance = new PDO('mysql:host=' . CONFIG_MYSQL_SERVER . ';dbname=' . CONFIG_MYSQL_DATABASE, CONFIG_MYSQL_USERNAME, CONFIG_MYSQL_PASSWORD);;
self::$_instance-> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
trigger_error($e->getMessage());
}
}
return self::$_instance;
}
public static function query($query /*string*/, $bindings = NULL)
{
$queryPortion = substr($query,0, 6);
try {
if ($bindings) {
$prepared = self::getInstance()->prepare($query);
foreach($bindings as $binding=>$data) { // defaults to string
if (!is_array($data)) {
$prepared->bindParam($binding, $data);
} else {
switch(count($data)) {
case 1:
$prepared->bindParam($binding, $data['value']);
break;
case 2:
$prepared->bindParam($binding, $data['value'], $data['dataType']);
break;
case 3:
$prepared->bindParam($binding, $data['value'], $data['dataType'], (int)$data['length']);
break;
default:
trigger_error('An error has occured with the prepared statement bindings.');
return false;
break;
}
}
}
$prepared->execute();
return $prepared->fetchAll(PDO::FETCH_ASSOC);
} else if (String::match($queryPortion, 'select')) { // if this is a select query
$rows = self::getInstance()->query($query);
return $rows->fetchAll(PDO::FETCH_ASSOC);
} else {
return self::getInstance()->exec($query);
}
}
catch(PDOException $e)
{
trigger_error($e->getMessage());
}
}
public static function getLastInsertId()
{
try {
self::getInstance()->lastInsertId();
}
catch(PDOException $e)
{
trigger_error($e->getMessage());
}
}
public static function disconnect()
{
// kill PDO object
self::$_instance = NULL;
}
}
It's not bad and as it's been said it might help for small applications although it's mostly a very thin abstraction on another abstraction. It's not bringing a lot of others functionalities.
Something you might want to consider, amongst other things:
As this is PHP5 code, use exceptions instead of trigger_error and set_exception_handler if necessary until exceptions are more widespread, but it's definitely cleaner and more future-proof.
You are using a singleton, it's not a bad thing necessarily but in this case, for example, one shortcoming will be that you'll only be able to handle one connection to one database.
I don't know if you make use of stored procedures, but a stored procedure might return a result set through the query() method too.
You have two semi-colons (;;) at the end of your new PDO line.
That being said, I don't think your query method is too big and there's not much that could be recalled from elsewhere in there at the moment. Though as soon as you see two or three lines that could be called from another function, split it. That's a good way to DRY.
Yes and No.
It is good code for a simple quick and dirty application.
The problem comes when you use this in a more complex structured application.
Where the error handling will vary depending on which sql you are executing.
Also any severe errors will show up as "problem at line 999" type errors
where 999 is in your super duper routine and you will have difficulty tracing it back
to a particular sql request.
Having said that I do this sort of thing myself all the time on small projects.
Here's what I've used (just replace the references to Zzz_Config with $GLOBALS['db_conf'] or something):
/**
* Extended PDO with databse connection (instance) storage by name.
*/
class Zzz_Db extends PDO
{
/**
* Named connection instances.
*
* #var array
*/
static private $_instances;
/**
* Retrieves (or instantiates) a connection by name.
*
* #param string $name Connection name (config item key).
* #return Zzz_Db Named connection.
*/
static public function getInstance($name = null)
{
$name = $name === null ? 'db' : "db.$name";
if (!isset(self::$_instances[$name])) {
if (!$config = Zzz_Config::get($name)) {
throw new RuntimeException("No such database config item: $name");
}
if (!isset($config['dsn'])) {
if (!isset($config['database'])) {
throw new RuntimeException('Invalid db config');
}
$config['dsn'] = sprintf('%s:host=%s;dbname=%s',
isset($config['adapter']) ? $config['adapter'] : 'mysql',
isset($config['host']) ? $config['host'] : 'localhost',
$config['database']);
}
$db = self::$_instances[$name] = new self(
$config['dsn'],
isset($config['username']) ? $config['username'] : null,
isset($config['password']) ? $config['password'] : null);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//$db->setAttribute(PDO::ATTR_STATEMENT_CLASS, 'Zzz_Db_Statement');
if ($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') {
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
$db->exec('SET CHARACTER SET utf8');
}
}
return self::$_instances[$name];
}
}
Usage whould be:
$db = Zzz_Db::getInstance(); // or Zzz_Db::getInstance('some_named_db')
$stmt = $db->prepare('SELECT ...
The goal is to keep the db configuration in an *.ini file (editable by a non-coder).
I went the other way and made a class that extends PDO with a bunch of wrapper functions around prepare()/execute(), and it's much nicer than the built in functions (though that's a bit subjective...).
One other thing: you should set PDO::ATTR_EMULATE_PREPARES to false unless you're using a really old version of mysql (<=4.0). It defaults to true, which is a huge headache and causes things to break in obscure ways... which I'm guessing is the reason you've got a huge wrapper around bindParam() in the first place.
To answer your question, if it is a good code or not, ask yourself:
What is the added value of my code compared to using PDO directly?
If you find a good answer, go for using your code. If not, I would stick with PDO.
Also try considering implementing Zend Framework's DB class which works on its own and supports PDO.