Tree behavior и callbacks
29/09/2010

Как это ни печально, но в версии CakePHP 1.3.3 опять криво работают каллбэки модели, если модель использует поведение Tree.

В чем заключается эта кривость? Допустим, вы пытаетесь удалить запись №1 в модели Category, использующей tree behavior, у которой есть дочерние записи №2 и 3.

$this->Category->delete(1);

И, допустим в модели определен каллбэк beforeDelete(). Вы будете удивлены, но beforeDelete() сработает только для записи №1, проигнорировав записи 2 и 3.

Не знаю, может в этом есть какая-то глубокая логика, но мне лично и многим другим, судя по обсуждениям в google groups, она не понятна.

В версии CakePHP 1.2 это "лечилось" переопределением в app_model.php метода deleteAll и использованием его вместо delete:

function deleteAll($conditions, $cascade = true, $callbacks = true) {
    $this->Behaviors->disable('Tree');
    $return = parent::deleteAll($conditions, $cascade, $callbacks);
    $this->Behaviors->enable('Tree');
    return $return;
}

С кейком 1.3.3 у меня эта фишка не прокатила, рыться глубоко в коде мне лень, поэтому для beforeDelete делаю так:

1. Переношу весь код из beforeDelete в приватный метод beforeDeleteId($id):

private function beforeDeleteId($id) {
    //код Вашего каллбэка
    return true;
}

2. В beforeDelete() прописываю:

function beforeDelete() {
    $children = $this->children($this->id, false, array('id'));
    if (!empty($children)) {
        $ids = Set::format($children, '{1}', array('{n}', "{n}.$this->alias.id"));
        foreach ($ids as $id) {
            if(!$this->beforeDeleteId($id)) {
                return false;
            }
        }
    }
    return $this->beforeDeleteId($this->id);
}

С afterDelete() ситуация немного сложнее, т.к. после удаления мы не можем узнать детей удаленной записи (они ведь тоже уже удалены). Что ж, будем узнавать их и запоминать перед удалением:

1. Добавляем в класс модели переменную $ids и модифицируем наш beforeDelete():

var $ids;
function beforeDelete() {
    $children = $this->children($this->id, false, array('id'));
    if (!empty($children)) {
        $this->ids = Set::format($children, '{1}', array('{n}', "{n}.$this->alias.id"));
        foreach ($this->ids as $id) {
            if(!$this->beforeDeleteId($id)) {
                return false;
            }
        }
    }
    return $this->beforeDeleteId($this->id);
}

2. Опять же, выносим всё из каллбэка afterDelete() в произвольный приватный метод (у меня он называется afterDeleteId($id)):

private function afterDeleteId($id) {
    //тут Ваш каллбэк
}

3. В afterDelete():

function afterDelete() {
    if (!empty($this->ids)) {
        foreach ($this->ids as $id) {
            $this->afterDeleteId($id);
        }
        $this->ids = null;
    }
    $this->afterDeleteId($this->id);
}

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

Выносить всё это куда-нибудь в app_model не вижу смысла, ибо поведение Tree у меня всегда используют максимум две модели.

Опубликовано в категории CakePHP | Теги: CakePHP, PHP

Комментарии

  1. phpdude | 07-11-2010 18:43
    вах бля нах!!!!!! :D:D
  2. Sinkler | 14-11-2010 04:36
    Интересно, что это значит)))

Добавить комментарий