Yii2, случайная рекурсия при использовании магических методов

Сижу, никого не трогаю, и понимаю, что сделал очень неприятную ошибку. Сейчас опишу ее суть.

Была определенная задача, создавать альбом/категорию, при создании продукта, и связывать их между собой. В бд, в таблице, у продукта было стандартное поле album_id, с его помощью я связывал продукт и категорию. Но при добавлении продукта, а это были платья, альбом уже или создан, или еще нет. Но его id нам уже нужен. Не долго думая, решил использовать магические методы, и при получении поля album_id в модели, я вызывал метод, который создавал или получал нужную категорию, и выдавал ее нам. Это было очень удобно, не занимало много места, и было довольно гибко. При добавлении нового товара, цель была удовлетворена полностью. Возможно были другие, более лучшие варианты, но я их не знал, в итоге вышел такой код.

public function __get($property) {
    switch ($property) {
    case 'album_id':
        $album_id = parent::__get($property);
        $model = new Albums();
        $model->photo_id = 0;
        if (!$model->newDefaultAlbum()) {
            $this->addError('album_id', 'Album not created');
            return null;
        }

        $this->album_id = $model->id;
        return $model->id;
        break;

    default:
        return parent::__get($property);
        break;
    }
}

public function newDefaultAlbum($dress_id = null) {
    $this->name = Yii::$app->formatter->asDate(time() , 'long');
    $album = $this::find()->where(['name' => $this->name])->one();
    if ($album) {
        $this->id = $album->id;
        return true;
    }

    $this->description = Yii::$app->formatter->asDate(time() , 'long');
    return $this->save();
}

В итоге, мы чекали поле, далее доставали id сегодняшнего альбома и жили себе счастливо.

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

Первым делом пришла идея проверять поле на наличие, если есть, значит выводить, если нету, создавать.

if (isset($this->album_id)) {
    return $this->album_id;
}

И тут я получил самую красивую логическую ошибку. Это условие загоняло весь процесс в бесконечную рекурсию. Получая поле album_id, мы опять вызывали магический метод __get(), в котором получали поле album_id, что в последствии вызывала магический метод, который …

Тут я уже поник, предчувствуя много работы по переписке кода, но через 5 минут вспомнил, что мы можем использовать метод родительского класса, и коль мы наследуемся от “\yii\db\ActiveRecord”, то мы можем вызвать его магический метод, что мы и делаем для остальных полей. Все заработало, дополнив код нужным условием, а именно:

$album_id = parent::__get($property);

if (isset($album_id)) 
    return $album_id;
}

В итоге вышло:

public function __get($property){
   switch ($property) {
   case 'album_id':
       $album_id = parent::__get($property);
       if (isset($album_id)) {
           return $album_id;
       }

       $model = new Albums();
       $model->photo_id = 0; //$this->photo_id;
       if (!$model->newDefaultAlbum($this->id)) {
           $this->addError('album_id', 'Album not created');
           return null;
       }

       $this->album_id = $model->id;
       return $model->id;
       break;

   default:
       return parent::__get($property);
       break;
   }
}

Перед созданием нового альбома, мы проверяем, установлен ли в текущем объекте id альбома, если установлен, выводим его. Возможно, по логике, в будущем, нам нужно будет что-то дописывать, а пока, мы полностью реализовали нашу потребность.

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