Как связать модель в CakePHP по полям, не названным по соглашению?

У меня есть две таблицы с полем username в обеих. Как я могу указать имя поля как для локальной, так и для внешней таблицы?

Я хочу, чтобы CakePHP делал что-то вроде

ON (`T1`.`username` = `T2`.`username`)`

в результате. Без каких-либо изменений таблицы будут объединены со следующим условием:

ON (`T1`.`id` = `T2`.`t1_id`)`

Установки свойства 'foreign_key' = 'username' недостаточно, потому что он будет создавать такой запрос:

ON (`t1`.`id` = `t2`.`username`)`

У меня есть два решения. Первый — использовать свойство «присоединиться» и присоединиться к таблице «на лету». В таком случае я могу установить как локальное, так и внешнее поле. Но если мне нужно присоединить больше таблиц к этой, объединенной вручную, я больше не могу использовать содержащуюся информацию, мне нужно написать следующие объединения вручную, даже если эти ассоциации были установлены правильно. Поэтому мне нужно каждый раз писать длинные определения соединения, вместо этого просто используйте 'contain' => array('T1', 'T2', 'T3')

Во-вторых, установить «primary_key» таблицы в соответствующее поле. Это можно сделать в файле модели или во время выполнения. В моем случае это невозможно сделать в модели, потому что эта таблица также имеет «правильную» ассоциацию по полю 'id'. Настройте его во время выполнения, но мне это не нравится, потому что это неочевидно и выглядит как взлом.

Когда я задаю этот вопрос, я думал, что упускаю что-то очевидное, но теперь я понимаю, что CakePHP просто не может этого сделать. Поэтому я начал щедрость, надеясь, что кто-нибудь поделится решением. Если нет, я попытаюсь прочитать исходники торта и переопределить модель некоторых методов, чтобы добавить возможность определять локальное поле рядом с 'foreign_key' в определении ассоциации.


person Yaroslav    schedule 11.11.2013    source источник
comment
Можно изменить первичный ключ модели и заставить CakePHP использовать это поле в условии соединения. Но я не доволен таким решением.   -  person Yaroslav    schedule 28.02.2014
comment
Почему вас не устраивает такой ответ. Измените свой вопрос, чтобы более конкретно указать, чего вы хотите, а какое поведение вам не нравится. Возможно, ваши проблемы возникают из-за неудачного дизайнерского решения, для которого существуют лучшие решения.   -  person Jeroen    schedule 01.03.2014
comment
Jeroen, спасибо за интерес. Я недоволен, потому что это что-то вроде попытки обмануть CakePHP, и мне не нравятся такие решения. В дизайне нет ничего, я просто хочу объединить две таблицы с помощью настраиваемых полей в обеих таблицах. И я хочу написать это условие один раз в моем файле модели, чтобы использовать его везде в моем приложении. Может быть, вы правы, я попытаюсь переписать свой вопрос.   -  person Yaroslav    schedule 02.03.2014


Ответы (5)


Иностранный ключ ложь

Чтобы иметь ассоциацию, которая не использует первичный ключ связанной модели в условиях соединения — стандартный способ сделать это — использовать 'foreignKey' => false.

т.е. тогда как эта ассоциация:

class Comment extends AppModel {
    public $belongsTo = array(
        'Profile' => array(
        )
    );
}

Будет генерировать этот sql:

SELECT ... LEFT JOIN profiles on ON (Profile.id = Comment.profile_id)

Указание того, что ForeignKey не следует использовать следующим образом:

class Comment extends AppModel {
    public $belongsTo = array(
        'Profile' => array(
            'foreignKey' => false
        )
    );
}

Произведет этот (недопустимый) sql:

SELECT ... LEFT JOIN profiles on ON ()

С этого момента желаемые условия могут быть указаны с помощью ключа массива условий:

class Comment extends AppModel {
    public $belongsTo = array(
        'Profile' => array(
            'foreignKey' => false,
             'conditions' => array(
                 'Comment.username = Profile.username'
             ),
        )
    );
}

(Обратите внимание, что условия определены как строка), в результате чего:

 SELECT ... LEFT JOIN profiles on ON (Comment.username = Profile.username)
person AD7six    schedule 04.03.2014
comment
Я очень рад, что Дэйв получил согласие (и награду, если это возможно), если ответ будет обновлен соответствующим образом. - person AD7six; 04.03.2014

Обновление:

Просто укажите, что вы не хотите использовать ForeignKey, а затем укажите условия (все в рамках ассоциации:

'foreignKey' => false и 'conditions' => 'Comment.username = User.username'

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


Это очень четко определено в книге CakePHP: http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasmany

class User extends AppModel {
    public $hasMany = array(
        'Comment' => array(
            'className' => 'Comment',
            'foreignKey' => 'user_id', // <---- THIS HERE you can define the foreign_key
            'conditions' => array('Comment.status' => '1'),
            'order' => 'Comment.created DESC',
            'limit' => '5',
            'dependent' => true
        )
    );
}

Что такое внешний ключ?:

В контексте реляционных баз данных внешний ключ — это поле в одной таблице, которое однозначно идентифицирует строку другой таблицы.

person Dave    schedule 11.11.2013
comment
Пожалуйста, процитируйте ту часть, где есть ответ на мой вопрос. - person Yaroslav; 11.11.2013
comment
Вы действительно хотите, чтобы я перешел по ссылке за вас и скопировал/вставил ответ в stackoverflow? - person Dave; 11.11.2013
comment
Я только что снова прочитал эту часть руководства и до сих пор не могу найти ответ. Так что, если вы можете указать мне напрямую, я буду благодарен. Также, пожалуйста, посмотрите мой ответ выше. - person Yaroslav; 11.11.2013
comment
Хорошо, внешний ключ. Я это уже знаю. А теперь: как определить имя поля не в ДРУГОЙ таблице, а в текущей таблице, к которой я хочу присоединиться? Потому что мой вопрос на самом деле об этой части. И, как я уже говорил ранее, если я это сделаю, он изменит только внешнее поле и возьмет id из текущей таблицы. Но мне нужно использовать другое поле, а не id. - person Yaroslav; 11.11.2013
comment
Похоже, вам просто нужно потратить несколько минут на разъяснение вашего вопроса. - person Dave; 11.11.2013
comment
Дэйв, похоже, тебе нужно потратить несколько минут, чтобы снова прочитать вопрос. У меня есть две таблицы с именем пользователя поля в обеих. И нужно присоединиться к этому полю в ОБЕИХ таблицах. Не только в иностранном столе. Вот и все. - person Yaroslav; 11.11.2013
comment
Извините, вы получили отрицательный голос, потому что даже не пытались внимательно прочитать мой вопрос и продолжать спорить. Я нашел решение, но оно мне не нравится. Смотрите мой ответ, если вам интересно. - person Yaroslav; 13.11.2013
comment
Меня не интересует обходной путь, так как я считаю, что мой ответ решает проблему в рамках обычных опций CakePHP. - person Dave; 13.11.2013
comment
Поэтому, пожалуйста, попробуйте свое решение с моей схемой и отладкой 2 и посмотрите запрос. Я считаю, что вы ошибаетесь. Потому что у меня огромный опыт работы с CakePHP и я знаю о внешнем ключе. - person Yaroslav; 14.11.2013
comment
Я упрощу задачу: посмотрите мое обновление к моему собственному ответу. Есть пример приложения CakePHP с моей схемой и вашим решением. И это решение не работает. Поэтому, пожалуйста, заберите свой отрицательный голос, потому что вы совершенно неправы, но продолжайте спорить, даже не пытаясь понять мой вопрос. - person Yaroslav; 14.11.2013
comment
@Ярослав Дэйв прав, и я проверил ваш ответ, ваша БД просто неудобна. Кроме того, даже этот неуклюжий дизайн можно использовать так, как описывает Дэйв с CakePHP, зачем вообще создавать такой дизайн БД? - person floriank; 03.03.2014
comment
@burzum, Дэйв не прав. Посмотрите мой снимок экрана выше, там вы можете увидеть фактический запрос, созданный CakePHP. Обратите внимание, что он использует значения 1 и 2 в состоянии 'username', но вместо этого должен быть 'firstusername'. Пожалуйста, относитесь к этому неудобному дизайну как к примеру. Это не фактическая схема моего приложения. У меня сложная схема БД. Но все же речь идет не об оптимизации БД. - person Yaroslav; 04.03.2014

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

class User extends AppModel {
    public $hasMany = array(
        'Comment' => array(
            'finderQuery' => 'SELECT * FROM comments Comment 
                              LEFT JOIN users User 
                              ON Comment.username = User.username 
                              WHERE User.id = {__CakeID__}'
        )
    );
}

Надеюсь, поможет

person arilia    schedule 04.03.2014
comment
Спасибо, это будет работать для совершенно эзотерических условий соединения, но в моем случае достаточно 'foreignKey' => false, 'conditions' => array() решения. - person Yaroslav; 04.03.2014

Я боролся с этой точной проблемой в течение нескольких дней, и решение, которое я нашел (не указано выше), таково: hasMany должен иметь 'foreignKey' => false, в то время как belongsTo ТАКЖЕ ДОЛЖЕН иметь 'foreignKey' => false и иметь 'conditions' => 'Comment.username = User.username'. С conditions в виде строки, а не массива assoc. Вот так:

// Profile
public $hasMany = array(
    'Comment' => array(
        'foreignKey' => false,
    )
);

// Comment
public $belongsTo = array(
    'Profile' => array(
        'foreignKey' => false,
        'conditions' => array('Profile.username = Comment.username')
    )
);
person user3287495    schedule 17.07.2014

public $hasMany = array(
    'Comment' => array(
        'className' => 'T1',
        'foreignKey' => 'username',
        'dependent' => true
    )
);

Обратите внимание, что зависимый true удалит все T1, когда T2 будут удалены из торта.

person Chris Pierce    schedule 11.11.2013
comment
Говоря о руководстве Cake: в этом случае запрос будет что-то вроде ON (Comment.username` = User.id)`, но мне нужно username для обеих таблиц. - person Yaroslav; 11.11.2013
comment
Это похоже на то, что мне нужно присоединиться к комментариям не по идентификатору пользователя, а по логину: Select * from users as User LEFT JOIN comment as Comment ON (User.login = Comment.login) Надеюсь, это более понятное объяснение. - person Yaroslav; 11.11.2013