Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP by-reference parameters and default null

Let's say we have a method signature like

public static function explodeDn($dn, array &$keys = null, array &$vals = null,
    $caseFold = self::ATTR_CASEFOLD_NONE)

we can easily call the method by omitting all parameters after $dn:

$dn=Zend_Ldap_Dn::explodeDn('CN=Alice Baker,CN=Users,DC=example,DC=com');

We can also call the method with 3 parameters:

$dn=Zend_Ldap_Dn::explodeDn('CN=Alice Baker,CN=Users,DC=example,DC=com', $k, $v);

and with 4 parameters:

$dn=Zend_Ldap_Dn::explodeDn('CN=Alice Baker,CN=Users,DC=example,DC=com', $k, $v, 
    Zend_Ldap_Dn::ATTR_CASEFOLD_UPPER);

But why is it impossible to call the method with the following parameter combination for example:

$dn=Zend_Ldap_Dn::explodeDn('CN=Alice Baker,CN=Users,DC=example,DC=com', $k, null, 
    Zend_Ldap_Dn::ATTR_CASEFOLD_UPPER);
$dn=Zend_Ldap_Dn::explodeDn('CN=Alice Baker,CN=Users,DC=example,DC=com', null, $v);

What's the difference between passing null to the method and relying on the default value? Is this constraint written in the manual? Can it be circumvented?

like image 479
Stefan Gehrig Avatar asked Nov 11 '08 09:11

Stefan Gehrig


6 Answers

It's because you can't have a reference to null.

You can have a reference to a variable that contains null - that is exactly what the default value does. Or you can pass in null as a literal value - but since you want an out parameter this is not possible here.

like image 185
Tomalak Avatar answered Sep 22 '22 15:09

Tomalak


While you must create a dummy variable for by-ref arguments if you want to pass NULL explicitly, you don't have to create that variable on a separate line. You can use an assignment expression like $dummy=NULL directly as a function argument:

function foo (&$ref = NULL) {

    if (is_null($ref)) $ref="bar";
    echo "$ref\n";        
}

foo($dummy = NULL); //this works!
like image 40
Szczepan Hołyszewski Avatar answered Sep 25 '22 15:09

Szczepan Hołyszewski


@ Tomalak

Actually, the default value creates a variable without any reference involved. Which is something you simply cannot kick off when you pass something.

What i find to illustrate the reasons is the following example (which i did not test):

function foo (&$ref = NULL) {
  $args = func_get_args();
  echo var_export($ref, TRUE).' - '.var_export($args, TRUE);
}
$bar = NULL;
foo();     // NULL - array()
foo($bar); // NULL - array(0 => NULL)

In my opinion, PHP should offer a way to NOT pass certain parameters, like with
foo($p1, , , $p4); or similar syntax instead of passing NULL.
But it doesn't, so you have to use dummy variables.

like image 24
josh Avatar answered Sep 26 '22 15:09

josh


I just found out this myself, and I'm quite in shock o_O!

This is what the PHP documentation says:

function makecoffee($type = "cappuccino")
{
    return "Making a cup of $type.\n";
}
echo makecoffee(); // returns "Making a cup of cappuccino."
echo makecoffee(null); // returns "Making a cup of ."
echo makecoffee("espresso"); // returns "Making a cup of espresso."

I would have expected makecoffee(null) to return "Making a cup of cappuccino.". One work-around I have used is to check inside the function if the argument is null:

function makecoffee($type = null)
{
    if (is_null($type)){ 
       $type = "capuccino";
    }
    return "Making a cup of $type.\n";
}

Now makecoffee(null) returns "Making a cup of cappuccino."

(I realize this doesn't actually solve the Zend-related question, but it might be useful to some...)

like image 28
Rafa Avatar answered Sep 25 '22 15:09

Rafa


Just to confirm what Tomalak stated here:

The following works:

$k=array();
$v=null;
$dn=Zend_Ldap_Dn::explodeDn('CN=Alice Baker,CN=Users,DC=example,DC=com', $k, $v, 
    Zend_Ldap_Dn::ATTR_CASEFOLD_UPPER);

Not nice - but the explanation is clear and comprehensible.

like image 23
Stefan Gehrig Avatar answered Sep 26 '22 15:09

Stefan Gehrig


As @aschmecher pointed out in a comment on @Szczepan's answer here, doing func($var = null) generates a strict standards notice.

One solution

Here's a method that does not generate any such warnings:

<?php
error_reporting(E_ALL | E_STRICT);

function doIt(&$x = null) {
    if($x !== null) echo "x not null: $x\n";
    $x = 2;
}

function &dummyRef() {
    $dummyRef = null;
    return $dummyRef;
}

doIt(dummyRef());

doIt(dummyRef());

Explanation

In place of passing in a variable, we pass in the result of a function returning a reference. The second call to doIt(dummy()) is to verify that the reference $dummy value is not persisting between calls. This contrasts with creating a variable explicitly, where one needs to remember to clear any accumulated value:

$dummyRef = null;
doIt($dummyRef);
doIt($dummyRef); // second call would print 'x not null: 2'

Application

So in the OP's example, it would be:

$dn = Zend_Ldap_Dn::explodeDn(
    'CN=Alice Baker,CN=Users,DC=example,DC=com',
    $k,
    dummyRef(),
    Zend_Ldap_Dn::ATTR_CASEFOLD_UPPER
);

Additional considerations

One thing I might worry is whether this method creates a memory leak. The following test shows that it doesn't:

<?php
function doItObj(&$x = null) {
    if(gettype($x) !== "object") echo "x not null: $x\n";
    $x = 2;
}

function &dummyObjRef() {
    $dummyObjRef = new StdClass();
    return $dummyObjRef;
}

echo "memory before: " . memory_get_usage(true) .  "\n";

for($i = 0; $i < 1000000; $i++) {
    doItObj(dummyObjRef());
}

echo "memory after: " . memory_get_usage(true) . "\n";

echo "\n$i\n";

On my system (using PHP 5.6.4), both calls to memory_get_usage showed ~ 262 KB.

like image 26
Chris Middleton Avatar answered Sep 22 '22 15:09

Chris Middleton