I'm using a slight variation on the PHP PDO Wrapper Class found here: http://www.imavex.com/php-pdo-wrapper-class/
This is the new constructor I'm using:
public function __construct($dsn, $user='', $passwd='') {
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
try {
parent::__construct($dsn, $user, $passwd, $options);
} catch (PDOException $e) {
$this->error = $e->getMessage();
}
}
error is a private variable of this class, defined at the begin of the class, above the constructor.strong text
And when I use it like this
$database = new Database(
'mysql:host=' . $config['mysql_host'] . ';port=' . $config['mysql_port'] . ';dbname=' . $config['mysql_database'],
$config['mysql_username'],
$config['mysql_password']
);
It shows me this error: Creating default object from empty value, pointing to the database class file, at the line $this->error = $e->getMessage();.
The reason an exception is thrown is because I haven't set the mysql username and password yet, so don't worry about that. I'm trying to figure out why it gives this error and how to fix it.
It seems that since the db class extends PDO, when the PDO constructor throws an exception $this becomes NULL.
class db extends PDO {
public function __construct($dsn, $user="", $passwd="") {
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
try {
parent::__construct($dsn, $user, $passwd, $options);
} catch (PDOException $e) {
echo "Got PDOException\n";
var_dump($this);
}
}
}
new db('mysql:host=localhost;port=3306;dbname=something', 'invaliduser', 'invalidpass');
If you run that code you will get this output:
Got PDOException
NULL
To solve that you could remove the offending line and check if the db object is null or remove the try-catch and do the exception handling when you instantiate a new db.
Alternatively, you could rewrite the class to store the PDO object in a variable instead of extending PDO.
Related
I have searched the forum, but I have not found anything directly related to my issue. I am fairly new to PDOs and class OOP creation. I am trying to create a database connection class where I can instantiate the connection as needed. I having issues instantiating my connection.
File Organization:
parent directory (folder)
|
private (folder)
|
config.php
classes (folder)
|
class1.class.php
DatabaseConnection.class.php
db_cred.inc.php
public (folder)
|
search.php
Process:
I have created a database credential php file "db_cred.inc.php"
I have a database connection class called "DatabaseConnect" in "DatabaseConnect.class.php" file
I load those files as follows
require_once 'db_cred.inc.php'; in the "DatabaseConnect.class.php" file
spl_autoload_register for all of my class files
Expected actions:
When my "search.php" page request data from mysql via a pdo, a new database connection will instantiate a new connection via the "openConnection()" method.
The class "DatabaseConnection" will load the credentials from "db_cred.inc.php" as a sting and connect to the database
The class will then use the credentials to connect to the mysql database, and execute the requested pdo query returning the results and storing them into a variable "$row".
Issue:
When I execute the pdo, the following error is returned:
Uncaught TypeError: PDO::__construct() expects parameter 1 to be string, array given in private/classes/DatabaseConnect.class.php:21 Stack trace: #0 private\classes\DatabaseConnect.class.php(21): PDO->__construct(Array) #1 \public\search.php(53): DatabaseConnect->openConnection() #2 {main} thrown in \private\classes\DatabaseConnect.class.php on line 21
spl_autoload_register() in the config.php file
function my_autoload($class) {
if(preg_match('/\A\w+\Z/', $class)) {
require ('classes/' . $class . '.class.php');
}
}
spl_autoload_register('my_autoload');
Credential setup in "db_cred.inc.php"
<?php
// define an array for db connection.
define("DB", [
"DB_HOST" => "mysql:host=localhost",
"DB_USER" => "user",
"DB_PASS" => "pass",
"DB_DATABASE" => "mytestdb",
"DB_CHAR" => "utf8",
]);
?>
My class for database connection:
<?php
require_once 'db_cred.inc.php';
// creating db connection class
class DatabaseConnect {
private $server = 'DB_HOST';
private $database = 'DB_DATABASE';
private $pass = 'DB_PASS';
private $user = 'DB_USER';
private $opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
protected $con;
public function openConnection() {
**ERROR TAKES PLACE HERE**
try {
$this->con = new PDO([$this->server, $this->database, $this->user, $this->pass]);
return $this->con;
} catch(PDOExeption $e){
echo "ERROR: " . $e->getMessage(), (int)$e->getCode();
}
}
public function closeConnection() {
$this->con = null;
}
}
?>
PDO for search.php
<?php
$dbconn = new DatabaseConnect();
$pdo = $dbconn->openConnection();
$sql = "SELECT * FROM report";
foreach ($pdo->query($sql) as $row) {
echo " PI: ".$row['pi'] . "<br>";
}
?>
I am not sure what is causing the error. I am sure it is due to my inexperience with classes and oop. It appears that the config.php file is working fine. The class is identified by the request because the error happens inside the PDO __construct method. Please help.
UPDATE - MY WORKING SOLUTION
I hope this might help someone moving forward with a similar question. I am not finished with the development of this process, but this kicked in a huge door.
My revised class
<?php
// Associating db_cred.inc.php with class
require_once('db_cred.inc.php');
// Creating db connection class
class DatabaseConnect {
/*
!! Assigning defined constants per define('DB_CONSTANT', 'value') loaded from "db_cred.inc.php" file to variables
!! "db_cred.inc.php" should not be loaded into the "config.php" file
!! BECAUSE THE VALUES OF THE VARIABLES ($variable) ARE CONSTANTS (DB_CONSTANT), DO NOT USE SINGLE OR DOUBLE QUOTES. THE PDO __CONSTRUCT FUNCTION WILL IGNORE THE VALUE OF THE VARIABLE
*/
private $host = DB_HOST;
private $database = DB_DATABASE;
private $pass = DB_PASS;
private $user = DB_USER;
private $char = DB_CHAR;
// Setting attributes and storing in variable $opt
private $opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
// $con variable will store PDO connection and is set to NULL
private $con;
// Create Connection to database
public function openConnection() {
// Setting $con to null
$this->con = NULL;
// If $con is not NULL make it NULL
if ($this->con === NULL){
try {
// Establish DSN
$dsn = "mysql:host={$this->host};dbname={$this->database};charset={$this->char}";
// Complete the PDO connection
$this->con = new PDO($dsn, $this->user, $this->pass,$this->opt);
// Return the connection and store it in $con
return $this->con;
// Catch any exceptions and store in $e
} catch(PDOExeption $e){
// Echo error and Exception message
echo "ERROR: " . $e->getMessage(), (int)$e->getCode();
}
// If the try/catch block fails, echo that no connection was established
} else {
echo "ERROR: No connection can be established";
}
}
// Close connection and set it to NULL
public function closeConnection() {
if($this->con !== NULL){
$this->con = NULL;
}
}
// create CRUD subclass (Create, Read, Update, Delete)
}
?>
I changed "db_cred.inc.php" eliminating the array. I may revisit the idea.
<?php
// defining DB Credential CONSTANTS to be stored in variables and instantiated by connect class
define("DB_HOST", "localhost");
define("DB_USER", "user");
define("DB_PASS", "pass");
define("DB_DATABASE", "mytestdb");
define("DB_CHAR", "utf8");
// define an array for db connection.
/*define("DB", [
"DB_HOST" => "localhost",
"DB_USER" => "user",
"DB_PASS" => "pass",
"DB_DATABASE" => "mytestdb",
"DB_CHAR" => "utf8",
]);*/
?>
The error explains itself: You're passing an array where you should be using a string instead.
You need to change this line:
$this->con = new PDO([$this->server, $this->database, $this->user, $this->pass]);
To this, (specifies the DSN first):
$dsn = "mysql:dbname={$this->database};host:{$this->host}";
$this->con = new PDO($dsn, $this->user, $this->password);
I've seen variations of this code all over the place, including many S.O. posts:
class db extends PDO {
public function __construct( $dbconf ) {
$options = array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_PERSISTENT => $dbconf['persist'] ? true : false
);
try {
parent::__construct('mysql:host='. $dbconf['dbhost'] .';port=3306;dbname='. $dbconf['dbname'] .';' , $dbconf['dbuser'],
$dbconf['dbpass'],
$options);
} catch (PDOException $e) {
$this->myerror( $e->getMessage() );
// echo 'Connection failed ... '. $e->getMessage();
}
}
...
private function myerror( $error ) {
echo 'Connection failed ... '. $error;
}
}
The class is instantiated with $db = new db( $config );, and it works great if the connection is valid, but it seems PDOException doesn't actually work if the connection fails. The catch totally fails to execute the $this->myerror(...) function! Instead of the useful $e->getMessage() that says "Connection failed ... Access denied for user blah", I get a PHP Fatal error: Call to a member function myerror() on a non-object in /.../lib/pdo.class.php on line 16.
If I comment out the first line in the catch and uncomment the echo, it works as expected, reporting the reason for the connection error. Why would the message be available in the catch but not in the simple myerror class function?
This post ... PHP, PDO, and Exceptions ... seems applicable, but doesn't explain much. Is catch (PDOException $e) obsolete, or dysfunctional under certain circumstances? How do I keep get this to work?
The reason being is PDO::__construct only creates the object upon successful connection. So when you call the parent constructor and it fails your object doesn't exist anymore. You should use self::myerror() to access the error function statically in this case.
There shouldn't be myerror() function at all. A database layer scarcely need it's own error handler.
And constructor have to be
public function __construct( $dbconf ) {
$dsn = "mysql:";
$dsn .= "host=". $dbconf['dbhost'].";";
$dsn .= "dbname=". $dbconf['dbname'].";";
$dsn .= "port=". $dbconf['dbport'].";";
$dsn .= "charset=".$dbconf['dbcharset'];
$options = array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES '.$dbconf['charset'],
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_PERSISTENT => (bool)$dbconf['persist'],
);
parent::__construct($dsn, $dbconf['dbuser'], $dbconf['dbpass'],$options);
}
So, I'm working on a project that requires a database connection. I've chosen to use PDO for its versatility and need to figure out how to set up the connection. Currently I'm going for something like this:
class Database {
private static $db;
static function initDB() {
if(!is_object(self::$db) || get_class(self::$db) != 'PDO') {
include('core/db.php');
try {
$db = new PDO($database, $username, $password);
} catch(PDOException $e) {
print("<br />Could not establish database connection. Error message: ".$e->getMessage()."<br />");
die();
}
}
//Try the transaction
/*
if($transaction = $db::query(PDO::quote($value)))
$db::query(PDO::quote("INSERT INTO log VALUES ('".Authorization::$user."','".PDO::quote($value)."', 'Success')"));
else
$db::query(PDO::quote("INSERT INTO log VALUES ('".Authorization::$user."','".PDO::quote($value)."', 'Failure')"));*/
}
}
So, this pretty much reveals one of the concepts I don't really know: singletons and static classes/objects. Any way to set up a database connection using OO best practices that initializes with the script via some kind of __construct method?
A database connection should not be either static or a singleton. This merely introduces another form of global state, which is bad for unit-testing and does hide obvious dependencies.
The right way here would be is to inject an instance of PDO into the classes that need it. You adhere the Single-Responsibility Principle and Dependency Injection.
Note, you should never log errors and do include() inside PDOAdapter constructor because its masked violation of the Single-Responsibility Principle
So, this would look like this:
final class PDO_Provider extends PDO
{
/**
* Constructor. Inits PDO
*
* #param array $params
* #return void
*/
public function __construct(array $params)
{
try {
extract($params);
parent::__construct(sprintf('mysql: host=%s; dbname=%s', $host, $database), $user, $password);
$this->setAttribute(parent::MYSQL_ATTR_INIT_COMMAND, 'SET NAMES UTF8');
$this->setAttribute(parent::ATTR_ERRMODE, parent::ERRMODE_EXCEPTION);
$this->setAttribute(parent::ATTR_EMULATE_PREPARES, false);
$this->setAttribute(parent::ATTR_DEFAULT_FETCH_MODE, parent::FETCH_ASSOC);
} catch(PDOException $e) {
die($e->getMessage());
}
}
}
And you would use it like this,
<?php
$sql_config = array(
'host' => 'localhost',
'user' => 'root',
'password' => '',
'database' => '_DB_NAME_',
);
// <- Or you can include that, like
$sql_config = include(__DIR__ . '/core/db_params.php');
$pdoProvider = new PDO_Provider($sql_config);
$user = new User_Login($pdoProvider); // the point here is to inject an instance of $pdoProvider. User_Login is actually irrelevant
if you want to use a normal object instead of sigleton, try something like this:
class PDOConnector{
protected $connection;
function __construct($host, $user, $pass, $db_name)
{
//create database connection
try{
$this->connection = new PDO('mysql:host='.$this->host.';dbname='.$this->db_name.';charset=utf8', $this->user, $this->pass,array(PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
}
catch(PDOException $ex) {
echo "An Error occured : ".$ex->getMessage();
}
}
}
I've seen variations of this code all over the place, including many S.O. posts:
class db extends PDO {
public function __construct( $dbconf ) {
$options = array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_PERSISTENT => $dbconf['persist'] ? true : false
);
try {
parent::__construct('mysql:host='. $dbconf['dbhost'] .';port=3306;dbname='. $dbconf['dbname'] .';' , $dbconf['dbuser'],
$dbconf['dbpass'],
$options);
} catch (PDOException $e) {
$this->myerror( $e->getMessage() );
// echo 'Connection failed ... '. $e->getMessage();
}
}
...
private function myerror( $error ) {
echo 'Connection failed ... '. $error;
}
}
The class is instantiated with $db = new db( $config );, and it works great if the connection is valid, but it seems PDOException doesn't actually work if the connection fails. The catch totally fails to execute the $this->myerror(...) function! Instead of the useful $e->getMessage() that says "Connection failed ... Access denied for user blah", I get a PHP Fatal error: Call to a member function myerror() on a non-object in /.../lib/pdo.class.php on line 16.
If I comment out the first line in the catch and uncomment the echo, it works as expected, reporting the reason for the connection error. Why would the message be available in the catch but not in the simple myerror class function?
This post ... PHP, PDO, and Exceptions ... seems applicable, but doesn't explain much. Is catch (PDOException $e) obsolete, or dysfunctional under certain circumstances? How do I keep get this to work?
The reason being is PDO::__construct only creates the object upon successful connection. So when you call the parent constructor and it fails your object doesn't exist anymore. You should use self::myerror() to access the error function statically in this case.
There shouldn't be myerror() function at all. A database layer scarcely need it's own error handler.
And constructor have to be
public function __construct( $dbconf ) {
$dsn = "mysql:";
$dsn .= "host=". $dbconf['dbhost'].";";
$dsn .= "dbname=". $dbconf['dbname'].";";
$dsn .= "port=". $dbconf['dbport'].";";
$dsn .= "charset=".$dbconf['dbcharset'];
$options = array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES '.$dbconf['charset'],
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_PERSISTENT => (bool)$dbconf['persist'],
);
parent::__construct($dsn, $dbconf['dbuser'], $dbconf['dbpass'],$options);
}
I'm using this function to connect to my MySQL db when needed, and also to re-use the same connection object for any further query I might need in the same php script.
function cnn() {
static $pdo;
if(!isset($pdo)) {
try {
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME, DB_USER, DB_PASS);
$pdo->setAttribute(PDO::ATTR_TIMEOUT, 30);
$pdo->setAttribute(PDO::ATTR_PERSISTENT, true);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
return $pdo;
} catch(PDOException $e) {
http_response_code(503);
echo $e->getCode.': '.$e->getMessage();
die(); //or whatever error handler you use
}
} else {
return $pdo;
}
}
First query (object is created)
echo cnn()->query('SELECT firstname FROM user WHERE id=4;')->fetch(PDO::FETCH_COLUMN)
Second query (object is reused)
echo cnn()->query('SELECT title FROM news WHERE id=516;')->fetch(PDO::FETCH_COLUMN)
Do you agree on this approach? Do you think it can be optimized? Thanks for your opinions.
I agree with the method, though many people will tell you that this "singleton" approach is bad, bad.
However, I disagree with your implementation. It should be:
function cnn() {
static $pdo;
if(!$pdo) {
$conf = array(PDO::ATTR_TIMEOUT => 30,
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
);
$dsn = 'mysql:host='.DB_HOST.';dbname='.DB_NAME;
$pdo = new PDO($dsn, DB_USER, DB_PASS, $conf);
}
return $pdo;
}
Also, it looks sensible to move handler code into handler (and of course without echoing the error unconditionally!)
function my_exceptionHandler($exception) {
http_response_code(503);
if (ini_get('display_errors')) {
echo $e->getMessage().$e->getTrace();
} else {
log_error($e->getMessage().$e->getTrace());
}
die(); //or whatever error handler you use
}
set_exception_handler("my_exceptionHandler");
Also, I'd extend it to accept a parameter to make several connections possible.