I have run into an unexpected fatal error while testing an app that uses my database utility class (see code below). Here is a rundown of the test that was performed:
a new product was added to the database
the new product was put on an order
an attempt was made to delete the new product (while it was still being used on an order)
Results:
as expected, the foreign key constraint in the database prevented deletion of the product
unexpectedly, the following fatal error was displayed - Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[23000]: [Microsoft][SQL Server Native Client 11.0][SQL Server]The DELETE statement conflicted with the REFERENCE constraint "FK_Orders_Products". The conflict occurred in database "AppTEST", table "dbo.Orders", column 'productID'.' in C:\xampp\htdocs\App\classes\utility\DB.php:140 Stack trace: ...Line 140 is $stmt->execute();, which is in the query() method in the code below.
Forum posts describing similar errors called out prepending global classes with a backslash, which I did, but to no avail. Why is the fatal error occurring?
Here are the pertinent parts of my database utility class for this question:
namespace classes\utility;
use \PDO,
\Exception,
\PDOException;
class DB
{
private $_pdo, # stores PDO object when it's instantiated
$_error, # stores whether query failed or not
$_results, # stores dataset returned by query
$_count = 0; # stores number of data rows returned
/*connect to database*/
private function __construct()
{
try
{
$connection = new \PDO('sqlsrv:Server=' . DB_HOST . ';Database=' . DB_NAME);
$connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); //to capture error messages returned by db
$this->setPdo($connection);
}
catch(\PDOException $e)
{
exit((DEBUGMODE) ? $e->getMessage() : 'There was a problem connecting to the database.');
}
}
# generic query method - uses named placeholders for parameter binding
private function query($sql, $parameters = [])
{
try
{
$this->setError(false);
$stmt = $this->getPdo()->prepare($sql); //assign to a variable with a short name
if ($stmt):
if (count($parameters)):
foreach($parameters as $name => $parameter):
$stmt->bindValue(':'.$name, $parameter);
endforeach;
endif;
$stmt->execute();
$this->setCount($stmt->rowCount());
if ($stmt->rowCount() == -1): //conditionally do the following if SQL stmt was a SELECT
$this->setResults($stmt->fetchAll(\PDO::FETCH_OBJ));
$this->setCount($stmt->rowCount()); //overwrite count attribute with number of rows returned by SELECT stmt
endif;
endif;
return $this;
}
catch (\PDOException $e)
{
$this->setError(true);
throw $e;
}
}
// setter and getter methods for private variables not shown
}
EDIT:
Here is the code that calls the query() method. This method is also part of the DB class.
public function runQuery($operation, $sql, $parameters)
{
try
{
$this->query($sql, $parameters); //call query method all $operation types (read, write)
if ($operation == 'read'):
if ($this->getCount()):
if ($this->getCount() > 1): //for "read all"
$data = $this->getResults();
else: //for "read one"
$data = $this->getFirstResult();
endif;
$data = Arr::objectToArray($data);
return $data;
else:
throw new \Exception("No data to display."); //throw an exception if query runs fine but returns zero rows
endif;
endif;
}
catch (\PDOException $e)
{
throw (DEBUGMODE) ? $e : new \Exception("A database error occurred. The $operation operation failed.");
}
}
The problem turned out to be missing a backslash in a calling method. I had prepended the backslash in all references to global classes in the DB class, but not in other classes in other namespaces that had catch blocks.
There were actually 2 different ways to resolve this problem.
One approach was to change catch (Exception $e) to catch (\Exception $e). Alternatively, leaving catch (Exception $e) as-is throughout the code and adding use \Exception; after the namespace declaration and before the class definition worked as well.
Related
I'm using a class for database connection, this class holds the CRUD methods, and also the database connection methods.
It's basically the core of all other classes that uses the DB.
I'm currently facing a problem on dealing with duplicate entry in unique columns, when inserting a record.
Database.class.php insertDB method:
class Database {
(...)
public function insertDB($sql,$params=null){
$con=$this->connect();
$query=$con->prepare($sql);
$query->execute($params);
$rs = $con->lastInsertId();
return $rs;
self::__destruct();
}
(...)
}
There is another class, PersonDAO.class.php, which is a class that inherits Database.class.
PersonDAO.class.php has its own method for inserting a record, which finally uses Database.class.php InsertBD method.
PersonDAO.class.php insert method: (notice it calls insertDB at the end).
class PersonDAO extends Database{
(...)
public function insert ($fields, $params=null) {
$numparams="";
for($i=0;$i<count($params);$i++) $numparams .= ",?";
$numparams=substr($numparams, 1);
$sql = "INSERT INTO Person ($fields) VALUES ($numparams)";
$t=$this->insertDB($sql,$params);
return $t;
}
(...)
}
The problem occurs in the registration form register.php, when I instantiate PersonDAO and use its insert method for inserting a duplicate entry into a column set as unique.
(...)
$person= new PersonDAO();
$fields="email,name";
$email="john#doe.com"; //already existing record. email column set as unique
$name="John Doe";
$params=array($email,$name);
try {
$rs = $person->insert($fields,$params);
} catch (PDOException $e) {
if ($e->errorInfo[1] == 1062) {
echo "Cannot insert record. Duplicate entry";
}
}
(...)
It's not catching duplicate entry exception, as if there was no errors.
var_dump($rs) contains:
string(1) "0"
But should not it catch PDOException and print "Cannot insert record. Duplicate entry"?
Is there any "Uncaught Exception ..." message ? Perhaps, You first need to catch the Exception in your Database.class.php insertDB method and then throw it.
class Database {
(...)
public function insertDB($sql,$params=null){
try{
$con=$this->connect();
$query=$con->prepare($sql);
$query->execute($params);
$rs = $con->lastInsertId();
return $rs;
self::__destruct();
}
catch(Exception $e){
throw $e;
}
}
(...)
}
Or if there is no error at all. Check
$con->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
If, for whatever reason, there is an error in creation of an entry using the mapper I get an error.
I'd like to do a custom notification and fail gracefully like so...
try {
$request->save();
} catch (Exception $e) {
$this->utils->errorNotify($f3,'could not create a request entry',http_build_query($_POST));
return null;
}
is this possible with F3?
\DB\SQL is a subclass of PDO so it can throw catchable PDO exceptions. Since these are disabled by default, you need to enable them first. This can be done in 2 different ways:
at instantiation time, for all transactions:
$db = new \DB\SQL($dsn, $user, $pwd, array( \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION ));
later on in the code, on a per-transaction basis:
$db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
Once PDO exceptions are enabled, just catch them as other exceptions:
try {
$db->exec('INSERT INTO mytable(id) VALUES(?)','duplicate_id');
} catch(\PDOException $e) {
$err=$e->errorInfo;
//$err[0] contains the error code (23000)
//$err[2] contains the driver specific error message (PRIMARY KEY must be unique)
}
This also works with DB mappers, since they rely on the same DB\SQL class:
$db=new \DB\SQL($dsn,$user,$pwd,array(\PDO::ATTR_ERRMODE=>\PDO::ERRMODE_EXCEPTION));
$mytable=new \DB\SQL\Mapper($db,'mytable');
try {
$mytable->id='duplicate_id';
$mytable->save();//this will throw an exception
} catch(\PDOException $e) {
$err=$e->errorInfo;
echo $err[2];//PRIMARY KEY must be unique
}
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'm using the following script to use a database using PHP:
try{
$db = new PDO('mysql:host='.$host.';port='.$port.';dbname='.$db, $user, $pass, $options);
}
catch(Exception $e){
$GLOBALS['errors'][] = $e;
}
Now, I want to use this database handle to do a request using this code:
try{
$query = $db->prepare("INSERT INTO users (...) VALUES (...);");
$query->execute(array(
'...' => $...,
'...' => $...
));
}
catch(Exception $e){
$GLOBALS['errors'][] = $e;
}
Here is the problem:
When the connection to the DB is OK, everything works,
When the connection fails but I don't use the DB, I have the $GLOBALS['errors'][] array and the script is still running afterwards,
When the connection to the DB has failed, I get the following fatal error:
Notice: Undefined variable: db in C:\xampp\htdocs[...]\test.php on line 32
Fatal error: Call to a member function prepare() on a non-object in C:\xampp\htdocs[...]\test.php on line 32
Note: Line 32 is the $query = $db->prepare(...) instruction.
That is to say, the script crashes, and the try/catch seems to be useless. Do you know why this second try/catch don't works and how to solve it?
Thanks for the help!
EDIT: There are some really good replies. I've validated one which is not exactly what I wanted to do, but which is probably the best approach.
try/catch blocks only work for thrown exceptions (throw Exception or a subclass of Exception must be called). You cannot catch fatal errors using try/catch.
If your DB connection cannot be established, I would consider it fatal since you probably need your DB to do anything meaningful on the page.
PDO will throw an exception if the connection cannot be established. Your specific problem is that $db is not defined when you try to call a method with it so you get a null pointer (sort of) which is fatal. Rather than jump through if ($db == null) hoops as others are suggesting, you should just fix your code to make sure that $db is either always defined when you need it or have a less fragile way of making sure a DB connection is available in the code that uses it.
If you really want to "catch" fatal errors, use set_error_handler, but this still stops script execution on fatal errors.
In PHP7, we now can using try catch fatal error with simple work
try {
do some thing evil
} catch (Error $e) {
echo 'Now you can catch me!';
}
But usualy, we should avoid using catch Error, because it involve to miss code which is belong to programmer's reponsibility :-)
I will not report what has already been written about testing if $db is empty. Just add that a "clean" solution is to artificially create an exception if the connection to the database failed:
if ($db == NULL) throw new Exception('Connection failed.');
Insert the previous line in the try - catch as follow:
try{
// This line create an exception if $db is empty
if ($db == NULL) throw new Exception('Connection failed.');
$query = $db->prepare("INSERT INTO users (...) VALUES (...);");
$query->execute(array(
'...' => $...,
'...' => $...
));
}
catch(Exception $e){
$GLOBALS['errors'][] = $e;
}
Hope this will help others!
If database connection fails, $db from your first try .. catch block will be null. That's why later you cannot use a member of non-object, in your case $db->prepare(...). Before using this add
if ($db) {
// other try catch statement
}
This will ensure that you have db instance to work with it.
Try adding the following if statement :
if ($db) {
$query = $db->prepare("INSERT INTO users (...) VALUES (...);");
$query->execute(....);
}
else die('Connection lost');
try{
if(!is_null($db))
{
$query = $db->prepare("INSERT INTO users (...) VALUES (...);");
$query->execute(array(
'...' => $...,
'...' => $...
));
}
}
catch(Exception $e){
$GLOBALS['errors'][] = $e;
}
I have a pretty large site and every page is built from several included files, my site is 100% in a procedural format and I am trying to learn to use classes and a more OOP approach in PHP.
Currently my site has a header file that is included into every page, in this header is a mysql connection that is made and last the duration of the page, so if I need to run 10 different queries from different files, they all run without needing to make a new connection, so the connection is only made once.
Now that I am trying to convert to a more OO way, I am starting with writing a mysql class to connect and run queries, so I am thinking of using the classes __construct function to make a connection to mysql, I am just curious how this would work though, everytime that class gets called it would make or try to make a connection to mysql instead of just once.
Maybe I am not thinking it out clearly. Should I just initiate this class in the header 1 time and then I wont have to worry anymore?
You could create a single global object of your MySQL class and use that object everywhere. Then your constructor would only be called once.
Or you could create new objects of your MySQL class everywhere. mysql_connect doesn't open new connections if there already is one open:
If a second call is made to mysql_connect() with the same arguments, no new link will be established, but instead, the link identifier of the already opened link will be returned.
The best way I think is to use a special class to handle mysql connections and use it as a singleton. Make the constructor private and get it to return an instance of either an existing connection or a new one.
Here's my example:
class db
{
public $host;
public $user;
public $pass;
public $database;
private static $instance = false;
private function __construct()
{
}
public static function getInstance()
{
if (self::$instance === false)
{
self::$instance = new db;
}
return self::$instance;
}
public function db_connect()
{
}
public function db_disconnect()
{
}
}
This way, whenever you call: db::getInstance()->db_connect(), you are certain there's only going to be ONE instance of that connection everywhere.
Yes, you shouldn't connect multiple times. The overhead of opening and closing the connection all the time is bigger than the cost of keeping it open during the relative small time your scripts run. So you should create an instance of the class at the start and keep it in a global variable.
It's certainly not a bad idea to write your own classes as a exercise, but maybe you should look into one of the existing solutions for database connection management (Zend_Db etc.).
You can always store the database link reference in a STATIC class variable and call it whenever needed. However, PHP by itself tries to use an existing link if it exists in the memory.
I've a sample database handler code for you, ofcourse its PHP 5 and uses PDO.
<?php
// Class providing generic data access functionality
class DatabaseHandler
{
// Hold an instance of the PDO class
private static $_mHandler;
// Private constructor to prevent direct creation of object
private function __construct()
{
}
// Return an initialized database handler
private static function GetHandler()
{
// Create a database connection only if one doesn’t already exist
if (!isset(self::$_mHandler))
{
// Execute code catching potential exceptions
try
{
// Create a new PDO class instance
self::$_mHandler =
new PDO(PDO_DSN, DB_USERNAME, DB_PASSWORD);
// Configure PDO to throw exceptions
self::$_mHandler->setAttribute(PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION);
self::$_mHandler->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
}
catch (PDOException $e)
{
// Close the database handler and trigger an error
self::Close();
trigger_error($e->getMessage(), E_USER_ERROR);
}
}
// Return the database handler
return self::$_mHandler;
}
// Clear the PDO class instance
public static function Close()
{
self::$_mHandler = null;
}
// Wrapper method for PDO::prepare
private static function Prepare($queryString)
{
// Execute code catching potential exceptions
try
{
// Get the database handler and prepare the query
$database_handler = self::GetHandler();
$statement_handler = $database_handler->prepare($queryString);
// Return the prepared statement
return $statement_handler;
}
catch (PDOException $e)
{
// Close the database handler and trigger an error
self::Close();
trigger_error($e->getMessage(), E_USER_ERROR);
}
}
// Wrapper method for PDOStatement::execute()
public static function Execute($sqlQuery, $params = null)
{
// Try to execute an SQL query or a stored procedure
try
{
$statement_handler = self::Prepare($sqlQuery);
// Execute query
$statement_handler->execute($params);
}
// Trigger an error if an exception was thrown when executing the SQL query
catch(PDOException $e)
{
// Close the database handler and trigger an error
self::Close();
trigger_error($e->getMessage(), E_USER_ERROR);
}
}
// Wrapper method for PDOStatement::fetchAll()
public static function GetAll($sqlQuery, $params = null,
$fetchStyle = PDO::FETCH_ASSOC)
{
// Initialize the return value to null
$result = null;
// Try to execute an SQL query or a stored procedure
try
{
$statement_handler = self::Prepare($sqlQuery);
// Execute the query
$statement_handler->execute($params);
// Fetch result
$result = $statement_handler->fetchAll($fetchStyle);
}
// Trigger an error if an exception was thrown when executing the SQL query
catch(PDOException $e)
{
// Close the database handler and trigger an error
self::Close();
trigger_error($e->getMessage(), E_USER_ERROR);
}
// Return the query results
return $result;
}
// Wrapper method for PDOStatement::fetch()
public static function GetRow($sqlQuery, $params = null,
$fetchStyle = PDO::FETCH_ASSOC)
{
// Initialize the return value to null
$result = null;
// Try to execute an SQL query or a stored procedure
try
{
$statement_handler = self::Prepare($sqlQuery);
// Execute the query
$statement_handler->execute($params);
// Fetch result
$result = $statement_handler->fetch($fetchStyle);
}
// Trigger an error if an exception was thrown when executing the SQL query
catch(PDOException $e)
{
// Close the database handler and trigger an error
self::Close();
trigger_error($e->getMessage(), E_USER_ERROR);
}
// Return the query results
return $result;
}
// Return the first column value from a row
public static function GetOne($sqlQuery, $params = null)
{
// Initialize the return value to null
$result = null;
// Try to execute an SQL query or a stored procedure
try
{
$statement_handler = self::Prepare($sqlQuery);
// Execute the query
$statement_handler->execute($params);
// Fetch result
$result = $statement_handler->fetch(PDO::FETCH_NUM);
/* Save the first value of the result set (first column of the first row)
to $result */
$result = $result[0];
}
// Trigger an error if an exception was thrown when executing the SQL query
catch(PDOException $e)
{
// Close the database handler and trigger an error
self::Close();
trigger_error($e->getMessage(), E_USER_ERROR);
}
// Return the query results
return $result;
}
}
?>
You should pass a connection object (probably PDO) around, and the various places should be able to pick that up, either as a parameter, or as a property of some central object which the others have a reference to, or something.
Having multiple connections can be useful, it seems insane that mysql_connect picks up an existing connection when you might have wanted a new one - but it's insane anyway. Just use PDO.
You can use that method if you use mysql_pconnect() function, wich will search if there's already a mysql connection and in case it finds it, it wouldn't create another one.
In alternative, if you consider nor to use instances in php, you can call php database object directly, like :
class DB {}
DB::connect($host, $user, $pass);
If you use this method, you don't need to worry about multiple connections. Of course that if you need to use several connections to more than one database at a time, you can make use of object instances, or make your class so it can take several parameters and store them all at once (not very recomemded this one)