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 thequery()
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.");
}
}