/** * Si no esta salvado: * Para cada hasOne: * ... * save_object() * Para cada hasMany: * ... * * @return boolean true si no hubo error, false en caso contrario */ public function save_cascade(PersistentObject $obj, $sessId) { Logger::getInstance()->pm_log("PM::save_cascade " . get_class($obj) . " SESSIONID: " . $sessId); // Para detectar loops en el salvado del modelo $obj->setLoopDetectorSessId($sessId); // Si el objeto no fue salvado en la operacion actual... if (!$obj->isSaved($sessId)) { // Nuevo: solo salva si se ha cambiado un atributo o una relacion hasOne (dirty) if ($obj->isDirtyOne()) { // La relacion con los hasOne, o sea el id, se salva como atributo simple en save_object. // Este codigo de abajo solo verifica si hay que salvar en cascada cada objeto hasOne que tenga asociado. //asOne no necesita tablas intermedias (salvar la referencia) // Retorna los valores no nulos de hasOne $sassoc = $obj->getSimpleAssocValues(); // TODO?: Podria chekear si debe o no salvarse en cascada... foreach ($sassoc as $attrName => $assocObj) { // ojo el objeto debe estar cargado (se verifica eso) if ($assocObj !== PersistentObject::NOT_LOADED_ASSOC) { //echo "=== PO loaded: $attrName<br/>"; // Si se detecta un loop en el salvado del modelo. // FIXME: deberia salvar solo si el objeto soy owner del relacionado. if ($assocObj->isLoopMarked($sessId)) { //Logger::getInstance()->pm_log("LOOP DETECTADO " . get_class($obj) . " " . get_class($assocObj)); // Agrega al objeto un callback para que se llame cuando termine de salvarse, para salvar el objeto hasOne asociado. // Se salva el objeto actual sin el asociado (assocObj viene a ser instancia de A del modelo A -> B -> C -> A, donde obj viene a ser instancia de C). // Esto deja a obj inconsistente, pero se arregla con el callback cuando termina de salvar a A, se actualiza la referencia de C a A. // ============================================================================= // Se empezo a salvar desde A, se quiere salvar C que a su vez necesita A. // $assocObj es A. // $obj es C. // 1. Actualizar ids de hasOne. // update_simple_assocs $callb_update = new Callback(); $callb_update->set($obj, 'update_simple_assocs', array()); // FIXME (posible bug TICKET #4.1): OJO!, este save deberia ser un save simple (no salvar nada en cascada) y hacerce obligatoriamente, sin considerar el id de session... // 2. Salvar el objeto. Llama a save del PO que es el wrapper del PM... $callb_save = new Callback(); $callb_save->set($obj, 'single_save', array()); // Intento solucion TICKET #4.1 // Registro los callbacks en A, para que cuando se salve, se actualice C con su id. $assocObj->registerAfterSaveCallback($callb_update); $assocObj->registerAfterSaveCallback($callb_save); // No se sigue salvando en cascada el objeto asociado xq ya se quiso salvar y se llego // a un loop, se corta el loop y se salvan los objetos con los datos que tienen, y los // datos que no se tienen se salvan en callbacks. // ===================================================================================== } else { // El objeto puede estar salvado en otra sesion, por ejemplo se crea y salva, y luego se asocia al obj. // La condicion es: si no esta salvado en esta session o esta sucio, salvo en cascada. if ($obj->isOwnerOf($attrName) && (!$assocObj->isClean() || !$assocObj->isSaved($sessId))) { //Logger::getInstance()->pm_log("PM::save_assoc save_cascade de ". $assocObj->getClass() .__LINE__); // Valido el objeto antes de intentar salvarlo // La excepcion se catchea en el save y hace rollback de todo el save en cascada. if (!$assocObj->validate()) { throw new Exception("El objeto " . $assocObj->getClass() . " (" . $assocObj->getId() . ") no valida. " . __FILE__ . " " . __LINE__); } // hasOne no necesita tablas intermedias (salvar la referencia) // salva objeto y sus asociaciones. $this->save_cascade($assocObj, $sessId); } } } // Si el hasOne esta cargado. Sino esta cargado, no hago nada (la relacion se actualiza en save_object). } // Para cada objeto asociado // ------------------------------------------------------------------------------------------------------------------ // VERIFY: Como y donde se setean los atributos de id de las referencias!! // (tendria que hacerse en DAL verificando que el atributo corresponde a una asociacion hasOne) // // Aca tengo los ids de los hasOne y puedo salvar las referencias desde obj a ellos. // FIXME!!!!!: TENGO QUE SALVAR ANTES LOS hasOne para tener sus ids y setear los atributos generados "email_id" ...!!! $obj->update_simple_assocs(); // Actualiza los atributos de referencia a objetos de hasOne (como "email_id") } // si la instancia esta dirty //Logger::struct( $obj , "PRE PM.save_object en PM.save_cascade"); //Logger::getInstance()->pm_log("PM::save_assoc save_object ". $obj->getClass() ." @".__LINE__); // salva el objeto simple, verificando restricciones en la instancia $obj // FIXME: esta operacion no verifica restricciones, se deberia validar el objeto antes de salvar. // la validacion para el objeto raiz de la estrucura se hace en PO, pero no para el resto // de los objetos asociados que se salvan en cascada // FIXME: el problema es que si falla la validacion de un objeto salvado en cascada, deberia // abortar todo el save, o sea ser transaccional, y esto todavia no esta soportado. $this->save_object($obj, $sessId); // Si se han modificado los hasMany if ($obj->isDirtyMany()) { $massoc = $obj->getManyAssocValues(); // Es una lista de listas de objetos. foreach ($massoc as $attrName => $objList) { $ord = 0; //Logger::getInstance()->pm_log("save_cascade foreach hasManyAssoc: ". $attrName ." ". __FILE__ ." ". __LINE__ ); foreach ($objList as $assocObj) { // Problema con cascada hasMany: a1 -> b1 -> c1 -> a1 // cuando c1 quiere salvar a a1 no entra aca, eso esta bien, pero deberia salvarse la relacion c1 -> a1... // No se cual es la condicion para salvar la relacion solo, voy a intentar solo decir que c1 es owner de a1 a ver que pasa... if ($obj->isOwnerOf($attrName)) { //Logger::getInstance()->pm_log("PM::save_assoc ". $obj->getClass()." isOwnerOf $attrName. " .__LINE__); // FIXME ?: por que aca no es igual que en las relaciones hasOne? // FIXME: Es probable que el assocObj no se haya salvado en esta sesion y que este salvado. // Se pudo haber creado y salvado antes, y luego asociado a hasMany del obj. // Habria que preguntar si NO esta salvado en esta session o si esta dirty. if (!$assocObj->isClean() || !$assocObj->isSaved($sessId)) { // Valido el objeto antes de intentar salvarlo // La excepcion se catchea en el save y hace rollback de todo el save en cascada. if (!$assocObj->validate()) { throw new Exception("El objeto " . $assocObj->getClass() . " (" . $assocObj->getId() . ") no valida. " . __FILE__ . " " . __LINE__); } // salva objeto y sus asociaciones. $this->save_cascade($assocObj, $sessId); //Logger::getInstance()->pm_log("PM::save_cascade objeto guardado: ". $assocObj->getClass(). " ". $assocObj->getId(). " " .__LINE__); } //Logger::getInstance()->pm_log("PM::save_assoc save_assoc de ". $obj->getClass(). " ". $assocObj->getClass(). " " .__LINE__); // El objeto puede estar salvado y la relacion no. // Actualiza tabla intermedia. // Necesito tener, si la relacion es bidireccional, el nombre del atributo de assocObj que tiene Many obj, podria haber varios! $this->save_assoc($obj, $assocObj, $attrName, $ord); // Se debe salvar aunque a1 este salvado (problema loop hasmany) } $ord++; } // para cada objeto dentro de una relacion hasMany } // para cada relacion hasMany } // si tiene dirtyMany } // if is_saved obj // Termina de guardar el objeto, limpia los bits de dirty. $obj->resetDirty(); }