Asociaciones polimórficas en CakePHP2
polymorphic-associations (1)
Tengo 3 modelos, Page
, Course
y Content
Page
y el Course
contienen metadatos y el Content
contiene contenido HTML.
Page
y Course
ambos tienen muchos Content
Content
pertenece a la Page
y el Course
Para evitar tener los campos page_id
y course_id
en el Content
(porque quiero que esto course_id
a más de 2 modelos), estoy buscando utilizar asociaciones polimórficas. Empecé utilizando el comportamiento polimórfico en la panadería pero está generando demasiadas consultas SQL para mi gusto y también está arrojando un error de "compensación ilegal" que no sé cómo solucionar (fue escrito en 2008 y nadie parece haberlo mencionado recientemente, entonces quizás el error se deba a que no se diseñó para Cake 2?)
De todos modos, he descubierto que casi puedo hacer todo lo que necesito al codificar las asociaciones en los modelos como tales:
Modelo de página
CREATE TABLE `pages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`slug` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
)
<?php
class Page extends AppModel {
var $name = ''Page'';
var $hasMany = array(
''Content'' => array(
''className'' => ''Content'',
''foreignKey'' => ''foreign_id'',
''conditions'' => array(''Content.class'' => ''Page''),
)
);
}
?>
Modelo de curso
CREATE TABLE `courses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`slug` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
)
<?php
class Course extends AppModel {
var $name = ''Course'';
var $hasMany = array(
''Content'' => array(
''className'' => ''Content'',
''foreignKey'' => ''foreign_id'',
''conditions'' => array(''Content.class'' => ''Course''),
)
);
}
?>
Modelo de contenido
CREATE TABLE IF NOT EXISTS `contents` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`class` varchar(30) COLLATE utf8_unicode_ci NOT NULL,
`foreign_id` int(11) unsigned NOT NULL,
`title` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`content` text COLLATE utf8_unicode_ci NOT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
)
<?php
class Content extends AppModel {
var $name = ''Content'';
var $belongsTo = array(
''Page'' => array(
''foreignKey'' => ''foreign_id'',
''conditions'' => array(''Content.class'' => ''Page'')
),
''Course'' => array(
''foreignKey'' => ''foreign_id'',
''conditions'' => array(''Content.class'' => ''Course'')
)
);
}
?>
Lo bueno es que $this->Content->find(''first'')
solo genera una sola consulta SQL en lugar de 3 (como fue el caso con el Comportamiento polimórfico) pero el problema es que el conjunto de datos devuelto incluye ambos pertenencias a modelos, mientras que solo debería devolver realmente el que existe. Así es como se ven los datos devueltos:
array(
''Content'' => array(
''id'' => ''1'',
''class'' => ''Course'',
''foreign_id'' => ''1'',
''title'' => ''something about this course'',
''content'' => ''The content here'',
''created'' => null,
''modified'' => null
),
''Page'' => array(
''id'' => null,
''title'' => null,
''slug'' => null,
''created'' => null,
''updated'' => null
),
''Course'' => array(
''id'' => ''1'',
''title'' => ''Course name'',
''slug'' => ''name-of-the-course'',
''created'' => ''2012-10-11 00:00:00'',
''updated'' => ''2012-10-11 00:00:00''
)
)
Solo quiero que devuelva una Page
o Course
dependiendo de cuál se especifique en Content.class
ACTUALIZACIÓN: la combinación de los modelos de Page
y Course
parecería la solución obvia a este problema, pero los esquemas que he mostrado arriba se muestran solo para el propósito de esta pregunta. Los esquemas reales son realmente muy diferentes en términos de sus campos y cada uno tiene un número diferente de asociaciones con otros modelos también.
ACTUALIZACIÓN 2
Aquí está la consulta que resulta de ejecutar $this->Content->find(''first'');
:
SELECT `Content`.`id`, `Content`.`class`, `Content`.`foreign_id`, `Content`.`title`,
`Content`.`slug`, `Content`.`content`, `Content`.`created`, `Content`.`modified`,
`Page`.`id`, `Page`.`title`, `Page`.`slug`, `Page`.`created`, `Page`.`updated`,
`Course`.`id`, `Course`.`title`, `Course`.`slug`, `Course`.`created`,
`Course`.`updated` FROM `cakedb`.`contents` AS `Content`
LEFT JOIN `cakedb`.`pages` AS `Page` ON
(`Content`.`foreign_id` = `Page`.`id` AND `Content`.`class` = ''Page'')
LEFT JOIN `cakedb`.`courses` AS `Course` ON (`Content`.`foreign_id` = `Course`.`id`
AND `Content`.`class` = ''Course'') WHERE 1 = 1 LIMIT 1
Tu consulta está bien. Simplemente filtra los valores vacíos después de encontrar:
public function afterFind($results, $primary = false) {
return Set::filter($results, true);
}
Eche un vistazo a esta pregunta.
También puede intentar proporcionar conditions
para que find
consulta que Page.id
no es nulo o Course.id
no es nulo.