Как вызвать функцию внутри себя? - PullRequest
8 голосов
/ 23 октября 2009

У меня есть функция, которая генерирует ключ из 4 символов, который должен быть уникальным для каждого раза. Чтобы сделать это, функция сначала генерирует ключ, а затем проверяет таблицу базы данных, чтобы определить, используется ли она кем-то еще.

Если он не используется, он возвращает ключ, в противном случае он снова вызывает себя, но это заставляет функцию выполнять бесконечный цикл, который является нет-нет. Вот вся функция:

function key_generator($length = 4)
{
    // I've subsequently left out the generating code,
    // which is not necesarry in this case

    $key = 'xxxx';

    if ($this->user_model->valid_key($key) == true)
    {
        return $key;
    }
    else
    {
        $this->key_generator(4);
    }
}

Как правильно снова вызвать функцию?

Кстати, я использую CodeIgniter, поэтому $this.

Ответы [ 7 ]

25 голосов
/ 23 октября 2009

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

do {
    $key = ...; // Generate your key here...
} while (!$this->user_model->valid_key($key));

return $key;

Если вы находитесь рядом с максимальным количеством ключей, это приведет к очень длительному циклу, поэтому вы можете установить какое-то максимальное ограничение.

О, и если это происходит в нескольких потоках одновременно, и вы проверяете базу данных, вы должны реализовать блокировку записи в таблицу, чтобы один и тот же ключ не мог быть вставлен дважды. Предпочтительно функция, которая проверяет, доступен ли ключ, должна блокировать , проверять и, если доступно, записывать в той же транзакции, чтобы избежать любых коллизий.

5 голосов
/ 23 октября 2009

Вам необходимо вернуть результат самостоятельного вызова, в противном случае действительный ключ не будет возвращен после его повторения.

return $this->key_generator($length);
3 голосов
/ 23 октября 2009

но это заставляет функцию выполнять бесконечный цикл,

Если вы абсолютно хотите сохранить рекурсивную стратегию, вы должны определить конечный случай. Например, вы можете определить счетчик, например так:

function key_generator($length = 4, $limit=5)
{
    if($limit === 0) {
         throw new YourException();
    }

    // I've subsequently left out the generating code,
    // which is not necesarry in this case

    $key = 'xxxx';

    if ($this->user_model->valid_key($key) == true)
    {
        return $key;
    }
    else
    {
        return $this->key_generator(4, ($limit-1));
    }
}

Тем не менее, возможно также сделать ваш код итеративно ...

2 голосов
/ 23 октября 2009

Если вы включите достаточно уникальности в свою процедуру генерации ключей, вы, возможно, сможете избежать этой ситуации. Например. подпрограмма должна учитывать текущую временную метку и локальное имя хоста и / или PID.

Зацикливание таким недетерминированным образом обычно является доказательством того, что какая-то часть слишком наивна. Это не хорошо. : -)


Во всяком случае, по крайней мере, было бы хорошей практикой перехватывать ее и регистрировать какую-то ошибку, а не зависать запрос и, наконец, отключать время:

    function key_generator($length = 4)
    {
        /* The $attempts_left clearly depends on how much trust 
           you give your key generation code combined with the key space size. */
        $attempts_left = pow(16, $length) * 2;
        /* ... just guessing, in case your key base is 16, i.e. [0-9a-z] for example */

        do {
            // ... key generation goes here ...
            $key = 'xxxx';
        } while ( $this->user_model->valid_key($key) == false && $attempts_left-- > 0 );

        if( $attempts_left < 1 )
            return false;
        else
            return $key;
    }
1 голос
/ 23 октября 2009

Почему бы вам просто не отсканировать пространство значений ключей для поиска первого неиспользованного ключа? Нужен ключ для выполнения дополнительных ограничений, помимо длины в четыре символа и уникальности?

Вы можете вспомнить последний возвращенный ключ, чтобы возобновить сканирование оттуда при последующих вызовах.

Если вы хотите, чтобы последующие вызовы не возвращали подобные ключи, вы можете сначала перетасовать свою базу данных ключей. Это будет означать, что вам нужно где-то хранить массив элементов 456976, 1679616, 7311616 или 14776336 (в зависимости от того, являются ли используемые буквы алфавита одинарными или двойными, с цифрами или без них).

1 голос
/ 23 октября 2009

Вы можете поместить свой код в цикл и определить ключ итеративно вместо рекурсивно .

Пример:

function key_generator($length = 4)
{
  do {
    $key = 'xxxx'; //TODO
    if (timeOutReached()) return InvalidKey;
  } while (!$this->user_model->valid_key($key))

  return $key;
}

Сам цикл не предотвращает цикл infinte, но в отличие от вызова функции, он не поглощает пространство стека, поэтому вы не рискуете переполнением стека.

Также это немного упрощает вещи. В зависимости от типа ключа вы также можете адаптировать метод генерации ключа, например, с помощью пронумерованных ключей вы можете увеличивать экспоненциально с каждой итерацией.

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

Также убедитесь, что вы защищаете свой код от одновременного доступа. Что если два экземпляра этой функции попытаются сгенерировать ключ, и они оба определят одно и то же? Используйте критические разделы или транзакции, чтобы убедиться, что ничего плохого не происходит.

0 голосов
/ 15 апреля 2017

Использование функции внутри себя

function test($val) {
    /*initialize return value by using the conditions*/
    if($val>=5){
        /*do something with return statement*/
        return $val+10;
    } else {
        /*set the return default value for avoid the error throwing*/
        return "default value";
    }
    /*return the function used for check the condition*/
    return test($val);
}

echo test(4);  // output "default value";
echo test(6);  //output 16
...