Получите исходные ассоциации после использования поведения Containable в CakePHP

Предыстория: CakePHP 2.6.3. Довольно стабильное приложение. Новое поведение (MyCustomBehavior) создано для вывода дополнительной информации. У меня есть модель MyModel, действующая как Containable (определенная в AppModel), а затем MyCustom (определенная в MyModel). MyCustomBehavior написан таким образом, что он должен работать с ассоциациями модели с другими моделями в моем приложении.

Проблема. Всякий раз, когда я добавляю связанные модели в свой find() вызов MyModel, я не могу получить полный список MyModel ассоциаций, поскольку Containable поведение отменяет привязку моделей, которые не содержатся. Однако, если я не установлю contain в своих параметрах find() или не установлю 'contain' => false, все будет работать как положено.

Образец MyModel->belongsTo

public $belongsTo = array(
    'MyAnotherModel' => array(
        'className' => 'MyAnotherModel',
        'foreignKey' => 'my_another_model_id',
        'conditions' => '',
        'fields' => '',
        'order' => ''
    ),
    'Creator' => array(
        'className' => 'User',
        'foreignKey' => 'user_id',
        'conditions' => '',
        'fields' => '',
        'order' => ''
    ),
    'Approver' => array(
        'className' => 'User',
        'foreignKey' => 'approver_id',
        'conditions' => '',
        'fields' => '',
        'order' => ''
    ),
    'Status' => array(
        'className' => 'Status',
        'foreignKey' => 'status_id',
        'conditions' => '',
        'fields' => '',
        'order' => ''
    ),
);

Образец find()

$this->MyModel->find('all', array(
    'fields' => array(...),
    'conditions' => array(...),
    'contain' => array('Approver', 'Status')
));

Результат MyModel->belongsTo из MyCustomBehavior::beforeFind()

$belongsTo = array(
    'Approver' => array(
        ...
    ),
    'Status' => array(
        ...
    ),
);

Ожидается MyModel->belongsTo через MyCustomBehavior::beforeFind()

$belongsTo = array(
    'MyAnotherModel' => array(
        ...
    ),
    'Creator' => array(
        ...
    ),
    'Approver' => array(
        ...
    ),
    'Status' => array(
        ...
    ),
);

Очевидное решение. Один из тупых способов решить проблему — просто установить Containable поведения в MyModel вместо AppModel, чтобы управлять порядком загрузки поведения, т. е. public $actsAs = ['MyCustom', 'Containable']. Это решение не самое лучшее, потому что в других моделях, зависящих от Containable, может быть другое поведение, поэтому порядок Containable должен быть установлен в каждой модели в приложении явно и надеюсь, что я где-то не сломал приложение.

Аналогичный (связанный) вопрос был задан на SO здесь, но ответов нет.

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


person Fr0zenFyr    schedule 30.08.2018    source источник
comment
Что ж, я думал, что кто-то опубликует ответ, который лучше моего, или, по крайней мере, укажет мне на потенциальную проблему с решением, которое я считал идеальным. Поскольку в этом сообщении не было большой активности, я отмечаю свое решение как принятое до тех пор, пока кто-нибудь не предложит лучшее решение с хорошей аргументацией. Я был бы не против изменить принятое решение, если оно того стоит.   -  person Fr0zenFyr    schedule 03.09.2018


Ответы (1)


Попытка 1 (несовершенная, подвержена ошибкам):

Один из способов восстановить все исходные ассоциации — вызвать

$MyModel->resetBindings($MyModel->alias);
$belongsToAssoc = $MyModel->belongsTo;    // will now have original belongsTo assoc

Однако этот подход может привести к сбою (ошибка SQL 1066 Not unique table/alias) для правильной работы, если я использовал joins в своем вызове поиска (используя по умолчанию alias) для явного присоединения к уже связанной модели. Это связано с тем, что Containable также попытается присоединиться ко всем этим таблицам, восстановленным вызовом resetBindings(), в результате чего объединение будет выполнено дважды с одним и тем же псевдонимом.

Попытка 2 (отлично#, никаких известных побочных эффектов##):
Дальнейшее изучение ядра Containable поведение и документы привели меня к объектам $MyModel->__backOriginalAssociation и $MyModel->__backAssociation (достаточно странно, что ContainableBehavior никогда не использовал $__backContainableAssociation, как следует из названия переменной), которая была создана и использована этим поведением для выполнения resetBindings(). Итак, мое окончательное решение состояло в том, чтобы просто проверить, включен ли Containable в моем модальном окне (избыточный в моем случае, потому что он подключен в AppModel и никогда не отключается и не отсоединяется во всем приложении), и проверяет, установлен ли объект в модели.

// somewhere in MyCustomBehavior
private function __getOriginalAssociations(Model $Model, $type = 'belongsTo') {
    if(isset($Model->__backAssociation['belongsTo']) && !empty($Model->__backAssociation['belongsTo'])) {   // do an additional test for $Model->Behaviors->enabled('Containable') if you need
        return $Model->__backAssociation[$type];
    }

    return $Model->$type;
}

public function beforeFind(Model $Model, $query) {
    // somewhere in MyCustomBehavior::beforeFind()
    ...
    $belongsToAssoc = $this->__getOriginalAssociations($MyModel, 'belongsTo');    // will now have original belongsTo assoc
    ...
return $query;
}

$__backAssociation временно удерживает ассоциации моделей, чтобы обеспечить динамическую (раз)привязку. Это решение определенно можно улучшить, объединив результаты $Model->belongsTo и $Model->__backAssociation['belongsTo'] (или hasMany, hasOne, hasAndBelongsToMany), чтобы включить любые модели, которые были связаны на лету. Мне это не нужно, поэтому я пропустить код для слияния.


# Идеально для моего собственного варианта использования и настройки моего приложения.
## Никаких побочных эффектов были обнаружены в моем тестировании, что ограничено моим уровнем знаний/навыков.
Отказ от ответственности: Моя работа в этом посте лицензирована в соответствии с Общественная лицензия WTF (WTFPL). Так что делайте с материалом что хотите. Кроме того, я не несу никакой ответственности за любые финансовые, физические или моральные потери любого рода, связанные с использованием вышеуказанных материалов. Используйте на свой страх и риск и проведите собственное гребаное исследование перед попыткой копирования/вставки. Не забудьте взглянуть на cc by-sa 3.0, потому что SO говорит "вклады пользователей под лицензией cc by-sa 3.0 с обязательным указанием авторства". (проверьте нижний колонтитул на этой странице. Я знаю, что вы никогда не замечали его до сегодняшнего дня! :p)

person Fr0zenFyr    schedule 30.08.2018