/** * Si $resursive es true, se cargan las asociaciones de la clase y tambien se pasan a json. */ public static function toJSON(PersistentObject $po, $recursive = false, $loopDetection = NULL, $currentPath = '') { if ($loopDetection === NULL) { $loopDetection = new ArrayObject(); } // Necesito hacer que cada nodo tenga una path para poder expresar las referencias por loops detectados. // La idea es simple: (ver http://goessner.net/articles/JsonPath/) // - vacio es la path que referencia al nodo raiz // - el nombre del atributo referencia a un objeto hasOne relacionado // - el nombre del atributo con notacion de array referencia a un objeto hasMany relacionado // p.e. x.store.book[0].title donde x es el objeto raiz, entonces una path valida es: .store.book[0].title $loopDetection[$currentPath] = $po->getClass() . '_' . $po->getId(); // Marca como recorrido: TODO falta la marca de loop cuando se detecta. $json = "{"; $i = 0; $n = count($po->getAttributeTypes()) - 1; foreach ($po->getAttributeTypes() as $attr => $type) { $value = $po->aGet($attr); if (is_bool($value)) { $value ? $value = 'true' : ($value = 'false'); } // Si no esta esto, aparece 1 para true y nada para false. $json .= '"' . $attr . '" : "' . $value . '"'; // TODO: si es numero, no poner comillas if ($i < $n) { $json .= ", "; } $i++; } // Agrega errores de validacion si los hay // http://code.google.com/p/yupp/issues/detail?id=86 if ($po->getErrors()->hasErrors()) { $errors = $po->getErrors(); $json .= ', "errors": {'; foreach ($errors as $attr => $theErrors) { $json .= '"' . $attr . '": ['; foreach ($theErrors as $theError) { $json .= '"' . $theError . '", '; } $json = substr($json, 0, -2); // Saco ', ' del final $json .= '], '; } $json = substr($json, 0, -2); // Saco ', ' del final $json .= '}'; } if ($recursive) { foreach ($po->getHasOne() as $attr => $clazz) { $relObj = $po->aGet($attr); if ($relObj !== NULL) { if (!in_array($relObj->getClass() . '_' . $relObj->getId(), (array) $loopDetection)) { // FIXME: las tags de los atributos hijos de la instancia raiz deberian // tener su nombre igual al nombre del atributo, no el nombre de // la clase. Con este codigo es el nombre de la clase. $json .= ', "' . $attr . '": ' . self::toJSON($relObj, $recursive, $loopDetection, $currentPath . '.' . $attr); } else { // Agrego un objeto referencia $keys = array_keys((array) $loopDetection, $relObj->getClass() . '_' . $relObj->getId()); $path = $keys[0]; $json .= ', "' . $attr . '": "' . $path . '"'; } } } foreach ($po->getHasMany() as $attr => $clazz) { // TODO: type de la coleccion, en el de XML tengo: //$hm_node->setAttribute( 'type', $obj->getHasManyType($attr) ); // list, colection, set //$hm_node->setAttribute( 'of', $obj->getAttributeType($attr) ); // clase de las instancias que contiene la coleccion $relObjs = $po->aGet($attr); if (count($relObjs) > 0) { $json .= ', "' . $attr . '": '; $json .= self::toJSONArray($relObjs, $recursive, $attr, $loopDetection, $currentPath); /* $json .= ', "'. $attr .'": ['; echo "attr hasMany $attr<br/>"; $idx = 0; // Se usa para la referencia por loop en la JSON path foreach ($relObjs as $relObj) { echo 'relObj: '. $relObj->getClass().'_'.$relObj->getId() .'<br/>'; if(!in_array($relObj->getClass().'_'.$relObj->getId(), (array)$loopDetection)) // si no esta marcado como recorrido { $json .= self::toJSON( $relObj, $recursive, $loopDetection, $currentPath.'.'.$attr.'['.$idx.']' ) .', '; } else // referencia por loop { // Agrego un objeto referencia $keys = array_keys((array)$loopDetection, $relObj->getClass().'_'.$relObj->getId()); $path = $keys[0]; $json .= '"'.$attr.'": "'. $path .'", '; } $idx++; } $json = substr($json, 0, -2); // Saco ', ' del final $json .= ']'; */ } } } $json .= '}'; return $json; }
/** * Nombre de la tabla que almacena informacion sobre la relacion entre 2 instancias de PO. * El nombre de la tabla de la relacion es el nombre derivado de la instancia1 concatenado * al nombre del atributo que apunta a la instancia 2, concatenado al nombre derivador de * la instancia 2. * @param PersistentObject $ins1 Instancia duenia de la asociacion. * @param String Atributo de $ins1 que apunta a $ins2. * @param PersistentObject $ins2 Instancia hija en la asociacion. */ public static function relTableName(PersistentObject $ins1, $inst1Attr, PersistentObject $ins2) { // Problema: si el atributo pertenece a C1, y $ins1 es instancia de G1, // la tabla que se genera para hasMany a UnaClase es "gs_unaclase_" // y deberia ser "cs_unaclase_", esto es un problema porque si cargo // una instancia de C1 no tiene acceso a sus hasMany "UnaClase". // TODO: esta es una solucion rapida al problema, hay que mejorarla. // Esta solucion intenta buscar cual es la clase en la que se declara el atributo hasMany // para el que se quiere generar la tabla intermedia de referencias, si no la encuentra, // es que el atributo hasMany se declaro en $ins1. // Tambien hay un problema cuando hay composite> // Si ins1->hasMany[ins1Attr] es a una superclase de ins2, genera mal el nombre de la tabla de join. // El nombre se tiene que generar a la clase para la que se declara le hasMany, // no para el nombre de tabla de ins2 (porque ins2 puede guardarse en otra tabla // que no sea la que se guarda su superclase a la cual fue declarado el hasMany // ins1->hasMany[inst1Attr]). // Solucion: ver si la clase a la que se declara el hasMany no es la clase de ins2, // y verificar si ins2 se guarda en otra tabla que la clase a la que se // declara el hasMany en ins1. Si es distinta, el nombre debe apuntar al // de la clase declarada en el hasMany. (aunque en ambos casos es a esto, // asi que no es necesario verificar). $classes = ModelUtils::getAllAncestorsOf($ins1->getClass()); //Logger::struct( $classes, "Superclases de " . $ins1->getClass() ); $instConElAtributoHasMany = $ins1; // En ppio pienso que la instancia es la que tiene el atributo masMany. foreach ($classes as $aclass) { $ins = new $aclass(NULL, true); //if ( $ins->hasManyOfThis( $ins2->getClass() ) ) // la clase no es la que tenga el atributo, debe ser en la que se declara el atributo if ($ins->attributeDeclaredOnThisClass($inst1Attr)) { //Logger::getInstance()->log("TIENE MANY DE " . $ins2->getClass()); $instConElAtributoHasMany = $ins; break; } //Logger::struct( $ins, "Instancia de $aclass" ); } $tableName1 = self::tableName($instConElAtributoHasMany); //echo "=== " . $ins1->getAttributeType( $inst1Attr ) . " ==== <br/>"; // La tabla de join considera la tabla en la que se guardan las instancias del tipo // declarado en el hasMany, NO A LOS DE SUS SUBCLASES!!! (como podia ser ins2) $tableName2 = self::tableName($ins1->getAttributeType($inst1Attr)); // $tableName2 = self::tableName( $ins2 ); // TODO: Normalizar $inst1Attr ? // echo "Nombre tabla relTableName: ". $tableName1 . "_" . $inst1Attr . "_" . $tableName2 ."<br/>"; return $tableName1 . "_" . $inst1Attr . "_" . $tableName2; // owner_child }
/** * Agrega una instancia de PO a la coleccion de una relacion hasMany. * La operacion se hace en memoria, no guarda en la base de datos. */ public function aAddTo($attribute, PersistentObject $value) { Logger::getInstance()->po_log("PO:aAddTo {$attribute} []=" . $value->getClass()); // CHEK: attribute es un atributo hasMany // 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. $attribute = $this->getRoleWithAssocName($attribute); // TODO: Se podria poner la restriccion de que no se puede hacer set('id', xxx); // o sea el id no se puede modificar por el usuario. // (asi puedo asumir que si no tiene id es xq no esta guardado... y me ahorro consultar si existe en la base) // Aqui se hace todo lo del codigo comentado abajo $this->lazyLoadHasMany($attribute); // Chekeo de tipos con el tipo definido en hasMany para este atributo. // Si es colection, se agrega normalmente, // si es set se verifica que no hay otro con el mismo id, // si es list al salvar y cargar se respeta el orden en el que se agregaron los elementos. $add = false; switch ($this->hasManyType[$attribute]) { case self::HASMANY_COLLECTION: case self::HASMANY_LIST: // Por ahora hace lo mismo que COLECTION, en PM se verificaria el orden. $add = true; break; case self::HASMANY_SET: // Buscar repetidos por id, si ya esta no agrego de nuevo. $found = false; reset($this->attributeValues[$attribute]); $elem = current($this->attributeValues[$attribute]); while ($elem) { if ($elem->getId() === $value->getId()) { $found = true; break; // while } $elem = next($this->attributeValues[$attribute]); } $add = !$found; // Agrega solo si no esta. break; } if ($add) { $this->attributeValues[$attribute][] = $value; // TODO: Verificar que args0 es un PersistentObject y es simple! // FIXME: bool is_subclass_of ( mixed $object, string $class_name ) $this->dirtyMany = true; // Marca como editado el hasMany } }