Рекурсия с Doctrine ORM - логическая ошибка - PullRequest
1 голос
/ 04 марта 2011

Алло,

У меня есть своего рода ежедневная система случайных изображений с Codeigniter и Doctrine.

Есть ключевая функция, которая возвращает дневное изображение или устанавливает новое. Я использую рекурсивные вызовы, но иногда выбираются два изображения на один день .

class Daily extends Controller {

    function find_daily(){
        $connection = Doctrine_Manager::connection();
        $connection->setCharset('utf8');

        $q = Doctrine_Query::create()
            ->select('COUNT(id) as dailycount')
            ->from('Image')
            ->where('daily_date = ?',date("Y-m-d"));



        $set = $q->execute();

        if( $set[0]->dailycount != 0 ){ //theres one or more for today



            $q = Doctrine_Query::create()
            ->select('id')
            ->from('Image')
            ->where('daily_date = ?',date("Y-m-d"))
            ->limit(1);


            $set = $q->execute();
            $i = Doctrine::getTable('Image')->findOneById($set[0]["id"]);

            return $i;

            }else { // none image selected with today date


                $q = Doctrine_Query::create()
                ->select('id')
                ->from('Image')
                ->where('daily_date = ?', "0000-00-00")
                ->andWhere("dir =?","queue")
                ->orderBy("rand()")
                ->limit(1);


                $set = $q->execute();


                $i = Doctrine::getTable('Image')->findOneById($set[0]["id"]);

                $i->daily_date = date("Y-m-d");
                $i->dir = "archive";
                $i->save();

                $this -> find_daily();
                };

        }

...

Я думаю, что есть какая-то глупая ошибка ... возможно, вызывать что-то вроде "Doctrine :: doAllAndClear" до $this -> find_daily(); или я что-то забываю ...

Или лучше не использовать рекурсивные вызовы?

Спасибо.

1 Ответ

0 голосов
/ 04 марта 2011

Ну, я вообще не проверял это, но я, вероятно, с большей вероятностью напишу ваш код более похожим на это; надеюсь, это даст вам несколько идей:

  function find_daily() {
    $connection = Doctrine_Manager::connection();
    $connection->setCharset('utf8');

    // Make sure "today" doesn't change while the script runs
    $today = date("Y-m-d"); 

    // Find an image with today's date.
    $q = Doctrine_Query::create()
      ->from('Image')
      ->where('daily_date = ?', $today)
      ->limit(1);

    $image = $q->fetchOne();

    if (!$image) {
      // There was no image with today's date, so pick one from the queue at random instead.
      $q = Doctrine_Query::create()
        ->from('Image')
        ->where('daily_date = ?', "0000-00-00")
        ->andWhere("dir =?","queue")
        ->orderBy("rand()")
        ->limit(1);

      $image = $q->fetchOne();
      // ToDo: Cope with there being no image in the queue

      $image->setDailyDate($today);
      $image->setDir("archive");
      $image->save();
    }
    return $image;
  }

Однако вам нужно подумать о том, что произойдет, если два человека ударили по вашему сценарию одновременно. В этом случае они могут оба получить новое изображение для дня (поскольку каждый из них может запустить первый SELECT одновременно, и оба получат результат, который говорит, что дневного изображения еще нет.)

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

Проверка руководства, beginTransaction() в начале, commit() в конце и настройка перед всем, что связано с $tx = $conn->transaction; $tx->setIsolation('SERIALIZABLE'); (в противном случае два пользователя, возможно, все еще могут запустить SELECT одновременно), вероятно, будут все вам нужно, но вы можете подождать, пока кто-то, кто действительно знает Доктрину, прокомментирует это ...

Я также сохранил дату «сегодня» в начале скрипта, в противном случае существует опасность того, что она будет отличаться во время SELECT и более поздних обновлений, если скрипт будет выполняться в полночь.

...