Please I am very new to PDO, rather a novice at PHP as well. I am currently working on a project that involves connections to many databases: MySQL, MSSQL and Oracle. So I am using the class below, with PDO, for my connection. The class code is below.
class db {
private static $objInstance;
/*
* Class Constructor - Create a new database connection if one doesn't exist
* Set to private so no-one can create a new instance via ' = new DB();'
*/
private function __construct() {}
/*
* Like the constructor, we make __clone private so nobody can clone the instance
*/
private function __clone() {}
/*
* Returns DB instance or create initial connection
* #param
* #return $objInstance;
*/
public static function getDB($DBtype, $DBindex) {
include('vars.inc.php');
if (!self::$objInstance){
$DBid = $DBindex - 1;
switch ($DBtype){
case "mysql":
self::$objInstance = new PDO("mysql:host=".$dbvars[$DBid][0].";dbname=".$dbvars[$DBid][1], $dbvars[$DBid][2], $dbvars[$DBid][3]);
break;
case "mssql":
self::$objInstance = new PDO("odbc:Driver={SQL Server};Server=".$dbvars[$DBid][0].";Database=".$dbvars[$DBid][1], $dbvars[$DBid][2], $dbvars[$DBid][3]);
break;
case "oci";
self::$objInstance = new PDO("oci:dbname=//".$dbvars[$DBid][0].":".$dbvars[$DBid][4]."/".$dbvars[$DBid][1], $dbvars[$DBid][2], $dbvars[$DBid][3]);
break;
// Add other case(s) here if another RDBMS (Relational Database Management system) is used
default:
break;
}
self::$objInstance-> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return self::$objInstance;
}
}
And here is the vars include file that is required by the class, I used an array cos I felt this way, new databases can be added easily to the vars file by a non-programmer over time. Of course, here, I have changed the var file values.
define('DB_SERVER', 'localhost');
define('DB_NAME', 'db1name');
define('DB_USER', 'root');
define('DB_PASSWORD', 'rootpass');
define('DB_PORT', '');
define('DB2_SERVER', 'xxx.xxx.xx.xxx');
define('DB2_NAME', 'db2name');
define('DB2_USER', 'root2');
define('DB2_PASSWORD', 'rootpass2');
define('DB2_PORT', '');
define('DB3_SERVER', 'xx.xxx.xxx.xxx');
define('DB3_NAME', db3name');
define('DB3_USER', 'root3');
define('DB3_PASSWORD', 'rootpass3');
define('DB3_PORT', '');
define('DB4_SERVER', 'xxx.xx.xxx.xx');
define('DB4_NAME', 'oracledb');
define('DB4_USER', 'root4');
define('DB4_PASSWORD', 'rootpass4');
define('DB4_PORT', '1521');
$dbvars = array( array(DB_SERVER, DB_NAME , DB_USER, DB_PASSWORD, DB_PORT),
array(DB2_SERVER, DB2_NAME , DB2_USER, DB2_PASSWORD, DB2_PORT),
array(DB3_SERVER, DB3_NAME , DB3_USER, DB3_PASSWORD, DB3_PORT),
array(DB4_SERVER, DB4_NAME , DB4_USER, DB4_PASSWORD, DB4_PORT)
);
Now the problem is that, whenever I have connected to one database and try to run my queries on another one, PDO keeps remembering the old database. But if I independently run either query, everything is fine. Can someone please help with this, or suggest a better method? :(
E.g.
include('./includes/db.class.php');
try {
$result = DB::getDB("mysql", 3)->query("SELECT myrow FROM mytable");
foreach($result as $row){
print $row['myrow'].'<br />';
}
}catch(PDOException $e){
echo $e->getMessage();
}
echo "<br />Then<br /><hr /><br />";
try {
$result = DB::getDB("mysql", 1)->query("SELECT yourrow FROM yourtable");
foreach($result as $row){
print $row['yourrow'].'<br />' ;
}
}catch(PDOException $e){
echo $e->getMessage();
}
In this case, PDO will simply keep checking DATABASE db1name for TABLE yourtable rather than check DATABASE db3name. So PDO will throw an error:
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'db1name.yourtable' doesn't exist
You have it setup as a singleton. So your next call to Db::getDB returns the original instance. If you want to cache the instances for the duration of the script, change $objInstance to an array, and then instead of doing:
if (!self::$objInstance){
Do
$signature = $DBtype . $DBindex;
if (!isset(self::$objInstances[$signature])) {
Of course you'll need to change the assignment lines and the return line as well, but I think you get the idea...
It appears that your getDB function will only connect once because of this line:
if (!self::$objInstance){
So the first time you execute it, it will connect, but on all subsequent calls the logic is ignored.
I suggest adding another property to your class that stores the current DBType and changing your conditional to:
if (!self::$objInstance || $DBtype != self::$dbtype){
You would have to set $dbtype inside each case of the switch statement.
Related
I am trying to fetch table categories data from DB named stores. Here when I run this code it displays the DB error mentioned ie. Could not select the indicated database. How to resolve it? My DB name is correct plus $con variable is also not accessed in index.php while calling the function. Why is it so?
Note: The class code was in MySQL which is now depreciated. I have tried to convert it into MySQL.
index.php
require_once("ajax_table.class.php");
$obj = new ajax_table();
$records = $obj->getRecords($conn);
config.php
define ( 'DB_HOST', 'localhost' );
define ( 'DB_USER', 'root' );
define ( 'DB_PASSWORD', '' );
define ( 'DB_DB', 'stores' );
ajax_table.class.php
class ajax_table {
private $conn;
public function __construct(){
$this->$conn=$this->dbconnect();
}
private function dbconnect() {
$conn = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD,DB_DB)
or die ("<div style='color:red;'><h3>Could not connect to MySQL server</h3></div>");
return $conn;
}
function getRecords($conn){
$this->res = mysqli_query($conn,"select * from categories");
if(mysqli_num_rows($this->res)){
while($this->row = mysqli_fetch_assoc($this->res)){
$record = array_map('stripslashes', $this->row);
$this->records[] = $record;
}
return $this->records;
}
//else echo "No records found";
}
I cant see that you have included your config page into your database class?
Add this to your database class:
<?php require_once("config.php"); ?>
It looks like you have the parameters to mysqli_select_db switched. Check out this link which has the syntax and also suggests using the 4th parameter to mysqli_connect to select the database instead. As Rishi's answer noted, you're actually already doing that, so you can remove this call entirely!
Regarding the $conn variable, you are returning $conn from dbconnect, but not doing anything with it. You probably want to have a property on the class:
private $conn;
Then in dbconnect do something like:
$this->conn = $this->dbconnect();
And use $this->conn in getRecords instead of a parameter.
There are several ways you can solve this; the fundamental point is that you need the return from dbconnect to be kept somewhere and made available when you need to talk to the DB.
No need to use mysqli_select_db as you already selecting database in mysqli_connect.
Remove below line from your code
mysqli_select_db(DB_DB,$conn)
or die ("<div style='color:red;'><h3>Could not select the indicated database</h3></div>");
I am learning how to use PDO and writing a Database class for the first time. I have read for a few hours about this subject and found some useful information here, here and here.
It seems clear that dependency injection is the right strategy, but I'm not sure on the best way to have the Database class access the configuration details for the database (host, dbname, user, pass).
In the first two examples above, this information is included inside the Database class. However, I usually keep all my global configuration variables inside an external file such as config.php.
In the last example, user 'prodigitalson' adds this comment to his getInstance() method:
// get the arguments to the constructor from configuration somewhere
Since the constructor for this Database class includes parameters for the database configuration, I assume that this information is not supposed to be stored in the Database class itself.
So what is the best strategy for accessing the database configuration (which I am currently storing in config.php along with other config variables) from the getInstance() method?
Someone asked a similar question here but none of the answers really addresses the question, imo.
As I said in the comments, you are using the singleton pattern which is not dependency injection.
Here is an example using dependency injection:
class Database
{
public function __construct($host, $user, $password) {
// ...
}
}
$db = new Database('foo', 'bar', 'baz');
Then you inject the database object in the classes where you need to use it:
$reportGenerator = new ReportGenerator($db);
Have a look at this tutorial if you are still lost.
While this is not what I would prefer, because \PDO does not lend itself to being configured after construction, it is the closest I've come to getting DI with \PDO.
<?php namespace b01\Database;
use Exception;
use b01\DatabaseException;
use PDO;
use PDOException;
/**
* This class is responsible for connecting to a Database.
* It wraps the PDO object, setting all the values needed in order to connect.
* Also displays some human readable error messages when connecting fails.
*
* #package \b01\Database
*/
class Connection
{
/**
* string
*/
const DSN_MYSQL = 'mysql:dbname=%s;host=%s;port=%s';
/**
* Initialized the PDO transport object.
*
* It was decided to pass the PDO class in as a string, as pseudo DI,
* to make the class more testable.
*
* #param string $server server.
* #param string $database Database name.
* #param string $username Database username for reading.
* #param string $password Database username password.
* #param int $port Server port number.
* #param string $pdoClass Defaults to \PDO, but is passed in this way to
* make this class more testable.
* #throws \b01\Exceptions\DatabaseException
*/
public function __construct(
$server,
$database,
$username,
$password,
$port = 3306,
$pdoClass = '\PDO'
) {
$dsn = sprintf(
self::DSN_MYSQL,
$database,
$server,
$port
);
$options = [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
];
try {
$this->connection = new $pdoClass($dsn, $username, $password, $options);
} catch (Exception $error) {
throw new DatabaseException(
DatabaseException::CODE_CONNECT,
[$dsn . "\n\n", $error->getMessage()]
);
} catch (PDOException $error) {
throw new DatabaseException(
DatabaseException::CODE_CONNECT,
[$dsn . "\n\n", $error->getMessage()]
);
}
}
In your config.php file, set some constants for your database credentials, depending on the server you are running in:
switch($_SERVER['HTTP_HOST']){
case 'your-live-domain.com':
define('MYSQL_HOST', '<input>');
define('DB_NAME', '<input>');
define('MYSQL_USER', '<input>');
define('MYSQL_PW', '<input>');
break;
default:
// perhaps this could be your localhost db credentials
define('MYSQL_HOST', '<input>');
define('DB_NAME', '<input>');
define('MYSQL_USER', '<input>');
define('MYSQL_PW', '<input>');
}
Then, you can make calls to these constants within your database connection class(es).
I'm rewriting my php code for mysql database access (now using a class definitions). While I had the old code (using old style functions) working I struggle to get the code for creating tables right - I think the problem relates in particular to the right definition of the sql statement, pls see code below. The new aspect (for me) compared to the old code is that the sql table creation command now must include the name of a database different than the master_database for which the PDO connection is set up (within the class constructor).
I looked at http://dev.mysql.com/doc/refman/5.6/en/create-table.html (I use mysql v5.6) - the part starting with "The table name can be specified as db_name.tbl_name to ..." but I cant get the sql syntax right. If I'm right the sql syntax is wrong what should be the right syntax in the code below for $sql (using the construction with various variables)?
Thnx for your help in advance.
Simplified code:
<?php
define('DB_HOST', '***');
define('DB_NAME', 'MASTER_DATABASE'); //different name
define('DB_USER', 'root');
define('DB_PASS', '*****');
//class definitions
class Database {
private $_db = null; //databasehandler
public function __construct() {
try {
$this->_db = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME, DB_USER, DB_PASS, array(
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true
));
$this->_db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->_db->query('SET CHARACTER SET utf8');
} catch (PDOException $e) {
exit('Error while connecting to database.'.$e->getMessage());
}
}
private function printErrorMessage($message) {
echo $message;
}
public function createDBTable($dbname,$dbtable,$tablestructure) {
try {
$sql = "CREATE TABLE `".$dbname."'.'".$dbtable.$tablestructure; //NOT CORRECT YET
$success = $this->_db->exec($sql);
return ($success > 0) ? true:false;
}
catch(PDOException $e){
$this->printErrorMessage($e->getMessage());
}
}
}//class
//code using class defs
$dbname = 'Mydatabase'; //just an existing database
$dbtable = 'Persons';
$tablestructure = '(FirstName CHAR(30),LastName CHAR(30),Age INT)';
$mydb = new Database();
if($mydb->createDBTable($dbname,$dbtable,$tablestructure)){
echo 'table creation success';
}
else{
echo 'table creation failure';
}
?>
You can replace you create table line as:-
$sql = "CREATE TABLE ".$dbname.".".$dbtable."".$tablestructure; //NOT CORRECT YET
Also instead of:-
$success = $this->_db->exec($sql);
Use the below lines:-
$stmt = $this->_db->prepare($sql);
$success = $stmt->execute();
Without the above 2 lines, the $succes variable will have value of 0 and although the table can be created, it would echo table creation failed.
Hope this helps.
I need to do continuous parsing of several external stomp data streams, inserts of relevant fields into a MySql db, and regular queries from the db. All of this is in a protected environment - ie I'm not dealing with web forms or user inputs
Because I'm implementing a range of inserts into + queries from different tables, I've decided to set up a PDO active record model - following the advice of Nicholas Huot and many SO contributors.
I've got a simple repeated insert working OK, but after several days of grief can't get a prepared insert to fly. I want to use prepared inserts given there are going to be a lot of these (ie for performance).
Relevant bits of the code are :
=========
Database class :
private function __construct()
{
try {
// Some extra bad whitespace removed around =
$this->dbo = new PDO('mysql:host=' . DBHOST . ';dbname=' . DBNAME, DBUSER, DBPSW, $options);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
}
public static function getInstance()
{
if(!self::$instance)
{
self::$instance = new self();
}
return self::$instance;
}
public function prepQuery($sql) // prepared statements
{
try {
$dbo = database::getInstance();
$stmt = new PDOStatement();
$dbo->stmt = $this->$dbo->prepare($sql);
var_dump($dbo);
}
catch (PDOException $e) {
echo "PDO prepare failed : ".$e->getMessage();
}
}
public function execQuery($sql) // nb uses PDO execute
{
try {
$this->results = $this->dbo->execute($sql);
}
catch (PDOException $e) {
echo "PDO prepared Execute failed : \n";
var_dump(PDOException);
}
}
=========
Table class :
function storeprep() // prepares write to db. NB prep returns PDOstatement
{
$dbo = database::getInstance();
$sql = $this->buildQuery('storeprep');
$dbo->prepQuery($sql);
return $sql;
}
function storexecute($paramstring) // finalises write to db :
{
echo "\nExecuting with string : " . $paramstring . "\n";
$dbo = database::getInstance(); // Second getInstance needed ?
$dbo->execQuery(array($paramstring));
}
//table class also includes buildQuery function which returns $sql string - tested ok
=======
Controller :
$dbo = database::getInstance();
$movements = new trainmovts();
$stmt = $movements->storeprep(); // set up prepared query
After these initial steps, the Controller runs through a continuous loop, selects the fields needed for storage into a parameter array $exec, then calls $movements->storexecute($exec);
My immediate problem is that I get the error message "Catchable fatal error: Object of class database could not be converted to string " at the Database prepquery function (which is called by the Table storeprep fn)
Can anyone advise on this immediate prob, whether the subsequent repeated executes should work in this way, and more widely should I change anything with the structure ?
I think your problem in this line $dbo->stmt = $this->$dbo->prepare($sql);, php want to translate $dbo to string and call function with this name from this. Actually you need to use $this->dbo.
And actually your functions not static, so i think you don't need to call getInstance each time, you can use $this.
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.