/** * Metodo auxiliar para cargar los objetos asociados en cascada, tanto los hasOne como los hasMany, * verificando previamente si no fueron ya cargados. * @param PersistentObject $obj el objeto al que hay que cargarle los objetos asociados. */ private function getCascadeAssocs($obj) { // Para saber si estaba limpia previamente, para poder limpiar lo que esta operacion ensucia (dirty bits) $wasClean = $obj->isClean(); // TODO: Verificar si para los objetos en hasOne, sus asociaciones son cargadas en cascada. // Para cada objeto hasOne, lo trae. // Para el objeto hago get para hasOne y getMany para los hasMany. $ho_attrs = $obj->getHasOne(); foreach ($ho_attrs as $attr => $assoc_class) { // attr = "email_id" ? $ho_instance = new $assoc_class(); $hasOneAttr = DatabaseNormalization::getSimpleAssocName($attr); // email $assoc_id = $ho_instance->aGet($attr); $assoc_obj = NULL; if (ArtifactHolder::getInstance()->existsModel($assoc_class, $assoc_id)) { $assoc_obj = ArtifactHolder::getInstance()->getModel($assoc_class, $assoc_id); } else { $assoc_obj = $this->get($assoc_class, $assoc_id); ArtifactHolder::getInstance()->addModel($assoc_obj); } //$obj->{"set".$attr}( $assoc_obj ); $obj->aSet($attr, $assoc_obj); } // Para cada objeto hasMany, lo trae // Para el objeto hago get para hasOne y getMany para los hasMany. $hm_attrs = $obj->getHasMany(); foreach ($hm_attrs as $attr => $class) { $this->getMany($obj, $attr); // Carga los elementos del atributo en la clase. } // TODO: si carga en cascada, entonces la instancia de obj y sus clases asociadas // se cargan todas en el mismo momento, por lo que no habria nada sucio, // y seria innecesario verificar si estaba sucia antes de la carga. // Solo limpia si la clase estaba limpia antes de la operacion if ($wasClean) { $obj->resetDirty(); } // Apaga las banderas que se prendieron en la carga }
/** * Nombre de la tabla que almacena una instancia de un objeto persistente. * Si tiene "withTable", elige ese, si no, toma el nombre de la clase normalizado como nombre de la tabla. * @param PersistentObject $ins Instancia de la cual derivar el nombre de la tabla en la base de datos. */ public static function tableName($instance_or_class) { $ins = $instance_or_class; // TEST if (empty($instance_or_class)) { debug_print_backtrace(); } //echo '<pre> tableName '. print_r($instance_or_class, true) .__FILE__.__LINE__.'</pre>'; if (!is_object($ins)) { $ins = new $instance_or_class(array(), true); } // Si no es instancia, es clase, creo una instancia de esa clase. if (!$ins instanceof PersistentObject) { throw new Exception("La instancia debe ser de PO y es " . gettype($ins)); } // FIXME: en pila de lados tengo que crear una instancia para poder llamar a este metodo, // porque no mejor hacer que pueda recibir tambien el nombre de la clase, y en ese caso, // resuelve la tabla como nombre de clase, sin considerar withTable, o directamente crea // la instancia internamente. Permitiendo tambien pasarle una instancia, o sea, ambas opciones. // FIXME: SI NO TIENE WIHT TABLE TENGO QUE VER POR EL NOMBRE DE LA CLASE, PERO ME VIENE PersistentObject, // TENGO CAPAZ QUE SETEARLE EL WITH TABLE A MANO SI NO LO TIENE SETEADO!! NOOOOO!!! // Si no tiene withTable, tengo que crear el nombre de la tabla a partir del nombre de la clase. // Si no tiene withTable, quiere decir que en ninguna superclase de ella se define, entonces tengo que // obtener la superclase de nivel 1 y el nombre de la tabla se saca de el nombre de esa clase. if ($ins->getWithTable() != NULL && strcmp($ins->getWithTable(), "") != 0) { $tableName = $ins->getWithTable(); } else { $superclaseNivel1 = $ins->getClass(); while (($parent = get_parent_class($superclaseNivel1)) !== 'PersistentObject') { $superclaseNivel1 = $parent; } $tableName = $superclaseNivel1; } // Filtro... $tableName = DatabaseNormalization::table($tableName); // TODO: La funcion de normalizacion esta deberia estar en un core.basic.String. return $tableName; }
/** * Se recibe un objecto a la que ya se ha verificado que debe insertarse en la base de datos. * @param $object POs a salvar. */ private function insert_query($object, $tableName = NULL) { Logger::getInstance()->dal_log("DAL:insert_query " . __FILE__ . " " . __LINE__); // INSERT INTO hello_world_persona ( nombre ,edad ,class ,id ,deleted ) VALUES ('pepe' ,'12' ,'Persona' ,'6' ,'' ); if (!$tableName) { $tableName = YuppConventions::tableName($object); } $q = "INSERT INTO " . $tableName . " ( "; // DBSTD $attrs = $object->getAttributeTypes(); // Recorro todos los atributos simples... $tableAttrs = ""; foreach ($attrs as $attr => $type) { $tableAttrs .= DatabaseNormalization::col($attr) . " ,"; // DBSTD } $tableAttrs = substr($tableAttrs, 0, sizeof($tableAttrs) - 2); $q .= $tableAttrs; $q .= ") VALUES ("; // El codigo es distinto al de update porque la forma de la consulta es distinta. // TODO: Si el valor es null tengo que poner null en la tabla, no el string vacio. // TODO: Verificar atributos no nullables en null en la instancia de la clase, esto falta agregar cosas a la clase persistente, "las restricciones" $tableVals = ""; foreach ($attrs as $attr => $type) { $value = $object->aGet($attr); // Valor del atributo simple. if ($value === NULL) { $tableVals .= "NULL ,"; } else { if (is_string($value)) { $tableVals .= "'" . addslashes($value) . "' ,"; } else { if (is_bool($value)) { $tableVals .= "'" . ($value === true ? "1" : "0") . "' ,"; } else { $tableVals .= "'" . $value . "' ,"; } } } // FIXME: OJO, si no es literal no deberia poner comillas !!!! y si es null deberia guardar null //echo $attr . " tiene tipo: " . gettype($value) . " y valor '" . $value . "'<br/>"; } $tableVals = substr($tableVals, 0, sizeof($tableVals) - 2); $q .= $tableVals; $q .= ");"; // Si hay una excepcion, llega hasta la capa superior. $this->db->execute($q); }
echo $attr; ?> </th> <?php } ?> </tr> <?php foreach ($list as $po) { ?> <tr> <?php foreach ($attrs as $attr => $type) { ?> <?php if ($attr === 'deleted' || DatabaseNormalization::isSimpleAssocName($attr)) { continue; } // No quiero mostrar la columna 'deleted' o hasone attr_id ?> <td> <?php if ($attr == "id") { ?> <?php // Si en la aplicacion actual existe el controlador para esta clase de dominio, // que vaya a la aplicacion actual y a ese controller. // Si no, va a la app y controller "core". if ($theApp->hasController($po->aGet('class'))) { ?> <?php
public function aGet($attr) { //Logger::getInstance()->po_log("PO:aGet $attr"); // Si no es un atributo simple tengo que ver si hago lazy load... if (!array_key_exists($attr, $this->attributeTypes)) { // Si llega aqui estoy seguro de que no pide un atributo simple, se pide uno complejo. // Podria ser simple pero se paso un nombre normalizado para una columna. // Si el rol tiene el nombre de la assoc declarado, necesito ver cual es el nombre // completo de la key en hasOne o hasMany porque usa attribute__assocName. $attr = $this->getRoleWithAssocName($attr); // Soporte para lazy loading para hasOne y hasMany // No verifico que tenga valor porque deberia venir inicializado //if ( isset($this->attributeValues[$attr]) && $this->attributeValues[$attr] === self::NOT_LOADED_ASSOC ) if ($this->attributeValues[$attr] === self::NOT_LOADED_ASSOC) { // Si no tiene ID todavia no se guardo, entonces no puede cargar lazy algo que no se ha guardado. if (!isset($this->attributeValues['id'])) { return NULL; } $pm = PersistentManager::getInstance(); if (array_key_exists($attr, $this->hasMany)) { $pm->get_many_assoc_lazy($this, $attr); // El atributo se carga, no tengo que setearlo... // Se marca el dirtyMany al pedir hasMany porque no se tiene control // sobre como se van a modificar las instancias de la relacion solicitadas, // si dirtyMany esta en false y las intancias son modificadas, al salvar esta // intancia, las hasMany no se van a salvar en cascada. $this->dirtyMany = true; } else { if (array_key_exists($attr, $this->hasOne)) { // Si hay id de asociacion, lo cargo, si no lo pongo en NULL $assocAttr = DatabaseNormalization::simpleAssoc($attr); // email_id $assocId = $this->attributeValues[$assocAttr]; if ($assocId != NULL) { $this->attributeValues[$attr] = $pm->get_object($this->hasOne[$attr], $assocId); // Se marca el dirtyOne al pedir hasOne porque no se tiene control sobre como se va a modificar la instancia solicitada. $this->dirtyOne = true; } else { $this->attributeValues[$attr] = NULL; } } else { // Aun puede ser simple porque se pide por el nombre de la columna en lugar del nombre del atributo, // entonces primero hay que buscar si no se pide por el nombre de la columna. Idem a lo que hago en aSet. foreach ($this->attributeTypes as $classAttr => $type) { if (DatabaseNormalization::col($classAttr) == $attr) { if (isset($this->attributeValues[$classAttr])) { return $this->attributeValues[$classAttr]; } else { return NULL; } } } throw new Exception("El atributo " . $attr . " no existe en la clase (" . get_class($this) . ")"); } } } // si no esta cargada } // si no es simple // Devuelve atributo hasOne o hasMany (la devolucion de atributos simples se hace arriba). // Si el hasOne o hasMany no estaban cargados, fueron cargados bajo demanda y devueltos aqui. if (isset($this->attributeValues[$attr])) { return $this->attributeValues[$attr]; } return NULL; }
/** * generate * Genera la tabla para una clase y todas las tablas intermedias * para sus relaciones hasMany de la que son suyas. * * Si dalForApp es NULL se usa this->dal, de lo contrario se usa esa DAL. */ private static function generate($ins, $dalForApp = NULL) { Logger::getInstance()->pm_log("TableGen::generate"); // La DAL que se va a usar //$dal = $this->dal; //if ($dalForApp !== NULL) $dal = $dalForApp; $dal = $dalForApp; // FIXME: creo que siempre se pasa dalForApp, no hay porque setear null por defecto. // TODO: Si la tabla existe deberia hacer un respaldo y borrarla y generarla de nuevo. //DROP TABLE IF EXISTS `acceso`; // Si la clase tiene un nombre de tabla, uso ese, si no el nombre de la clase. $tableName = YuppConventions::tableName($ins); // Ya se sabe que id es el identificador de la tabla, es un atributo inyectado por PO. $pks = array(array('name' => 'id', 'type' => Datatypes::INT_NUMBER, 'default' => 1)); /* EJEMPLO de la estructura que se debe crear. $cols = array( array('name' => 'name', 'type' => Datatypes :: TEXT, 'nullable' => false), // FK array('name' => 'ent_id', 'type' => Datatypes :: INT_NUMBER, 'nullable' => true) ); */ // ===================================================================================================== // $nullable = NULL; // Hay que determinar si el atributo es nullable. // Si es una clase de nivel 2 o superior y esta mapeado en la misma tabla que su superclase, // todos sus atributos (declarados en ella) deben ser nullables. // TODO: ahora no tengo una funcionalidad que me diga que atributos estan declarados en que // clase, por ahora le pongo que todos sus atributos sean nullables. // ===================================================================================================== // FIXME: no sirve chekear por la clase porque la instancia que me pasan es un merge de todas las // subclases que se mapean en la misma tabla, asi que puede ser que parent_class sea POe igual // tenga que declarar nullables. // >> Solucion rapida <<, para los atributos de las subclases, en generateAll inyectarles // contraints nullables true. // Son iguales, no se sobreescribe el valor de "class" por el de la instancia real porque no interesa, // solo son instancias de merges de POs para una tabla. //Logger::getInstance()->log( "getClass: " . $ins->getClass() ); //Logger::getInstance()->log( "GET_CLASS: " . get_class($ins) ); // if ( get_parent_class($ins) != PersistentObject && // self::isMappedOnSameTable($ins->getClass(), get_parent_class($ins)) ) // { // $nullable = true; // } // ===================================================================================================== $cols = array(); $attrs = $ins->getAttributeTypes(); // Ya tiene los MTI attrs! foreach ($attrs as $attr => $type) { if ($attr !== 'id') { $cols[] = array('name' => $attr, 'type' => $type, 'nullable' => DatabaseNormalization::isSimpleAssocName($attr) ? true : $ins->nullable($attr)); } } // ==================================================================================================== // Sigue fallando, genera esto: (el vacio en nullable es el false) // [5] => Array // ( // [name] => entrada_id // [type] => type_int32 // [nullable] => // ) // Mientras que tengo esto en el objeto: (o sea la constraint nullable esta en true) // [entrada_id] => Array // ( // [0] => Nullable Object // ( // [nullable:private] => 1 // ) // ) // El problema es que PO.nullable cuando es un atributo de referencia hasOne, // se va a fijar si el atributo hasOne es nullable, y en este caso el atributo // NO es nullable, lo que hace a la referencia no nullable. // SOLUCION!: Lo resuelvo fijandome si es un atributo de referencia, lo hago // nullable, si no me fijo en si es nullable en el PO. // ========================================================= //Logger::struct( $cols, "=== COLS ===" ); $dal->createTable2($tableName, $pks, $cols, $ins->getConstraints()); // Crea tablas intermedias para las relaciones hasMany. // Estas tablas deberan ser creadas por las partes que no tienen el belongsTo, o sea la clase duenia de la relacion. // FIXME: si la relacion hasMany esta declarada en una superClase, la clase actual tiene la // relacion pero no deberia generar la tabla de JOIN a partir de ella, si no de la // tabla en la que se declara la relacion. $hasMany = $ins->getHasMany(); foreach ($hasMany as $attr => $assocClassName) { //Logger::getInstance()->pm_log("AssocClassName: $assocClassName, attr: $attr"); //if ($ins->isOwnerOf( $attr )) Logger::show("isOwner: $attr", "h3"); //if ($ins->attributeDeclaredOnThisClass( $attr )) Logger::show("attributeDeclaredOnThisClass: $attr", "h3"); // VERIFY, FIXME, TODO: Toma la asuncion de que el belongsTo es por clase. // Podria generar un problema si tengo dos atributos de la misma clase pero // pertenezco a uno y no al otro porque el modelo es asi. // Para casos donde no es n-n el hasMany, lo que importa es donde se declara la relacion, // no que lado es el owner. Para la n-n si es importante el owner. // Verifico si la relacion es hasMany n-n if ($ins->getClass() !== $assocClassName) { $hmRelObj = new $assocClassName(NULL, true); if ($hmRelObj->hasManyOfThis($ins->getClass())) { if ($ins->isOwnerOf($attr)) { self::generateHasManyJoinTable($ins, $attr, $assocClassName, $dal); } } else { if ($ins->attributeDeclaredOnThisClass($attr)) { self::generateHasManyJoinTable($ins, $attr, $assocClassName, $dal); } } } else { if ($ins->attributeDeclaredOnThisClass($attr)) { self::generateHasManyJoinTable($ins, $attr, $assocClassName, $dal); } } } }
/** * Devuelve HTML para edicion de un objeto como una tabla con 2 columnas, la primera de nombres de campos la segunda con campos con valores para modificar. */ private static function display_edit(PersistentObject $po) { $res = '<table>'; $attrs = $po->getAttributeTypes(); foreach ($attrs as $attr => $type) { // Los atributos inyectados no se deberian poder editar! $res .= '<tr><td>'; $res .= $attr; // TODO: Habria que ver si esto es i18n, deberia haber algun "display name" asociado al nombre del campo. $res .= '</td><td>'; if ($po->isInyectedAttribute($attr)) { //$res .= $po->aGet($attr); $res .= self::field_to_html_show($attr, $type, $po->aGet($attr)); } else { if (DatabaseNormalization::isSimpleAssocName($attr)) { // Si es un fk a un id de un hasOne, quiero mostrar una lista de los posibles ids // de la clase de la relacion HO que le puedo asignar, y como esto es create o edit, // si tiene un valor actual, deberia quedar seleccionado en el select. $currentValue = $po->aGet($attr); // Puede ser NULL $role = DatabaseNormalization::getSimpleAssocName($attr); // email_id -> email $relClass = $po->getAttributeType($role); // Clase de la relacion HO // Objetos que puedo tener relacionadoss // Se puede en PHP 5.3.0... //$list = $relClass::listAll(new ArrayObject()); // Objetos que podria tener asociados // ... pero por las dudas ... $list = call_user_func_array(array($relClass, 'listAll'), array(new ArrayObject())); $select = '<select name="' . $attr . '"><option value=""></option>'; foreach ($list as $relObj) { $sel = $currentValue == $relObj->getId() ? ' selected="true"' : ''; $select .= '<option value="' . $relObj->getId() . '"' . $sel . '>' . $relClass . '[' . $relObj->getId() . ']</option>'; // TODO: Si se tuviera un toString en la clase se mostraria mejor } $select .= '</select>'; $res .= $select; } else { $maxStringLength = NULL; if ($type === Datatypes::TEXT) { $maxLengthConstraint = $po->getConstraintOfClass($attr, 'MaxLengthConstraint'); if ($maxLengthConstraint !== NULL) { $maxStringLength = $maxLengthConstraint->getValue(); } } $res .= self::field_to_html_edit($attr, $type, $po->aGet($attr), $maxStringLength); // Si el campo tiene errores, los muestro if ($po->getErrors()->hasFieldErrors($attr)) { $res .= '<div class="errors">'; $res .= self::fieldErrors($po, $attr); $res .= '</div>'; } } } $res .= '</td></tr>'; } $res .= '</table>'; return $res; }