Cannot run mysql stored procedure in laravel4 - php

Have some php code:
// Fetch PDO instance
$pdo = $this->conn->getPdo();
// Call procedure that checks is user credentials is ok
$stmt = $pdo->prepare('CALL Users_CheckLogin(:username, :password);');
$passw = md5($credentials['passw']);
$stmt->bindValue(':username', $credentials['username']);
$stmt->bindValue(':password', $passw);
// Execute procedure
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_CLASS, 'ArrayObject');
$stmt->closeCursor();
For some reasons this code doesn't work here and returns:
SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll(). Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.
I also googled about this error and haven't found any fix for this. So perhaps someone here can explain me what the problem is? I don't have paralels queries, only my procedure, inside this procedure I executing additional queries so perhaps that a reason but then it is huge bug in pdo. This procedure call works in mysql manager and with mysqli also.

Related

What is the best practice for calling stored procedures using the PHP mysqli interface to MariaDB?

I am attempting to port an 8-year-old PHP/MySQL web app to a more recent server stack using MariaDB instead of MySQL. I have found it impossible to run more than one stored procedure on the same connection due to a "packets out of order" error. Below is code that should work, but doesn't. Can someone point out where I may have gone astray here or what would be a successful alternative approach?
<?php
$host = "localhost";
$user = "mysqli_test";
$password = "";
$database = "mysqli_test";
function get_connection()
{
GLOBAL $host, $user, $password, $database;
$connection = new mysqli($host, $user, $password, $database);
if (! $connection || mysqli_connect_errno() )
printf("Connection failure: %s.\n", mysqli_connect_error());
return $connection;
}
// Minimum viable function: isolate necessary steps.
function get_person($connection, $first_name)
{
$query = "CALL Get_By_First_Name(?)";
if (($stmt = $connection->prepare($query))) // error here after first pass
{
$stmt->bind_param('s', $first_name);
if ($stmt->execute())
{
$stmt->store_result();
$stmt->bind_result($id, $fname, $lname, $pets);
while($stmt->fetch())
printf("%3d %20s %20s %2d.\n", $id, $fname, $lname, $pets);
$stmt->free_result();
while ($stmt->next_result()) // my suspected culprit
{
$stmt->store_result();
while ($stmt->fetch())
;
$stmt->free_result();
}
}
$stmt->close();
}
}
if ($conn = get_connection())
{
get_person($conn, "Samuel"); // it works the first time
get_person($conn, "Zelda"); // this time it fails
}
?>
Running aPHP/mysqli code,
lmost identical C++ code using the C API works fine, so I can isolate where I think the problem starts: with the next_result() function. In the C++ code, next_result() returns TRUE to indicate a new result was available after using the prepared statement to run the stored procedure. In the PHP/mysqli code, next_result() returns false, and, in fact, fails to produce a new result even if I ignore the false return value.
I created a github repository that includes more explanation and scripts that can run on your computer to replicate the error, if anyone is interested.
The best practice is to avoid using stored procedures from PHP... That is not always possible; sometimes stored procedures are necessary and they might even be useful on rare occasions. But if you can, try to move the logic to the PHP application rather than storing it on a MySQL server. It's much less cumbersome this way.
If you want to know how to call stored procedures correctly, the best resource to reach for is the PHP manual. I have recently improved most of the examples in the manual so I know that the examples there reflect best practices and actually work. Read stored procedures using mysqli and mysqli::multi_query() documentation.
I would advise avoiding mysqli::multi_query(), despite stored procedures being probably the primary reason that function even exists. You have made the right choice to use prepared statements so you can bind the parameters and avoid SQL injection.
The main thing you have to remember is that CALL() statement produces an empty result. If the stored procedure also produces result set/sets then you need to iterate over them and fetch each one. The problem with stored procedures is that you can never be certain how many result sets will be produced. How can you handle the unknown?
Take a look at this example (it's your code but I made some changes and enabled error reporting):
function get_connection($host, $user, $password, $database): mysqli
{
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
return new mysqli($host, $user, $password, $database);
// you might want to set the correct charset (e.g. utf8mb4) here before returning
}
function get_person(mysqli $connection, $first_name): mysqli_result
{
$query = "CALL Get_By_First_Name(?)";
$stmt = $connection->prepare($query);
$stmt->bind_param('s', $first_name);
$stmt->execute();
/* We expect this SP to return the main result set,
which we want to return and then an empty result for CALL.
Get the result here and return immediately.
The prepared statement will be closed automatically once
we leave the scope and this will clean up the remaining result set.
*/
return $stmt->get_result();
}
$conn = get_connection($host, $user, $password, $database);
$res1 = get_person($conn, "Samuel");
$res2 = get_person($conn, "Zelda");
var_dump($res1->fetch_all(), $res2->fetch_all());
In the above code, I make an assumption that the SP I am calling will return only two result sets. I want only the first one and I don't care about the result of CALL(). The second result set is discarded when the statement is cleaned up. If that was not the case, I would need to call mysqli_stmt::next_result() until all result sets are fetched (not the mysqli::next_result()!). This is the easiest way to handle the cumbersome stored procedures.
Even if you converted the code to PDO, this would still be the simplest way to do it. With mysqli things can get very complicated if you go over the board though, so be careful not to overengineer the solution. If your stored procedures use cursors, note that there was a bug in PHP up to PHP 7.4.

Why can't I catch the error with PDOException?

Get info passed by POST method, and trim all space in the string, then start a new pdo instance, connect mysql, and insert info passed by POST into table.
$title = trim($_POST["title"]);
$content = trim($_POST["content"]);
$dsn = "mysql:host=localhost;dbname=blog";
$con = new PDO($dsn,"root","xxxx");
$title = $con->quote($title);
$content = $con->quote($content);
try
{
$sql = "insert into tmp (`title`,`content`) values('$title','$content')";
$stmt = $con->prepare($sql);
$stmt->execute();
}
catch(PDOException $e)
{
echo $e->getMessage();
}
The above is my PHP code to make the job done,the most import command is
insert into tmp (`title`,`content`) values('$title','$content')";
No error info is shown by running the above PHP code, and no error exists in /var/log/mysql/error.log, but info has not been inserted into the database.
I changed the
insert into tmp (`title`,`content`) values('$title','$content')";
into
insert into tmp (`title`,`content`) values($title,$content)";
The info passed by POST can be inserted into mysql now, the issue that confuses me is that:
echo $e->getMessage(); take no effect at all.
no error info in /var/log/mysql/error.log
How can I catch these errors?
The exception you are trying to catch will never be thrown, because you need to tell PDO how you want it to handle errors.
$con = new PDO($dsn,"root","xxxx");
$con->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Otherwise, the default PDO::ERRMODE_SILENT will be used:
This is the default mode. PDO will simply set the error code for you to inspect using the PDO::errorCode() and PDO::errorInfo() methods on both the statement and database objects; if the error resulted from a call on a statement object, you would invoke the PDOStatement::errorCode() or PDOStatement::errorInfo() method on that object. If the error resulted from a call on the database object, you would invoke those methods on the database object instead.
Tangentially, you should be using prepared statements. You are using a prepare() call, but you are not parametrizing the query and binding the variables as you should. Using quote() is not secure enough.
2020 Update:
Interestingly, starting with PHP 8, the default behaviour for PDO will change and will throw exceptions by default. The change was voted on this RFC, which mentions:
The current default error mode for PDO is silent. This means that when an SQL error occurs, no errors or warnings may be emitted and no exceptions thrown unless the developer implements their own explicit error handling.
This causes issues for new developers because the only errors they often see from PDO code are knock-on errors such as “call to fetch() on non-object” - there's no indication that the SQL query (or other action) failed or why.
When PHP 8 is released on November 2020, the default error mode will be PDO::ERRMODE_EXCEPTION.

What is the purpose of the "if" statement in a mysqli prepared statement

Based off what I found online you should use this for error handling but I am still confused on the purpose of this. If before establishing a connection I have a check in place to make sure the connection has been established and if I have a query that does not take any user input such as "select * from table" why would I need to use the the "if" statement when preparing my query?
This is the code block I'm referencing
if ($stmt = $mysqli->prepare($query)) {
...
}
Basically, that would allow you to handle cases where the prepare fails.
See the prepare manual:
mysqli_prepare() returns a statement object or FALSE if an error occurred.
If the prepare fails, $stmt will be false rather than an object, and any other methods you try to call on it will result in fatal errors, so the idea with that if check is to detect the error earlier so you can handle the failure more gracefully.
... else {
$mysqli->error; // log this and/or react to it somehow
}

how to execute multiple statements in loop

Hallo I have several queries to execute, all returning independent resultsets:
select * from table;
call procedureA(par1);
call procedureB(par2);
I would like to execute them within a loop to perform other operations:
$queries = array("select * from table;", "call procedureA(par1);", "call procedureB(par2);");
foreach($queries as $query) {
$res=$db->query($query);
// do something here with the query result
someFunction($res);
}
The first statement runs fine; at the second iteration, it stops stating that $res is a non object:
Call to a member function ... on a non-object
Apart from using mysqli_multi_query(), in which way could I loop execute multiple queries?
UPDATE I removed $res->close(); from the code sample since it was misleading and ininfluent for the issue.
UPDATE2 - SOLUTION
For anyone's sake, here is a complete working code:
$queries = array(
"CALL procedureA(par1)"
,"CALL procedureB()"
, "Select * from tableC"
);
// Open connection
$db = new mysqli(
$config['host']
,$config['user']
,$config['pwd']
,$config['dbname']
);
foreach($queries as $query) {
if ($res instanceof mysqli_result) {
$res->free();
}
$res=$db->query($query);
// do something with the query
echo getHtmlTable($res);
// free current result
$res->free();
// free subsequent resultset (es. the one with OK/ERR sp call status)
while ($db->next_result()) {
//free each result.
$res = $db->use_result();
if ($res instanceof mysqli_result) {
$res->free();
}
}
}
There is nothing special in running queries in a loop.
There will be no difference, either if you write two queries just one after another, or run them in a loop. So, generally speaking, a couple of queries run in a loop is no different from a couple of queries run in order just like in all our scripts.
The only possible problem a query itself. Say, a stored procedure call always return at least two resultsets. And no other query can be run until all resultsets get retrieved.
So, as a quick-and-dirty solution a line like this could be added
while ($db->next_result()) {}
at the bottom of the loop to clean up all the resultsets possibly remained in a queue after the query execution.
It qould be also highly convenient to turn on error reporting for mysqli, to make you aware of all the mysql error occurred. Having that, you would have added such error message to your question (Which is "Commands out of sync"). To do so, add this line before mysqli connect:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$r->close();
should be
$res->close();
You are referencing a non existent object ($r).
The execution of multiple queries depends of a server configuration. But aside from thas. This would be better to use transactions (that depending of the effect of those stored procedures, of course)

Stored procedure errors in CakePHP

Any idea how to call a stored procedure in CakePHP?
$results = $this->query('call p2');
echo $results;
I keep getting this error though:
Error: SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered
queries are active. Consider using PDOStatement::fetchAll(). Alternatively, if your code
is only ever going to run against mysql, you may enable query buffering by setting the
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.
I've done this within a transaction to ensure that no other instance of the procedure is called inbetween:
$this->begin();
$this->query("CALL procedure();");
$result = $this->query("SELECT something");
$this->commit();
Your problem may be that you're calling:
$this->query('call p2');
Where you should be calling:
$this->query('call p2()');
as procedures are much like functions.
you should you open and close parentheses
$results = $this->query('call p2()');
echo $results;

Categories