Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing optional arguments in PHP

I have a few "setter" methods across classes, and for convenience I've added an optional parameter $previous, which takes an argument by reference and populates it with the existing value before replacing it with the new one. For example:

public function set_value($key, $value, &$previous = null)
{
    $previous = $this->get_value($key);
    $this->_values[$key] = $value;
    return $this;
}

This works fine; however in some circumstances, the corresponding "getter" method is a bit process intensive, and running it unconditionally is a waste. I figured I could test:

if(null !== $previous)
{
    $previous = $this->get_value($key);
}

This doesn't work though, as often the variable passed as the argument for $previous hasn't been previously defined in it's scope, and defaults to null anyway. The only solution I've hacked out is:

public function set_value($key, $value, &$previous = null)
{
    $args = func_get_args();
    if(isset($args[2])
    {
        $previous = $this->get_value($key);
    }
    $this->_values[$key] = $value;
    return $this;
}

Or, to one-line it:

if(array_key_exists(2, func_get_args()))
{
    // ...
}

I don't like the method body being reliant on the argument indices (when it seems it should be unnecessary) Is there a cleaner way to achieve what I'm after here?


I've tried:

if(isset($previous)){}

if(!empty($previous)){}

if(null !== $previous){}

Neither work.

Possible solutions thus far:

if(func_num_args() == $num_params){}

if(array_key_exists($param_index, func_get_args())){}

// 5.4
if(isset(func_get_args()[$param_index])){}

// 5.4
if(func_num_args() == (new \ReflectionMethod(__CLASS__, __FUNCTION__))
    ->getNumberOfParameters()){}

@DaveRandom -- So, something in the area of:

define('_NOPARAM', '_NOPARAM' . hash('sha4096', microtime()));

function foo($bar = _NOPARAM)
{
    // ...
}

@hoppa -- Use case:

$obj->set_something('some_key', $some_value, $previous) // set
    ->do_something_that_uses_some_key()
    ->set_something('some_key', $previous) // and reset
    ->do_something_that_uses_some_key()
    -> ...

Instead of:

$previous = $obj->get_something('some_key'); // get
$obj->set_something('some_key', $some_value) // set
    ->do_something_that_uses_some_key();
    ->set_something($previous) // and reset
    ->do_something_that_uses_some_key();
    -> ...
like image 654
Dan Lugg Avatar asked Dec 19 '11 13:12

Dan Lugg


People also ask

What is optional argument in PHP?

Arguments that do not stop the function from working even though there is nothing passed to it are known as optional arguments. Arguments whose presence is completely optional, their value will be taken by the program if provided. But if no value is given for a certain argument, the program will not halt.

What is PHP default argument?

PHP allows you to use a scalar value, an array, and null as the default arguments.

CAN default arguments be skipped from a function call?

Default arguments can be skipped from function call​.


2 Answers

possibly not how you wanted to solve your problem (testing somehow optional arguments), but this is how I would implement it:

public function set_value($key, $value)
{
    $this->_values[$key] = $value;
    return $this;
}
public function set_get_value($key, $value, &$previous)
{
    $previous = $this->get_value($key);
    $this->_values[$key] = $value;
    return $this;
}

Use case example:

$obj->set_get_something('some_key', $some_value, $previous) // set AND get
    ->do_something_that_uses_some_key()
    ->set_something('some_key', $previous) // and reset
    ->do_something_that_uses_some_key()
    -> ...

Why use another function?

This solution has a few advantages:

  1. the name is more explicit, less confusion for other coders
  2. no hidden side effects
  3. solves your problem with (undefined) variables already having a value
  4. no overhead of calling func_num_args, or some other "meta" function

EDIT: typo in code.

EDIT 2: removed default value of &$previous set_get_value() function (thanks to draevor)

like image 80
catchmeifyoutry Avatar answered Oct 13 '22 00:10

catchmeifyoutry


Extracted from the comments / discussion above:

In order to check whether the argument was passed you have 2 options - check the value of the argument against a value (as you've done with null) or check the number of arguments.

If you go with the first option, there's no value that cannot be passed from outside the function, so there will always be a chance for false positives (the same thing that's happening now with null). DaveRandom's example with a random string should be enough for most cases though, but I see it as overkill.

I think the second option is the most clean (fast, readable, etc). As a small improvement over what you've already done with func_get_args, I'd use func_num_args - this way you'll be checking the number of passed arguments, not the argument indices.

like image 37
deviousdodo Avatar answered Oct 12 '22 23:10

deviousdodo