PHP by-reference parameters and default null

2019-02-16 12:41发布

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?

6条回答
Rolldiameter
2楼-- · 2019-02-16 12:57

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

查看更多
走好不送
3楼-- · 2019-02-16 12:58

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.

查看更多
别忘想泡老子
4楼-- · 2019-02-16 13:09

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!
查看更多
别忘想泡老子
5楼-- · 2019-02-16 13:09

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

查看更多
狗以群分
6楼-- · 2019-02-16 13:10

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.

查看更多
Evening l夕情丶
7楼-- · 2019-02-16 13:16

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.

查看更多
登录 后发表回答