Esta entrada que vamos a escribir es un tanto atípica ya que no resuelve un problema en particular, ni un bug, ni cosas que suceden demasiado seguido. Así y todo nos pareció interesante compartir la experiencia de cómo resolvimos un problema de diseño al cual nuestro framework CakePHP no proponía una respuesta de diseño del todo clara. Sin más preámbulo vamos a ver de qué se trataba nuestro problema.
3 Tablas, 3 Modelos, 3 Registros de pagos de distintas cosas que guardan distinta información asociada al pago pero que al mismo tiempo comparten otra tanta información:
pago_servicios
pago_eventos
pago_extras
Hasta el momento todo perfecto, se programaron los modelos con sus validaciones correspondientes, sus controladores con sus acciones de ABM para cada uno y las vistas de estos controladores. El requerimiento que iba a disparar el problema llegó: «una pantalla con paginación (utilizando PaginatorHelper) que muestre todos los pagos ordenados por fecha_pago (campo común a todas las tablas)». Google combinado con cakePHP como siempre tiene la respuesta… «Join Models CakePHP», «Extends Models» fueron solo algunas de las búsquedas hechas sin encontrar una respuesta de diseño pensada integramente por la comunidad de CakePHP para el problema que estabamos atravesando. Fué entonces así que empezamos a buscar una solución por fuera del framework y por decirlo de alguna forma «A la vieja usanza».
La salvación a códigos engorrosos llegó de la mano de las estimadas y nunca bien poderadas vistas. Comenzamos a evaluarlo y fué una propuesta perfecta porque era una solución transparente al CakePHP, la idea fué crear una vista que sea una unión entre las 3 tablas y crear un modelo en Cake «Pagos» que este relacionado con esta vista (para el Cake una tabla como cualquier otra).
Vamos a ver ahora como hacer una vista que sea la unión de 3 tablas con distintos campos:
CREATE VIEW pagos AS SELECT pago_servicios.id, empresa_id, null as evento_id, pago_servicios.servicio_id, null as extra_id, pago_servicios.cliente_id, pago_servicios.fecha_vencimiento, pago_servicios.fecha_pago, pago_servicios.pago, pago_servicios.comentario, pago_servicios.created, pago_servicios.modified FROM pago_servicios INNER JOIN servicios ON pago_servicios.servicio_id = servicios.id UNION ALL SELECT pago_extras.id, empresa_id, null as evento_id, null as servicio_id, pago_extras.extra_id, pago_extras.cliente_id, null as fecha_vencimiento, pago_extras.fecha_pago, pago_extras.pago, pago_extras.comentario, pago_extras.created, pago_extras.modified FROM pago_extras INNER JOIN extras ON pago_extras.extra_id = extras.id UNION ALL SELECT pago_eventos.id, empresa_id, pago_eventos.evento_id, null as servicio_id, null as extra_id, pago_eventos.cliente_id, null as fecha_vencimiento, pago_eventos.fecha_pago, pago_eventos.pago, pago_eventos.comentario, pago_eventos.created, pago_eventos.modified FROM pago_eventos INNER JOIN eventos ON pago_eventos.evento_id = eventos.id
Prestar especial atención a los primeros campos para tomarlos como ejemplo, vamos a extraerlos así podemos visualizarlo mejor:
SELECT pago_servicios.id, empresa_id, null as evento_id, pago_servicios.servicio_id, null as extra_id,
SELECT pago_extras.id, empresa_id, null as evento_id, null as servicio_id, pago_extras.extra_id,
SELECT pago_eventos.id, empresa_id, pago_eventos.evento_id, null as servicio_id, null as extra_id,
como podemos ver en cada union los campos que faltan los completamos con valores NULL, de esta forma nos queda algo como esto:
Podemos ver los campos que son propios de cada tabla como NULL en las otras y los campos en común todos completos como es cliente_id.
Luego de esto creamos el modelo y el controlador como si estuviesemos creandolo sobre una tabla cualquiera y aplicamos PaginatorHelper todo sin agregar una sola lina de código de más.
Luego como un detalle agregamos en el modelo en el handler afterFind una variable que define de qué tipo de pago se trata:
function afetFind($data) { if (isset($data['servicio_id']) && !empty($data['servicio_id'])) $data['concepto'] = 'servicio'; elseif (isset($data['evento_id']) && !empty($data['evento_id'])) $data['concepto'] = 'evento'; elseif(isset($data['extra_id']) && !empty($data['extra_id'])) $data['concepto'] = 'extra'; else $data['concepto'] = null; return $data; }