Non-Object Errors using PHP PDO with MySQL - php

I've been scratching my head over this for a few days now and I've still got nowhere.
I'm trying to pull a small set of values from a MySQL database via PHP PDO; I know PDO works as I am using it else where and I have based my code around te previously working code.
function custom_sub_cat_list($db_details, $cat_id) { //ln21
$subcat = NULL;
try {
$h = new PDO("mysql:host=".$db_details['host'].";dbase=".$db_details['db'],$db_details['user'],$db_details['pass']);
$h->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch(PDOException $ex) {
return false;
}
try {
$q = $h->prepare("SELECT category FROM :tbl WHERE parentid = :catid;");
$q->bindValue(":tbl", $db_details['tbl'], PDO::PARAM_STR);
$q->bindValue(':catid', $cat_id, PDO::PARAM_STR);
$q->execute();
while($row = $_query->fetch()) {
$subcat['id'][] = $row['categoryid'];
$subcat['name'][] = $row['category'];
};
return $subcat;
} catch(PDOException $ex) {
return false;
}
}//ln49
I am getting "Call to a member function bindValue() on a non-object" on the bindValue's and being called up like below.
$cat_id = 123;
$db_details = array(
"host" => $sql_host,
"db" => $sql_db,
"user" => $sql_user,
"pass" => $sql_password,
"tbl" => $sql_tbl['categories']
);
custom_sub_cat_list ($db_details, $cat_id)
I'm sure it's something glaringly obvious but I can't see the problem and would like a fresh pair of eyes.
WORKING VERSION BELOW
Thank You Very Very Much! to everyone who helped, I've learnt a few bits too :-) There was some silly mistakes in there that I had overlooked, I just blame looking at it for two days solid.
function custom_sub_cat_list($db_details, $cat_id) {
$subcat = NULL;
try {
$h = new PDO("mysql:host=".$db_details['host'].";dbname=".$db_details['db'].";charset=utf8",$db_details['user'],$db_details['pass']);
$h->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$h->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$q = $h->prepare("SELECT category, categoryid FROM ".$db_details['table']." WHERE parentid = :cid;");
$q->bindParam(':cid', $cat_id, PDO::PARAM_INT);
$q->execute();
while($row = $q->fetch()) {
$subcat['id'][] = $row['categoryid'];
$subcat['name'][] = $row['category'];
};
$h = NULL;
return $subcat;
} catch(PDOException $ex) {
print_r($ex->getMessage());
print_r($ex->getTrace());
//return false;
}
}

You don't need a fresh pair of eyes
You are not painter but a programmer (supposedly).
So, instead of watching your code you have to run it. And enable error reporting.
Oh, just spotted it
And of course, you shouldn't gag error messages!
} catch(PDOException $ex) {
return false;
}
a modern version of # operator.
Please get rid of ALL try..catch blocks in your code and start using them only after learning what are they for.
So, in order to solve this very problem as well as many other problems in the future
Get rid of all try..catch blocks in your code.
Enable error reporting for PDO as described in tag wiki I linked to in the comments.
Do not use placeholders for the identifiers but format them as described in the tag wiki I linked to
Turn off display_errors setting if you don't want errors to be displayed (the only reason for suppressing error messages I can think of).
Also, you shouldn't open separate connection in every function call.
Create one connection in the beginning of your script and then use if in the function, using
global $h;

$q = $h->prepare("SELECT category FROM :tbl WHERE parentid = :catid;");
You can't use a bound parameter for a table name in SQL.
When you try to prepare an invalid SQL statement like this, prepare() returns false, not a PDOStatement object.
$q->bindValue(":tbl", $db_details['tbl'], PDO::PARAM_STR);
Then you try to call a bindValue() method on $q, but the scalar value false has no methods, so it's a fatal error.
You're also trying to catch exceptions, but you didn't configure PDO to throw exceptions on error. So the PDO functions default to reporting errors simply by returning false.
As #Arkh shows, you need to tell PDO to throw exceptions:
$h->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

So, your error tells you that $q is not an object. Which means that the $h->prepare("SELECT category FROM :tbl WHERE parentid = :catid;"); failed.
When reading the PDO::prepare doc, you can read this:
Return Values
If the database server successfully prepares the statement, PDO::prepare() returns a PDOStatement object. If the database server cannot successfully prepare the statement, PDO::prepare() returns FALSE or emits PDOException (depending on error handling).
As you have not set PDO to throw exceptions, you should either do it to get a good error message or check the value of $q and get the last reported error.
First method:
function custom_sub_cat_list($db_details, $cat_id) { //ln21
$subcat = NULL;
try {
$h = new PDO("mysql:host=".$db_details['host'].";dbase=".$db_details['db'],$db_details['user'],$db_details['pass']);
$h->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$h->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$q = $h->prepare("SELECT category FROM :tbl WHERE parentid = :catid;");
$q->bindValue(":tbl", $db_details['tbl'], PDO::PARAM_STR);
$q->bindValue(':catid', $cat_id, PDO::PARAM_STR);
$q->execute();
while($row = $_query->fetch()) {
$subcat['id'][] = $row['categoryid'];
$subcat['name'][] = $row['category'];
};
return $subcat;
} catch(PDOException $ex) {
echo $ex->getMessage();
return false;
}
}//ln49
Which gives the error:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '? WHERE parentid = ?' at line 1
Second method:
function custom_sub_cat_list($db_details, $cat_id) { //ln21
$subcat = NULL;
$h = new PDO("mysql:host=".$db_details['host'].";dbase=".$db_details['db'],$db_details['user'],$db_details['pass']);
$h->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$q = $h->prepare("SELECT category FROM :tbl WHERE parentid = :catid;");
if ($q === false) {
print_r($h->errorInfo());
return false;
}
$q->bindValue(":tbl", $db_details['tbl'], PDO::PARAM_STR);
$q->bindValue(':catid', $cat_id, PDO::PARAM_STR);
$q->execute();
while($row = $_query->fetch()) {
$subcat['id'][] = $row['categoryid'];
$subcat['name'][] = $row['category'];
};
return $subcat;
}//ln49
Result:
Array ( [0] => 42000 1 => 1064 [2] => You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '? WHERE parentid = ?' at line 1 )
Your problem is that you cannot use a bound value for :tbl (or other non values parts of your SQL string).
So you have to do this:
$q = $h->prepare("SELECT category FROM ".$db_details['tbl']." WHERE parentid = :catid;");

Related

array_merge() issue with PDO

I have a function which should return me an array like this
Array
(
[company1] => position1
[company2] => user2
)
I have a proper SQL query which fetches me the required data just fine (tested in MySQL workbench and by dumping the data.
The function that does the thing (see the comment in CAPS at the line with issue):
function check_user_status ($db){
try {
$log = new PDO("mysql:host=".$db['server'].";dbname=".$db['db'], $db['mysql_login'], $db['mysql_pass'], array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));
// set the PDO error mode to exception
$log->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// prepare sql and bind parameters
$stmt=$log->prepare ("select a.companyname,b.role from companies a, roles b where a.companyid=b.companyid and (b.uid = :uid and b.suspended = 0);");
$stmt->bindParam(":uid", $_SESSION['uid'], PDO::PARAM_INT);
$stmt->execute();
$count=$stmt->rowCount();
//echo "<br>count = $count<br>"; //outputs the numbner of resulting rows for debuging
if($count >=1) {
unset ($_SESSION['status']); //purge any previous user_status
$_SESSION['status'] = array();
while ($userRow = $stmt->fetch(PDO::FETCH_ASSOC)) {
//echo "<br>{$userRow['companyname']} => {$userRow['role']}<br>"; outputs the data for debuging
array_merge ($_SESSION['status'], array($userRow['companyname'] => $userRow['role']));// THIS DOESN'T WORK FOR SOME REASON
}
return true;
}
else
{
//return false;
}
}
catch(PDOException $e) {
return false;
echo 'error: '. $e->getMessage();
}
}
Now this:
check_user_status($db);
var_dump ($_SESSION['status']);
Outputs me empty array:
array(0) { }
array_merge_recursive() doesn't work either. Notices are enabled, no notice is thrown. Any help would be highly appreciated.
RTM: http://php.net/array_merge
Return Values: Returns the resulting array.
You have:
array_merge ($_SESSION['status'], array($userRow['companyname'] => $userRow['role']));
// THIS DOESN'T WORK FOR SOME REASON
"for some reason" = you're completely IGNORING the return value, which is your merged array.

Can I use try catch exceptions into PDO transactions?

Here is my script:
$id = $_GET['id'];
$value = $_GET['val'];
// database connection here
try{
$db_conn->beginTransaction(); // this
$stm1 = $db_conn->prepare("UPDATE table1 SET col = "updated" WHERE id = ?");
$stm1->execute(array($value));
$done = $stm->rowCount();
if ($done){
try {
$stm2 = $db_conn->prepare("INSERT into table2 (col) VALUES (?)");
$stm2->execute(array($id));
} catch(PDOException $e){
if ((int) $e->getCode() === 23000) { // row is duplicate
$stm3 = $db_conn->prepare("DELETE FROM table2 WHERE col = ?");
$stm3->execute(array($id));
}
}
} else {
$error = true;
}
$db_conn->commit(); // this
}
catch(PDOException $e){
$db_conn->rollBack();
}
First of all, I have to say, my script works. I mean the result or it is as expected in tests. Just one thing scares me. I read the documention and seen this sentence:
Won't work and is dangerous since you could close your transaction too early with the nested commit().
I'm not sure what's the meaning of sentence above, just I understand maybe I shouldn't use nested try - catch between beginTransaction() and commit(). Well I got it right? doing that is dangerous?
Exceptions have no direct relation to transactions. You can add as many catch blocks in your code as you need.
So your code is all right, given you already set PDO error reporting to Exceptions.

No error is thrown on inexistant table

I must miss something but I have a very strange behaviour with PDO (MySQL).
$req = null;
try {
$sql = 'INSERT INTO inexistant_table (idmember) VALUES(:idmember)';
$req = $db->prepare($sql);
$req->bindParam(':idmembre', $_SESSION['ID']);
$req->execute();
}
catch (PDOException $e) {
echo 'exception';
}
if( !$req ) {
echo 'false';
}
echo 'success';
Then I don't get any error, it only prints 'success'. Any idea?
EDIT: $db->errorCode() returns 00000.
The outcome is explained as such,
Exceptions are not enabled - no "exception"
To enable exceptions, as per Fred -ii-'s comment, thanks! ;-)
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
(Also see Reference - frequently asked questions about PDO)
The wrong value is being checked - no "false"
The $req variable represents the prepared statement object, not the result of such executing such a statement. Compare with the following that checks the result.
$result = $req->execute();
// ..
if (!$result) { /* fail! */ }

PDO adding single quotes to bound parameters

I'm using Slim framework to build a RESTful back end for a site, using PHP's PDO to query the database. However, when I'm trying to bind parameters to the prepared statement, I get the following error:
string(206) "SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''10'' at line 1"`
It looks as though PDO is surrounding my bound parameter with single quotes. My route code is as follows:
$app->get("/members/next10/:offset", function($offset) use($app) {
$app->response()->header('Content-Type', 'application/json');
try {
$pdo = connectToDatabase();
$query = $pdo->prepare("SELECT * FROM `members` LIMIT 10 OFFSET :offset");
$query->bindParam(":offset", $offset, PDO::PARAM_INT);
$query->execute();
$result = $query->fetchAll(PDO::FETCH_ASSOC);
if (!empty($result)) {
echo json_encode($result);
} else {
$app->response()->status(404);
}
} catch (PDOException $d) {
$app->response()->status(500);
var_dump($d->getMessage());
}
});
Am I missing something? I've tried grabbing :offset from the URL and assigning it to $offset cast as an integer before binding but that doesn't make a difference, either.
Possible solution: casting the param as an integer properly. If, in the first line of the method, I use $offset = (int) $offset; it works. Previously I was trying to do the cast in the $query->bindParam() method.
Route looks like:
$app->get("/members/next10/:offset", function($offset) use($app) {
$offset = (int) $offset;
$app->response()->header('Content-Type', 'application/json');
try {
$pdo = connectToDatabase();
$query = $pdo->prepare("SELECT * FROM `members` LIMIT 10 OFFSET :offset");
$query->bindParam(":offset", $offset, PDO::PARAM_INT);
$query->execute();
$result = $query->fetchAll(PDO::FETCH_ASSOC);
if (!empty($result)) {
echo json_encode($result);
} else {
$app->response()->status(404);
}
} catch (PDOException $d) {
$app->response()->status(500);
var_dump($d->getMessage());
}
});

PHP PDO MySQL Transaction code structure

I am trying to set up my first transaction in MySQL using PHP/PDO...
I just have a quick question, what is the best way to determine if the previous query was successful or not? Here is what I have right now, but I would rather find a way to test the query with an if statement.
This is pretty much mock up code to try to get a working model.. I know $results isn't effectively testing if anything was good or bad.. i have it there more as a place holder for the real deal when the time comes..
if ($_POST['groupID'] && is_numeric($_POST['groupID'])) {
$sql = "SET AUTOCOMMIT=0";
$dbs = $dbo->prepare($sql);
$dbs->execute();
$sql = "START TRANSACTION";
$dbs = $dbo->prepare($sql);
$dbs->execute();
$sql = "DELETE FROM users_priveleges WHERE GroupID=:groupID";
$dbs = $dbo->prepare($sql);
$dbs->bindParam(":groupID", $_POST['groupID'], PDO::PARAM_INT);
$dbs->execute();
try {
$sql = "DELETE FROM groups WHERE GroupID=:groupID LIMIT 1";
$dbs = $dbo->prepare($sql);
$dbs->bindParam(":groupID", $_POST['groupID'], PDO::PARAM_INT);
$dbs->execute();
$results["error"] = null;
$results["success"] = true;
try {
$sql = "DELETE FROM users WHERE Group=:groupID";
$dbs = $dbo->prepare($sql);
$dbs->bindParam(":groupID", $_POST['groupID'], PDO::PARAM_INT);
$dbs->execute();
$results["error"] = null;
$results["success"] = true;
$sql = "COMMIT";
$dbs = $dbo->prepare($sql);
$dbs->execute();
}
catch (PDOException $e) {
$sql = "ROLLBACK";
$dbs = $dbo->prepare($sql);
$dbs->execute();
$results["error"] = "Could not delete associated users! $e";
$results["success"] = false;
}
}
catch (PDOException $e)
{
$sql = "ROLLBACK";
$dbs = $dbo->prepare($sql);
$dbs->execute();
$results["error"] = "COULD NOT REMOVE GROUP! $e";
$results["success"] = false;
}
}
Some general notes:
Don't use bindParam() unless you use a procedure that modifies the parameter's value
Therefore, use bindValue(). bindParam() accepts argument value as a referenced variable. That means you can't do $stmt->bindParam(':num', 1, PDO::PARAM_INT); - it raises an error.
Also, PDO has its own functions for controlling transactions, you don't need to execute queries manually.
I rewrote your code slightly to shed some light on how PDO can be used:
if($_POST['groupID'] && is_numeric($_POST['groupID']))
{
// List the SQL strings that you want to use
$sql['privileges'] = "DELETE FROM users_priveleges WHERE GroupID=:groupID";
$sql['groups'] = "DELETE FROM groups WHERE GroupID=:groupID"; // You don't need LIMIT 1, GroupID should be unique (primary) so it's controlled by the DB
$sql['users'] = "DELETE FROM users WHERE Group=:groupID";
// Start the transaction. PDO turns autocommit mode off depending on the driver, you don't need to implicitly say you want it off
$pdo->beginTransaction();
try
{
// Prepare the statements
foreach($sql as $stmt_name => &$sql_command)
{
$stmt[$stmt_name] = $pdo->prepare($sql_command);
}
// Delete the privileges
$stmt['privileges']->bindValue(':groupID', $_POST['groupID'], PDO::PARAM_INT);
$stmt['privileges']->execute();
// Delete the group
$stmt['groups']->bindValue(":groupID", $_POST['groupID'], PDO::PARAM_INT);
$stmt['groups']->execute();
// Delete the user
$stmt['users']->bindParam(":groupID", $_POST['groupID'], PDO::PARAM_INT);
$stmt['users']->execute();
$pdo->commit();
}
catch(PDOException $e)
{
$pdo->rollBack();
// Report errors
}
}
I wouldn't prepare & execute the transaction statements. I'd use PDO::beginTransaction() , PDO::commit(), and PDO::rollback().
PDO::prepare() and PDO::execute() return FALSE if there's an error, or else they throw PDOException if you setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION).
In your exception handler, you should check PDO::errorInfo() and report the nature of the error. Best practice is to log the raw error info, but give the user a more friendly message.
Don't echo the literal error message in the UI -- this can give the user inappropriate knowledge about your SQL query and schema.
PDO Statement's execute() returns TRUE on success and FALSE on failure, so you can test the return value of the previous execute() in your if statement.
$pdo_result = $dbs->execute();
if ($pdo_result) {
// handle success
} else {
// handle failure
// you can get error info with $dbs->errorInfo();
}
That said, as #Bill Kerwin correctly points out (in his answer that I'm totally upvoting because it's exactly correct), it would be preferable to use PDO::beginTransaction(), PDO::commit(), and PDO::rollback().

Categories