113

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/...

Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
Ignacio
  • 7,947
  • 15
  • 63
  • 74

11 Answers11

147

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.)

Dharman
  • 30,962
  • 25
  • 85
  • 135
JasonWoof
  • 4,176
  • 1
  • 19
  • 28
  • I'm not sure the difference between those two, but I'll investigate some. Thanks, your answer was great too. – Ignacio Sep 08 '09 at 03:29
  • 3
    I think this might be a better answer than mine (if it indeed works) – Joe Phillips Mar 04 '10 at 19:51
  • my tests on PHP 5.3.8 + Mysql show that there is no difference in the generated query between the two. The important part seems to be that the passed value is NULL and not '' or '0' – Odin Nov 06 '11 at 18:19
  • 2
    I had trouble with PDO::PARAM_NULL on MySql 5.1.53, but PDO::PARAM_INT with a null value worked great. – Will Shaver Dec 05 '11 at 19:33
  • 2
    Odin: I think the most important part is to pass all three parameters :). in ign's question he was passing PDO::PARAM_NULL as the value, instead of as the type (and not passing a type at all.) – JasonWoof Dec 17 '11 at 09:30
  • 1
    FYI: in most cases you are completely fine with using `bindValue()` over `bindParam()`; for everything, not just for the `NULL` value. I can't think of a single case where you would need to bind the parameter to a reference of a PHP variable - but that's what `bindParam()` does. – low_rents Dec 03 '15 at 10:17
49

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);
Joe Phillips
  • 49,743
  • 32
  • 103
  • 159
  • You got that right, I just realized I had done it before in my code, $null = PDO::PARAM_NULL; thanks. – Ignacio Sep 08 '09 at 03:31
  • 1
    It's better to use bindValue() in this case, if you're going to make placeholders anyway. bindParam() is originally intended to execute a query, and then change the variables and re-execute without binding the parameters again. bindValue() binds immediately, bindParam() only on execute. –  Oct 07 '15 at 08:49
  • 1
    I found that null must be lowercase in the $myNull = null line. $myNull = NULL did NOT work. – raphael75 Nov 28 '17 at 15:11
28

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

ChrisF
  • 857
  • 9
  • 9
  • 1
    $stmt->execute(array( ':param' => !empty($val) ? $val : null )); Use !empty because for empty string you would also want to set null in database – Shehzad Nizamani Jun 30 '17 at 19:36
  • 1
    @ShehzadNizamani Only if the column type is integer, like in his example, yes. But otherwise `isset()` correlates better to `NULL`. An empty string is also a value. – StanE Nov 17 '17 at 02:25
  • @ShehzadNizamani what about `0` value? empty() will return true for it. and your code will store null instead of a legit 0 integer value – Your Common Sense Feb 13 '21 at 08:00
9

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.

Pedro Guglielmo
  • 119
  • 1
  • 4
5

I had the same problem and I found this solution working with bindParam :

    bindParam(':param', $myvar = NULL, PDO::PARAM_INT);
user1719210
  • 310
  • 3
  • 11
4

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.

mdfst13
  • 850
  • 8
  • 18
3

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,

Arian Acosta
  • 6,491
  • 1
  • 35
  • 32
  • 3
    Or, you can use MySQL's `IFNULL()` function in the query string. Like this: `$sql = "INSERT INTO products ( productId, productName ), VALUES ( IFNULL(:productId, NULL), IFNULL(:productName, NULL) )";` – starleaf1 Dec 10 '14 at 05:22
  • I like the cleanliness your solution. I just tested it, but unfortunately it inserts empty strings instead of NULL when the user doesn't write anything on the input. It's just a matter of preference, but I rather have NULLs instead of empty strings. – Arian Acosta Dec 14 '14 at 23:10
-1

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.

Vincent
  • 1,741
  • 23
  • 35
-1

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);
        }
  • This code makes absolutely no sense, using assignment operators in the conditional statement. Assuming you want to convert a string 'NULL' to a null value, for this **highly unusual** (and unrelated) case only **one** line is needed, `$this->companyid = $this->companyid === 'NULL' ? NULL : $this->companyid;` – Your Common Sense Dec 14 '22 at 05:21
  • the `$this->companyid == NULL` condition still makes no sense as you basically wrote `if ($this->companyid == NULL) {$this->companyid = NULL;}` – Your Common Sense Dec 15 '22 at 06:42
  • What you probably want is `if ($this->companyid == 'NULL' || !$this->companyid)` to cover all null-ish values (such as false, empty string, zero and such) as well as literal word null. But still your problem is outside of the scope of this question. – Your Common Sense Dec 15 '22 at 08:11
-2

Try This.

$stmt->bindValue(':v1', null, PDO::PARAM_NULL); // --> insert null
hector teran
  • 369
  • 1
  • 5
  • 15
-2

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).

centurian
  • 1,168
  • 13
  • 25