I have a database that I've build using PDO, and I am building some database tests using phpunit. However, I need to test some cases involved with pdo exec(), that returns a false or an int. What I want to know is: In which case pdo exec() will return false?
For example: I have this method that creates my database table. When my pdo exec() will be false?
public function createTable(): \PDO
{
try {
$createTable = "
CREATE TABLE IF NOT EXISTS Transactions(
idTransaction int NOT NULL PRIMARY KEY AUTO_INCREMENT,
description varchar(255) NOT NULL,
price float NOT NULL,
category varchar(45) NOT NULL,
date varchar(10) NOT NULL,
type TINYINT NOT NULL);";
$this->pdo->exec($createTable);
return $this->pdo;
} catch (\PDOException $PDOException) {
throw new \PDOException($PDOException->getMessage());
}
}
If PDO connection was not configured with param PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION (which is default behavior), exec() method will return false instead of throwing a PDOException. See this answer https://stackoverflow.com/a/8992933/6051839. In this case the last error happened will be accessible via PDO::errorCode() and PDO::errorInfo() methods, see this example https://www.php.net/manual/en/pdo.errorcode.php#refsect1-pdo.errorcode-examples.
So, to get false from exec(), just remove setting PDO::ATTR_ERRMODE attribute (if you set it now) and try to execute invalid sql, for example.
P.S. And as other commenters said, always check the result via strict comparison, since it can be 0 (which means no error, just no rows affected by query). I.e.
NOT
$result = $pdo->exec(...);
if (!$result) {// error!}
BUT
$result = $pdo->exec(...);
if ($result === false) {// error!}
Related
I am having the issue that I need to be able to put a NULL value in a mysql database. So from the frontend I send a basic javascript object:
{
"Info": {
"Name": "Michelangelo",
"Date" : null
}
}
In my PHP file I do get the request correctly and decode it:
if(isset($postdata) && !empty($postdata)) {
// Extract the data.
$request = json_decode($postdata);
}
It works for strings, booleans, ints correctly but not for null values. It will output in PHP after decoding like null and not NULL. As far as I know the only correct value in PHP is NULL:https://www.php.net/manual/en/language.types.null.php
I put the values like this in DB. Date column is of type date and it accepts NULL values. However with null it will be converted to 0000-00-00;
$sql = "UPDATE `users` SET
`Username`='$request->Info->Name',
`Date`='$request->Info->Date' //DB accepts NULL but not null (tested)
WHERE `id` = '{$id}' LIMIT 1";
So why does it not convert correctly? I know I can loop over the object and replace all the null with NULL values, but since the object has nesting it will be a major headache. How can I solve this and why does this happen? I would prefer to do this with PHP and not in the sql query.
The problem is because you are putting the "null" between single quotes!
`Date`='$request->Info->Date'
Solution: Use bind parameters in your prepared statement.
https://www.php.net/manual/en/pdo.prepare.php
$pdo = new PDO($dsn, $user, $pass, $options);
$sth = $pdo->prepare("UPDATE `users` SET
`Username`=:username, `Date`=:date
WHERE `id` = :id LIMIT 1");
$sth->bindParam(':username', $request->Info->Name, PDO::PARAM_STR);
$sth->bindParam(':date', $request->Info->Date);
$sth->bindParam(':id', $id);
$sth->execute();
You should build some validation class where it will return the proper (sanitized) values from the Request that you are going to use in the query.
Consider creating some class/logic like
final class InfoRequest
{
/** #var array */
private $info;
public function __construct($request)
{
$this->info = $request->Info;
}
public function date(): ?string
{
$date = $this->info['Date'];
if (!$this->isValidDate($date)) {
throw NotValidDateException();
}
if (!$date) {
return null;
}
$dateTime = new \DateTime($this->info['Date']);
return $dateTime->format('Y-m-d');
}
private function isValidDate($date): bool
{
return true; // TODO: Not implemented yet
}
public function name(): string
{
$name = $this->info['Name'];
if (!$this->isValidName($name)) {
throw NotValidNameException();
}
return $name;
}
private function isValidName($name): bool
{
return true; // TODO: Not implemented yet
}
}
Usage:
$info = new InfoRequest($request);
$sth->bindParam(':username', $info->name(), PDO::PARAM_STR);
Apart from that, you should never pass the direct input from the request into a raw query to the DB.
It's generally a good idea not to take the values directly from user input (no matter how much you think you should trust the source). See also obligatory xkcd.
Since you're now mapping and cleansing the data as you build your query (probably using prepared statements), it should be trivial to map the value null to the string "NULL" (or just let the prepared statement library handle it).
The problem is not related to the variations of NULL or null.
In PHP they are the same thing.
You are probably running in MySQL NO_ZERO_DATE mode, which fills null dates with zeroes. The command below changes the way that null dates are handled by MySQL.
SET sql_mode = 'NO_ZERO_DATE';
More info here.
I'm using this code and I'm beyond frustration:
try {
$dbh = new PDO('mysql:dbname=' . DB . ';host=' . HOST, USER, PASS);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$dbh->setAttribute(PDO::MYSQL_ATTR_INIT_COMMAND, "SET NAMES 'utf8'");
}
catch(PDOException $e)
{
...
}
$stmt = $dbh->prepare('INSERT INTO table(v1, v2, ...) VALUES(:v1, :v2, ...)');
$stmt->bindParam(':v1', PDO::PARAM_NULL); // --> Here's the problem
PDO::PARAM_NULL, null, '', all of them fail and throw this error:
Fatal error: Cannot pass parameter 2 by reference in /opt/...
You need to use bindValue, not bindParam
bindParam takes a variable by reference, and doesn't pull in a value at the time of calling bindParam. I found this in a comment on the PHP docs:
bindValue(':param', null, PDO::PARAM_INT);
P.S. You may be tempted to do this bindValue(':param', null, PDO::PARAM_NULL); but it did not work for everybody (thank you Will Shaver for reporting.)
When using bindParam() you must pass in a variable, not a constant. So before that line you need to create a variable and set it to null
$myNull = null;
$stmt->bindParam(':v1', $myNull, PDO::PARAM_NULL);
You would get the same error message if you tried:
$stmt->bindParam(':v1', 5, PDO::PARAM_NULL);
When using INTEGER columns (that can be NULL) in MySQL, PDO has some (to me) unexpected behaviour.
If you use $stmt->execute(Array), you have to specify the literal NULL and cannot give NULL by variable reference.
So this won't work:
// $val is sometimes null, but sometimes an integer
$stmt->execute(array(
':param' => $val
));
// will cause the error 'incorrect integer value' when $val == null
But this will work:
// $val again is sometimes null, but sometimes an integer
$stmt->execute(array(
':param' => isset($val) ? $val : null
));
// no errors, inserts NULL when $val == null, inserts the integer otherwise
Tried this on MySQL 5.5.15 with PHP 5.4.1
For those who still have problems (Cannot pass parameter 2 by reference), define a variable with null value, not just pass null to PDO:
bindValue(':param', $n = null, PDO::PARAM_INT);
Hope this helps.
I had the same problem and I found this solution working with bindParam :
bindParam(':param', $myvar = NULL, PDO::PARAM_INT);
If you want to insert NULL only when the value is empty or '', but insert the value when it is available.
A) Receives the form data using POST method, and calls function insert with those values.
insert( $_POST['productId'], // Will be set to NULL if empty
$_POST['productName'] ); // Will be to NULL if empty
B) Evaluates if a field was not filled up by the user, and inserts NULL if that's the case.
public function insert( $productId, $productName )
{
$sql = "INSERT INTO products ( productId, productName )
VALUES ( :productId, :productName )";
//IMPORTANT: Repace $db with your PDO instance
$query = $db->prepare($sql);
//Works with INT, FLOAT, ETC.
$query->bindValue(':productId', !empty($productId) ? $productId : NULL, PDO::PARAM_INT);
//Works with strings.
$query->bindValue(':productName',!empty($productName) ? $productName : NULL, PDO::PARAM_STR);
$query->execute();
}
For instance, if the user doesn't input anything on the productName field of the form, then $productName will be SET but EMPTY. So, you need check if it is empty(), and if it is, then insert NULL.
Tested on PHP 5.5.17
Good luck,
Several answers have given examples of what you should do. But they haven't really explained why you should do one of those things.
The bindParam method is meant to be used with something like a loop (or just repeated statements). It binds a variable reference. So something like
$stmt = $dbh->prepare('INSERT INTO t1 (v1) VALUES(:v1)');
$stmt->bindParam(':v1', $i, PDO::PARAM_INT);
for ($i = 0; $i < 10; $i++) {
$stmt->execute();
}
Would insert values 0 through 9 in a table.
That's obviously a very simple example that could be implemented in other, more efficient ways. You could have more complex logic here. But the basic idea is that you bind a reference to a variable and then you can change the value of the variable.
You can get around the need for a reference by creating a variable before calling bindParam. But in your case, you don't particularly want to bind to a variable reference. You just want to bind a value. So go ahead and do exactly that with bindValue.
You can mostly just use bindValue. But to show why both methods exist, let's rewrite the previous example to use bindValue instead of bindParam:
$stmt = $dbh->prepare('INSERT INTO t1 (v1) VALUES(:v1)');
for ($i = 0; $i < 10; $i++) {
$stmt->bindValue(':v1', $i, PDO::PARAM_INT);
$stmt->execute();
}
This will work, but you have to call bindValue on every iteration of the loop whereas you only needed to call bindParam once. But you aren't doing anything like that, so you can just
$stmt->bindValue(':v1', null, PDO::PARAM_INT);
And everything will work, as stated in the accepted answer. Because you want to bind a value, not a variable reference.
Based on the other answers but with a little more clarity on how to actually use this solution.
If for example you have an empty string for a time value but you want to save it as a null:
if($endtime == ""){
$db->bind(":endtime",$endtime=NULL,PDO::PARAM_STR);
}else{
$db->bind("endtime",$endtime);
}
Notice that for time values you would use PARAM_STR, as times are stored as strings.
So you just need to add an extra If statement that properly changes your variable to NULL before you call bindParam(). Here is an example that I figured out for my situation (I was stuck on this for days trying to INSERT a new DB record with a NULL value for one column):
if ($this->companyid == 'NULL' || $this->companyid == NULL) {
$this->companyid = NULL;
$this->companyname = NULL;
$stmt->bindParam(':companyid', $this->companyid);
$stmt->bindParam(':companyname', $this->companyname);
} else {
$stmt->bindParam(':companyid', $this->companyid);
$stmt->bindParam(':companyname', $this->companyname);
}
Try This.
$stmt->bindValue(':v1', null, PDO::PARAM_NULL); // --> insert null
In my case I am using:
SQLite,
prepared statements with placeholders to handle unknown number of fields,
AJAX request sent by user where everything is a string and there is no such thing like NULL value and
I desperately need to insert NULLs as that does not violates foreign key constrains (acceptable value).
Suppose, now user sends with post: $_POST[field1] with value value1 which can be the empty string "" or "null" or "NULL".
First I make the statement:
$stmt = $this->dbh->prepare("INSERT INTO $table ({$sColumns}) VALUES ({$sValues})");
where {$sColumns} is sth like field1, field2, ... and {$sValues} are my placeholders ?, ?, ....
Then, I collect my $_POST data related with the column names in an array $values and replace with NULLs:
for($i = 0; $i < \count($values); $i++)
if((\strtolower($values[$i]) == 'null') || ($values[$i] == ''))
$values[$i] = null;
Now, I can execute:
$stmt->execute($values);
and among other bypass foreign key constrains.
If on the other hand, an empty string does makes more sense then you have to check if that field is part of a foreign key or not (more complicated).
When I run the prepare statement to insert some data the database, it returns false, however I can see the new row in the database, anyone knows why? I appreciate for any help. And it only happens on INSERT statement.
$query = "INSERT INTO BbUsers(u_username,u_passhash,u_emailaddr,u_validate) VALUES(?,?,?,?)";
$refArr = array("ssss", $username, $passhash, $email, $vaildHash);
$result = cleanQuery($query, $refArr);
if ($result2) {
sendEmail($email, $hash);
} else {
echo "OOps something went wrong plz try again";
exit;
}
Here is the function for prepare statement
function cleanQuery($prepareState,$Arrs)
{
global $dbcon;
$refArr=array();
$res=$dbcon->prepare($prepareState);
$ref= new ReflectionClass('mysqli_stmt');
$method=$ref->getMethod("bind_param");
foreach($Arrs as $key => &$value)
{$refArr[] = &$value;
}
$method->invokeArgs($res,$refArr);
$res->execute();
$result=$res->get_result();
return $result;
}
I tried to get error information from $res and $dbcon, which it returns 0.
Here's db looks like
CREATE TABLE BbUsers(
u_userid INTEGER PRIMARY KEY AUTO_INCREMENT ,
u_username CHAR(60) UNIQUE ,
u_passhash CHAR(100) ,
u_emailaddr CHAR(255) UNIQUE ,
u_created TIMESTAMP ,
u_lastlogin TIMESTAMP ,
u_validate CHAR(40),
INDEX (u_username , u_emailaddr)
);
Anything else I missed?
See the documentation:
mysqli_stmt::get_result
Returns a resultset for successful SELECT queries, or FALSE for other DML queries or on failure. The mysqli_errno() function can be used to distinguish between the two types of failure.
So $res->get_result() works only as you expect for select.
To check if your prepared statement was executed correctly, you can directly use the return parameter for execute:
mysqli_stmt::execute
Returns TRUE on success or FALSE on failure.
You should probably use two different functions, one for select-statements that return a result set (that you by the way have to fetch(), otherwise further queries won't work, read the note in the documentation to execute()), and one for other statements, that return true or false.
And you obviously have to replace if ($result2) { with if ($result) {, but I assume that is just a typo.
I have a database table with an unsigned int that represents a wallet balance. this column is called wallet. It is in a table called users.
The following query fails via mysql cli:
UPDATE users set wallet = `wallet` - 550000000 WHERE username = 'user'
With error message:
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in
'(database.users.wallet - 550000000)'
The issue is, when executed via PDO with ERRMODE_EXCEPTION, it brings wallet balance to 0 and continues execution without raising the mentioned error in an exception
SELECT ##sql_mode returns nothing in both the code and mysql cli.
Here is the function that creates my database handle and returns it to the query object
//This function connects to a database
public function connect($dbname,DBConfig $config = NULL)
{
//If the connection is already set return it
if(isset($this->dbs[$dbname]))
return $this->dbs[$dbname];
//If the config object is not already set, and still null, throw exception
if(!isset($this->_config[$dbname]))
{
if($config === NULL)
throw new PDOdatabasesException('Database configuration object is not set',1);
$this->_config[$dbname] = $config;
}
$config = $this->_config[$dbname];
//Create a PDO object
$this->dbs[$dbname] = new PDO(
$config::type .
':host=' . $config::$host .
';port=' . $config::$port .
';dbname=' . $dbname,
$config::$user,
$config::$password
);
//Tell the handle to throw exceptions
$this->dbs[$dbname]->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$this->dbs[$dbname]->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );
//Return a reference to the newly created PDO object
return $this->dbs[$dbname];
} //End connect()
Trying to save the need to paste a bunch of unnecessary code.. my query object takes a database handle from above, and prepares and executes this statement:
'UPDATE users SET wallet = wallet - ? WHERE id = ?'
binding the amount (approximately 10 million) and the user id, then executing.
Wallet balance can be at 0 and the query will still execute successfully. Why does this happen when the cli cannot? It does not make sense!!
I believe i need to once again reiterate This query SHOULD fail if it drops wallet below 0!
It succeeds through pdo, but not through mysql cli, my question is WHY??
The result of wallet - 550000000 should be less than 0 and your column is UNSIGNED. Try to change your column type from BIGINT UNSIGNED to BIGINT
This is apparently an unsolved bug in PDO::bindValue() and PDO::bindParam()
EDIT:
I am apparently stupid and did not realize that data_type was REQUIRED to be defined when binding an integer to a query.
I have simplified the code to be the following:
$db = new PDO('mysql:dbname=test;host=127.0.0.1','omitted','omitted');
$db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
try{
$statement = $db->prepare("UPDATE test set number = number - ? WHERE id = 1");
$statement->bindValue(1,1000000000);
$statement->execute();
}catch(PDOException $e)
{
die($e->getMessage());
}
changing the statement to (and not using bindValue or bindParam):
$statement = $db->prepare("UPDATE test set number = number - 100000000 WHERE id = 1");
then the code throws the expected exception
CHANGING AGAIN TO:
$statement>bindValue(1,1000000000,PDO::PARAM_INT);
throws as expected
I am having trouble binding a null parameter in the following code
$nullVariable = NULL;
$sql = new PDO('mysql:host=' . $Server, $User, $Password);
$sql->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$statement = $sql->prepare("SELECT * FROM Table WHERE Binary16Column = :uuid");
$statement->bindParam(":uuid", $nullVariable, PDO::PARAM_NULL);
$statement->execute();
$results = $statement->fetchAll(PDO::FETCH_ASSOC);
The results variable will be a empty array. If I dont use parameters and modify my query to "WHERE Binary16Column IS NULL" it returns the expected number of rows. So the problem must be with how I am handling the parameter, rather than my SQL query.
My code is more complex than listed above, and I need to be able to use a parameter variable which may be null, so checking to see the variable is null and running a different query is less than ideal. Technically I have my own function for setting parameters, this is where I am checking if the contents of the variable is null, and binding the parameter appropriately, so I dont have to write an unnecessary number of queries. The query works also works fine if the variable contains valid data, and the parameter type is PARAM_LOB.
Does anyone know what i'm doing wrong? Thanks a lot!
Read up on three-valued logic. NULL is not a value; it is a marker for the absence of a value, and so NULL can never be equal to anything, including itself.
However, there is a null-safe comparison operator also known as the "spaceship operator," which does consider two nulls to be equivalent.
WHERE Binary16Column <=> :uuid
... should do what you expected.
If you want to select the record with Binary16Column is null, you need to use IS NULL as the condition, but not = NULL.
SELECT * FROM Table WHERE Binary16Column IS NULL
You need to do:
$uuid = /**some value**/;
$sql = new PDO('mysql:host=' . $Server, $User, $Password);
$sql->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
if ($uuid === null) {
$statement = $sql->prepare("SELECT * FROM Table WHERE Binary16Column IS NULL");
} else {
$statement = $sql->prepare("SELECT * FROM Table WHERE Binary16Column = :uuid");
$statement->bindParam(":uuid", $uuid);
}
$statement->execute();
$results = $statement->fetchAll(PDO::FETCH_ASSOC);
I think the reason you are not getting a result because NULL is a keyword. Because of the way MySQL treats NULL values, I think you are going to have to do IS NULL, when you are performing a search for NULL values. I did a bunch of tests in my local database where I have NULL values. The only time that it worked is when I was using IS NULL or IS NOT NULL.
I am sorry I can't be more help (or if I'm just telling you what you already know), but it seems like you are going to have to write separate queries, or perhaps some simple logic to concatenate the appropriate WHERE logic, depending on whether a variable is null or not.