Тестирование необязательных аргументов в PHP

У меня есть несколько методов "setter" для всех классов, и для удобства я добавил необязательный параметр $previous, который принимает аргумент по ссылке и заполняет его существующим значением перед его заменой новым. Например:

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

Это прекрасно работает; однако в некоторых случаях соответствующий метод "геттера" является немного интенсивным процессом, и его безотказность является бесполезной. Я решил, что смогу проверить:

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

Это не работает, так как часто переменная, переданная как аргумент для $previous, ранее не была определена в ее области, и по умолчанию имеет значение null. Единственное решение, которое я взломал, это:

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;
}

Или в одну строку:

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

Мне не нравится, что тело метода зависит от индексов аргументов (когда кажется, что это необязательно) Есть ли более чистый способ достичь того, что я здесь сделал?


Я пробовал:

if(isset($previous)){}

if(!empty($previous)){}

if(null !== $previous){}

Ни одна из них не работает.

Возможные решения:

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 - Итак, что-то в области:

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

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

@hoppa - Случай использования:

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

Вместо:

$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();
    -> ...

Ответы

Ответ 1

возможно, не так, как вы хотели решить свою проблему (тестирование как-то необязательных аргументов), но я бы это реализовал:

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;
}

Пример использования примера:

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

Зачем использовать другую функцию?

Это решение имеет несколько преимуществ:

  • имя более явное, меньше путаницы для других кодеров
  • нет скрытых побочных эффектов
  • решает вашу проблему с переменными (undefined), уже имеющими значение
  • нет накладных расходов на вызов func_num_args или какой-либо другой функции "meta"

EDIT: опечатка в коде.

EDIT 2: удалено значение по умолчанию функции $previous set_get_value() (спасибо draevor)

Ответ 2

Извлечен из комментариев/обсуждений выше:

Чтобы проверить, был ли аргумент передан, у вас есть 2 варианта - проверьте значение аргумента на значение (как вы сделали с нулем) или проверьте количество аргументов.

Если вы переходите к первому варианту, нет значения, которое нельзя передать из-за пределов функции, поэтому всегда будет возможность для ложных срабатываний (то же самое, что сейчас происходит с нулем). Например, пример DaveRandom со случайной строкой должен быть достаточным для большинства случаев, но я считаю это излишним.

Я думаю, что второй вариант - самый чистый (быстрый, читаемый и т.д.). Как небольшое улучшение по сравнению с тем, что вы уже сделали с помощью func_get_args, я бы использовал func_num_args - таким образом вы будете проверять количество переданных аргументов, а не индексы аргументов.