Does mysqli::reap_async_query() have side effects? - php

I'm maintaining a project based on legacy code that was written by an external company. We are in the process of fixing a lot of PHP errors in this code base and one of them comes from a call to mysqli::reap_async_query() :
mysqli::reap_async_query(): Connection not opened, clear or has been
closed
This happens only on the first call to this function:
function mysqlQuery($sql, $getLastId = false, $die = true, $alwaysDie = false, $async = false)
{
#$GLOBALS['con']->reap_async_query(); // This is what is triggering the error
if ($async) {
$result = $GLOBALS['con']->query($sql, MYSQLI_ASYNC);
} else {
$result = $GLOBALS['con']->query($sql);
}
if (!$result) {
if ($alwaysDie or ($_ENV['ENV_MODE'] === 'dev' and $die)) {
die('Etwas stimmte mit dem Query nicht: ' . $GLOBALS['con']->error . '<br/>Ganzes Query: ' . $sql);
}
return false;
} else {
$lastId = mysqlLastId();
if ($getLastId == true) {
return $lastId;
} else {
return $result;
}
}
}
Note that $GLOBALS['con'] is an instance of mysqli.
The code is calling mysqlQuery() with no specific parameter:
$result = mysqlQuery("SELECT * FROM someTable");
According to the Git history, the call to #$GLOBALS['con']->reap_async_query(); was added to support async SQL queries. But reading the PHP doc, it doesn't seem to be useful here at all since we are not storing its return value.
So my question is: is there a reason for it to be here, does calling it even without reading its return value have any important side effect ?
I might just remove this call completely if it is useless.
Also, why is it triggering this error ? I understand that trying to read a result before any query has been executed could trigger an error but the error indicates that the connection is not active, which does not seem to be the case.

Is there a reason for it [mysqli::reap_async_query()] to be here, does calling it even without reading its return value has any important side effect ?
The return value is not assigned to a local variable, however it is still returned.
So the original interest was in calling that function. And for what for, has been written about in the commit message.
According to the Git history, the call to #$GLOBALS['con']->reap_async_query(); was added to support async SQL queries.
Let's consider this example:
$con->query('SELECT SLEEP(5) as `zzzZZZ...Schnarch...Schmatz..zzz`', MYSQLI_ASYNC);
$con->reap_async_query();
How long does it take to execute this code?
This is the reason how that call supports async queries. If an async query would still run on the connection (it has not been reaped yet), every new query would fail.
So add of the call in-so-far supports async SQL queries as it allows to fire one on the same (shared) connection that might be in use for other queries, too.
Additionally you ask:
Also, why is it triggering this error ? I understand that trying to read a result before any query has been executed could trigger an error but the error indicates that the connection is not active, which does not seem to be the case.
Let's take a look at the error, actually a message on the diagnostic channel:
PHP Warning: mysqli::reap_async_query(): Connection not opened, clear or has been closed in ...
As we know the connection has been opened and has not been closed, the last point might be it:
[...] Connection [...] clear [...]
Now I have not programmed that error message, but my reading of it is that there is no async query running yet on the (open) connection - the connection is clear.
It produces a warning as this might not be intended (there is no need to reap a clear connection normally) and henceforth as with your function this is intended, the call is prefixed with the error suppression operator (#).

Thanks to #hakre's answer i understand that this call is there to get rid of the queue of async queries and i plan to fix it with a static flag to check if one is pending before calling #$GLOBALS['con']->reap_async_query():
function mysqlQuery($sql, $getLastId = false, $die = true, $alwaysDie = false, $async = false)
{
static $asyncPending = false;
if (true === $asyncPending) {
#$GLOBALS['con']->reap_async_query();
$asyncPending = false;
}
if ($async) {
$result = $GLOBALS['con']->query($sql, MYSQLI_ASYNC);
$asyncPending = true;
} else {
$result = $GLOBALS['con']->query($sql);
}
if (!$result) {
if ($alwaysDie or ($_ENV['ENV_MODE'] === 'dev' and $die)) {
die('Etwas stimmte mit dem Query nicht: ' . $GLOBALS['con']->error . '<br/>Ganzes Query: ' . $sql);
}
return false;
} else {
$lastId = mysqlLastId();
if ($getLastId == true) {
return $lastId;
} else {
return $result;
}
}
}

Related

How to jump from one Function to another in PHP Class

I have a php script to pipe through a mail,
class mailTest
{
// (some code here)
private function saveToDb()
{
// (some code here)
$select = $this->pdo->query("SELECT * FROM tbl_reques WHERE terminal_id = $term AND request_status ='' ");
$select = $select->fetchAll();
if (count($select) > 0) {
echo "Call already Exist (DISCARD)";
} else {
$select_tech = $this->pdo->query("SELECT * FROM tbl_email WHERE terminal_id = $term");
$select_tech = $select_tech->fetchAll();
// (some code here)
}
}
private function sendEmail()
{
$this->today = time();
$this->maildate = date("Y-m-d H:i:s", strtotime('-5 minutes', $this->today));
$select = $this->pdo->query("Select * from tbl_reques WHERE maildate >= '$this->maildate' ");
// some code here
mail($this->from_email, $this->subject, $newmsg, $headers);
}
}
The problem is any time the condition is False i.e echo "Call already Exist (DISCARD)"; The code will not go to the Next Function. i.e the program get halt.
PLS is there a way that if that condition is not met, the program will JUMP to next function for continuation of execution. Or is it possible to use GOTO statement.
Pls what is the best way to handle this in PHP.
Thanks
You have a couple option for this. You can return at the point of failure. Which would exit the function at this point and then do whatever is next in the script being ran. Be sure to do the clean up before your return.
if(count($select) > 0) {
echo "Call already Exist (DISCARD)";
//Clean up if needed
return; //You could also return the message
//or an error code and have another
//evaluation based on that.
} else {
// Or passes
}
You could call the next function but this would be a very bad flow in my opinion
if(count($select) > 0) {
echo "Call already Exist (DISCARD)";
//Clean up if needed
$this->sendEmail();
} else {
// Or passes
}
The reason this would be bad is if say in the script you have
$mailTest = new mailTest();
$mailTest->saveToDb();
$mailTest->sendEmail(); //When the above fails this is called twice.
You could likewise throw an exception
if(count($select) > 0) {
echo "Call already Exist (DISCARD)";
throw new Exception("Call already Exist (DISCARD)");
} else {
// Or passes
}
Now you need to use try and catch
$mailTest = new mailTest();
try {
$mailTest->saveToDb();
catch (Exception $e){
//Do something with $e
//Clean up the failure if needed
}
$mailTest->sendEmail();
There is a finally block as well which would run in cases where your catch stops the script.
PHP in fact has a GOTO statement, see http://php.net/manual/de/control-structures.goto.php
However, it is considered bad style to use it, or in the words of #Konamiman
Unless you are programming in assembler, GOTO should always be treated the same way as the life vest of the airplanes: it is good to have them available, but if you need to use them it means that you are in big trouble.
You can call a function simply by writing its name followed by brackets. In the case of class functions you apply it to $this:
$this->sendEmail();

I'm struggling to upgrade login script with PDO

I have a custom query() function in my functions. file. I created a login.php file, but when I make a SQL query, the query() function returns a PDO object, instead of an associative array I desire. I need help to pass parameters to be bound to a stored procedure/prepared statement.
The following is the login.php file:
<?php
// configuration
require("../../includes/config.php");
// if form was submitted
if ($_SERVER["REQUEST_METHOD"] == "POST")
{
// validate submission
if (empty($_POST["username"]))
{
adminapologize("You must provide your username.");
}
else if (empty($_POST["password"]))
{
adminapologize("You must provide your password.");
}
$username = $_POST["username"];
// query database for user
$sql = "SELECT * FROM admin WHERE username = '$username'";
$result = query($sql,array($username));
//var_dump($result);
//exit;
if($sql != false)
{
if($result->rowCount() == 0)
{
printf("No admin yet.");
}
// if we found user, check password
if($result->rowCount() == 1)
{
// first (and only) row
$row = $result->fetch();
// compare hash of user's input against hash that's in database
if ($_POST["username"] == $row["username"] && crypt($_POST["password"], $row["hash"]) == $row["hash"])
{
// remember that user is now logged in by storing user's ID in session
$_SESSION["admin_id"] = $row["admin_id"];
// redirect to admin home
redirect("index.php");
}
}
}
else
{
// else apologize
adminapologize("Invalid username and/or password.");
}
}
else
{
// else render form
adminrender("login_form.php", ["title" => "Admin Log In"]);
}
?>
Be advised that the config.php includes the functions.php file. And the following is the portion of the functions.php file:
/**
* Executes SQL statement, possibly with parameters, returning
* a pdo statement object on success, handling and halting execution on error.
*/
function query($sql, $parameters = null)
{
static $pdo; // define the var as static so that it will persist between function calls
try
{
// if no db connection, make one
if (!isset($pdo))
{
// connect to database
// you should set the character encoding for the connection
$pdo = new PDO("mysql:dbname=" . DB_NAME . ";host=" . DB_SERVER, DB_USERNAME, DB_PASSWORD);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // set the error mode to exceptions
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false); // turn emulated prepares off
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC); // set default fetch mode to assoc so that you don't have to explicitly list the fetch mode every place
}
if(empty($parameters))
{
// no bound inputs
$stmt = $pdo->query($sql);
} else {
// has bound inputs
$stmt = $pdo->prepare($sql);
// you should use explicit bindValue() statements to bind any inputs, instead of supplying them as a parameter to the ->execute() method. the comments posted in your thread lists the reasons why.
$stmt->execute($parameters);
}
}
catch (Exception $e)
{
// all errors with the connection, query, prepare, and execute will be handled here
// you should also use the line, file, and backtrace information to produce a detailed error message
// if the error is due to a query, you should also include the $sql statement as part of the error message
// if $pdo ($handle in your code) is set, it means that the connection was successful and the error is due to a query. you can use this to include the $sql in the error message.
trigger_error($e->getMessage(), E_USER_ERROR);
//exit; // note: E_USER_ERROR causes an exit, so you don't need an exit; here.
}
return $stmt; // if the query ran without any errors, return the pdo statement object to the calling code
}
Your help would be much appreciated.
You've got an excellent function, no need to spoil it.
the query() function returns a PDO object, instead of an associative array I desire.
It's actually an object that you want to be returned. As for the array, you can simply get it by chaining fetch to the call:
$result = query($sql,array($username))->fetch(); // voila!
Look, with function returning an object you can get not only single row but dozens different kinds of results. Like single column value with fetchColumn() or many formats supported by fetchAll(). Not to mention you can get numRows() from the object while from array you can't.
With this function in its current form you can run DML queries as well, while returning fetch it will end up with error. Returning an object is a really really cool thing!
The only bad thing about your function is that you are catching an exception and converting it into an error manually, while PHP is already doing it for you.
Just get rid of this try catch block and you will have exactly the same (actually, even better) error reporting.
// all errors with the connection, query, prepare, and execute will be handled here
// you should also use the line, file, and backtrace information to produce a detailed error message
// if the error is due to a query, you should also include the $sql statement as part of the error message
// if $pdo ($handle in your code) is set, it means that the connection was successful and the error is due to a query. you can use this to include the $sql in the error message.
ALL these things PHP is already doing for you, if you simply don't catch an exception.
(save for storing an $sql variable which is not needed actually, as you can find a query following a back trace)
As of the code, it should be five times shorter:
$sql = "SELECT * FROM admin WHERE username = ?";
$row = query($sql,array($username))->fetch();
if($row && crypt($_POST["password"], $row["hash"]) == $row["hash"])
{
// remember that user is now logged in by storing user's ID in session
$_SESSION["admin_id"] = $row["admin_id"];
// redirect to admin home
redirect("index.php");
//this is essential as otherwise anyone will be able to proceed with this page
exit;
}
BTW, I just noticed that you are using your function wrong, sending $username right into the query. I fixed it too.
Edit according to #Your Common Sense's answer who's absolutley right:
Call your function 'query' and do directly on that result a fetch.
For example as mentioned in the comments below:
$rows = query($sql, $params)->fetch();
If you want rows as associative array do
$rows = query($sql, $params)->fetch(PDO::FETCH_ASSOC);
This explicitly returns an associative array.
You have to fetch the result after executing.
$stmt->execute($parameters);
return $stmt->fetch(PDO::FETCH_ASSOC);
This should return an associative array.
See also: http://php.net/manual/de/pdostatement.fetch.php
/**
* Executes SQL statement, possibly with parameters, returning
* a pdo statement object on success, handling and halting execution on error.
*/
function query($sql, $parameters = null)
{
static $pdo; // define the var as static so that it will persist between function calls
$return = false;
try {
// if no db connection, make one
if (!isset($pdo)) {
// connect to database
// you should set the character encoding for the connection
$pdo = new PDO("mysql:dbname=" . DB_NAME . ";host=" . DB_SERVER, DB_USERNAME, DB_PASSWORD);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // set the error mode to exceptions
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false); // turn emulated prepares off
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC); // set default fetch mode to assoc so that you don't have to explicitly list the fetch mode every place
}
if(empty($parameters)){
// no bound inputs
$stmt = $pdo->query($sql);
} else {
// has bound inputs
$stmt = $pdo->prepare($sql);
// you should use explicit bindValue() statements to bind any inputs, instead of supplying them as a parameter to the ->execute() method. the comments posted in your thread lists the reasons why.
$stmt->execute($parameters);
$return = $stmt->fetch(PDO::FETCH_ASSOC);
}
} catch (Exception $e)
{
// all errors with the connection, query, prepare, and execute will be handled here
// you should also use the line, file, and backtrace information to produce a detailed error message
// if the error is due to a query, you should also include the $sql statement as part of the error message
// if $pdo ($handle in your code) is set, it means that the connection was successful and the error is due to a query. you can use this to include the $sql in the error message.
trigger_error($e->getMessage(), E_USER_ERROR);
//exit; // note: E_USER_ERROR causes an exit, so you don't need an exit; here.
}
return $return; // if the query ran without any errors, return the pdo statement object to the calling code

How to understand if an update statement worked successfully?

In my program I want update some information, so I used:
return ($result->rowCount() == 1)? true: false;
this way, if you save information without any change, false is returned and this is not the result usually we expect.
I change my function to this one
try{
$result=$db->prepare($sql);
$result->execute($arr);
return true;
}catch(Exception $e){
return false;
}
Is it the best way? Does this way guarantee that the update statement worked or not?
Assuming your $db is instance of PDO
When you run PDO::prepare you can detect sql syntax error and when you run PDO::execute you will be sure that syntax is correct. Then application is resposible for knowing if update should have updated something or not.
So here is some sample code:
function update($db, $sql)
{
$preparedSql = $db->prepare($sql);
if(!$preparedSql) {
// syntax error occured
$errorInfo = $db->errorInfo();
// handle error
return false;
}
$db->execute($preparedSql);
// note that rowCount returns 0 when none updated
return $db->rowCount();
}
In your application
$result = update($db, $sql);
if($result === false)
{
// error occured
} elseif($result === 0) {
// zero rows updated
} else {
// some rows were updated
}
Note that it's not tested and for learning purposes only, update function should accept to bind parameters for real world application

PHP: my page takes a very long time to load but does not time out. Are there too many queries at once or can my code be optimized?

This is the function I use to access the DB, theoretically maybe up 10-20 times in a do while loop, though right now I removed the loop so it only can do one db query at a time. Can this be optimized more or is this how transactions and commits are properly done? Also, I don't know what $db->rollBack() actually does, I just saw it on a stackoverflow
<?php
function util_db_access($statement, $array) {
$db = new PDO('mysql:host=localhost;dbname=db;charset=UTF8', 'user', 'pass');
try {
//echo "1";
$db->beginTransaction();
//echo "2";
$stmt = $db->prepare($statement);
//echo "3";
if($stmt->execute($array)) {
$db->commit();
//echo "4";
if($rows = $stmt->fetchAll(PDO::FETCH_ASSOC)) {
//echo "5";
if($stmt->rowCount() < 2) {
$rows = $rows[0];
}
return $rows;
} else {
//echo "6.1";
//$db->commit();
return true;
}
} else {
//echo "6.2";
//$db->commit();
return false;
}
} catch(PDOException $e) {
$db->rollBack();
//log, we are gonna keep a log eventually.. right?
return -1;
}
}
?>
This thing can be optimized very quickly.
For starters you are creating a new connection to the database with every function call. I don't know for sure if the connection is closed when the PDO object goes out of scope but nevertheless it's bad design.
UPDATE
PHP will close the connection to the database when the PDO object is destroyed.
Reference http://php.net/manual/en/pdo.connections.php
Try using a profiler to determine where your bottleneck is - there's one included in xdebug. Given the simplicity of this code, it may be the query itself - try running the query standalone, either using the mysql cli client or MySQL Workbench, and see what timings you get back. If it is the query that's slow, you can use EXPLAIN and the bountiful optimization sections in the MySQL manual to improve it.
$db->rollBack() make a roll back for the transaction. If you don't know what transactions are there is no point for you to use them, because it creates an unnecessary overhead for the server.
commit() permanently writes the data from the query, rollback() undo everything to the state where you called beginTransaction()
Transactions are to be used when its crucial that changes in more then one place happens simultaneously, imagine a transaction of money between two accounts. On simple selects there is absolutely no need for transactions.
(I'm also sceptic to how you use try/catch)
However... If you run one query directly on the db server, is it also slow? Do you open/close the db connection between each call? (if so -(yes)- don't). What is the network relationship between the http/db server?
I can't help rewriting your function, removing useless parts and adding essential ones
function util_db_access($type, $statement, $array) {
static $db;
if (empty($db)) {
$dsn = 'mysql:host=localhost;dbname=db;charset=UTF8';
$opt = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
);
$db = new PDO($dsn, 'user', 'pass', $opt);
}
$stmt = $db->prepare($statement);
$stmt->execute($array);
if($type == 'all') {
return $stmt->fetchAll();
}
if($type == 'row') {
return $stmt->fetch();
}
if($type == 'one') {
return $stmt->fetchColumn();
}
if($type == 'id') {
return $db->lastInsertId();
}
if($type == 'numrows') {
return $stmt->rowCount();
}
}
most likely this new version won't suffer any delays too
now you can use this great function without any delay or inconvenience:
$user = util_db_access("row", "SELECT * FROM users WHERE id = ?", array($someId));
$id = util_db_access("id", "INSERT INTO users SET name=?", array($name));
and so on
However, having just a function for this purpose is quite inconvenient, and I hope you will grow it into class soon. You can take my Safemysql class as a source of some ideas.

Internal Server Error on a PHP app that is calling a function that calls MySQL database

Basically I am building a MVC app. In the Model, dbFunctions.php is this code:
<?php
require("config.php");
require("dbconnection.php");
class dbFunctions
{
// setting up the object to connect to the Database
function __construct()
{
$dbConnect = new dbConnection();
}
public function insertPortfolioAdminData($value='')
{
# code...
}
public function login($value='')
{
# code...
}
public function logout($value='')
{
# code...
}
public function dbStoreContactForm($value='')
{
# code...
}
// returns a query with a collection of database objects for the portfolio
public function fetchAllPortfolioItems()
{
$fetchAllPortfolioItemsReturnQry = mysql_query("SELECT description FROM PortfolioItems") or die ('Error: '.mysql_error ());
if($fetchAllPortfolioItemsReturnQry){
return $fetchAllPortfolioItemsReturnQry;
}
}
public function fetchSinglePortfolioItems($primaryKey='')
{
# code...
}
}
?>
dbConnection.php
<?php
require("config.php");
class dbConnection {
private $databaseConnection;
public function dbConnection(){
$databaseConnection = mysql_connect(dbHostName,dbUserName,dbPassword) or die(mysql_error());
mysql_select_db(dbDatabaseName) or die(mysql_error());
}
public function closeConnection(){
mysql_close($databaseConnection);
}
}
?>
The controller:
<?php
// Calling the class to do the work on database
require("./model/dbfunctions.php");
$dbMethods = new dbFunctions();
while($row = mysql_fetch_array($dbMethods->fetchAllPortfolioItems()))
{
$pageContent = $row["description"];
}
// calling the template
require("./views/page_12.php");
?>
Here's the error:
Internal Server Error
The server encountered an internal
error or misconfiguration and was
unable to complete your request.
Please contact the server
administrator,
webmaster#mvcportfolio.adambourg.com
and inform them of the time the error
occurred, and anything you might have
done that may have caused the error.
More information about this error may
be available in the server error log.
Additionally, a 404 Not Found error
was encountered while trying to use an
ErrorDocument to handle the request.
Basically I am trying to do all my DB work in the model then through the model pass it to the view to output it.
I see a few problems that may contribute to the error:
1 - You're using require for the "config.php" but you should really be using require_once. There's no need to load it more than once.
// Replace the require('config.php') with:
require_once('config.php');
2 - Are you defining constants in your "config.php"? The dbConnection::dbConnection() function is looking for constants named dbHostName, dbUserName, dbPassword, and dbDatabaseName. Make sure your "config.php" is defining constants.
3 - The while($row = mysql_fetch_array($dbMethods->fetchAllPortfolioItems())) in dbFunctions.php is wrong. The mysql_fetch_array portion of the while is "recalculated" on every iteration which means that it's executed over and over again. If you assign the value of $dbMethods->fetchAllPortfolioItems() to a variable first, the function is only executed once and the while will iterate through the results. Use this instead:
$result = $dbMethods->fetchAllPortfolioItems();
while($row = mysql_fetch_array($result))
{
$pageContent = $row["description"];
}
As per the while documentation:
It tells PHP to execute the nested
statement(s) repeatedly, as long as
the while expression evaluates to
TRUE.
The mysql_fetch_array($dbMethods->fetchAllPortfolioItems()) part of the while you're using will always evaluate to TRUE as long as the query return a row as it's getting called over and over (thus returning the same first row every single time).
The error that's causing the "Internal Server Error" is probably because your script is taking more than the max_execution_time allowed in php.ini.
It's maybe because you're never actually connecting to the database before you query it.
Essentially, you've made a function with the same name as the class for dbConnection instead of using the __construct method.
You will see a very clear error message in your server error log, in apache on linux:
/var/log/apache/error.log
and I would also take a look (if it is not clear from the previews log) at the mysql error log, which is also somewhere under /var/log/...

Categories