-
Notifications
You must be signed in to change notification settings - Fork 2
Modelo de datos y ORM
Documentación:
Únicamente los valores asignados a los parámetros son tratados como inseguros. Valores provinientes del usuario o de orígenes externos únicamente deberían utilizarse en una consulta mediante parámetros (utilizando
establecerValores()
,teniendo()
,donde()
y sus variantes). Jamás debe utilizarse una variable sin validar y sanitizar para formar nombres de modelos de datos, tablas o campos.
El modelo de datos de una aplicación está compuesto por dos tipos de clases: Modelos (repositorios), que heredan \modelo
, y entidades (singular), que heredan \entidad
. Todas las clases deben residir en el directorio servidor/modelo
de la aplicación (es decir, aplicaciones/aplicacion/servidor/modelo
) bajo el espacio de nombres aplicaciones\aplicacion\modelo
(donde aplicacion
es el nombre de la misma).
Las clases de modelos se corresponden con las tablas y heredan métodos para filtrar, crear, actualizar y eliminar registros (entidades), a la vez que pueden definir métodos específicos.
namespace aplicaciones\ejemplo\modelo;
class usuarios extends \modelo {
protected static $tipoEntidad=usuario::class;
protected static $tabla='tabla_usuarios'; //(opcional)
}
La propiedad $tipoEntidad
relaciona el modelo con el tipo de entidades que representa. Puede asignarse la propiedad $nombre
para cambiar el nombre de la tabla en la base de datos (por defecto, coincide con el nombre de la clase del modelo).
Las clases de entidades se corresponden con las filas y definen las propiedades y relaciones con otros modelos de datos. Estos parámetros se establecen mediante comentarios. Todas las tablas deben tener las columnas id
como clave principal y autoicremental, y e
(tipo tinyint(1)
) a utilizarse como baja lógica (e
liminado). Utilizando el gestor de aplicaciones, serán creados automáticamente.
Las clases pueden crearse en subdirectorios. En estos casos, el espacio de nombres también cambiará para ambas clases: Un modelo denominado productos/variantes
existirá en el directorio servidor/modelo/productos
en el espacio aplicaciones\aplicacion\modelo\productos
(donde aplicacion
es el nombre de tu aplicación).
Nota: Es importante recordar que mediante este mecanismo pueden existir dos modelos con el mismo nombre (en distintos espacios), pero las tablas en la base de datos tomarán el nombre de la clase sin prefijo u otra característica que las diferencie: proveedores/productos
e inventario/productos
ambas utilizarían la tabla productos
. Si se diera esta situación, las clases deben implementar la propiedad $nombre
para especificar nombres de tablas diferentes explícitamente.
Durante las operaciones de selección, serán asignadas las entidades (1:1
o 1:0
) o los listados de entidades (1:n
) en las propiedades correspondientes, a menos que se deshabilite explícitamente el procesamiento de relaciones, ya sea por consulta (métodos $modelo->omitirRelaciones()
, etc.) o por propiedad (etiqueta @omitir
).
Durante las operaciones de inserción y actualización, serán procesados los campos relacionales en forma recursiva, creando o actualizando las entidades asignadas en ellos, según corresponda. Este comportamiento se puede deshabilitar invocando omitirRelaciones()
, en cuyo caso solo se almacenará el ID de la entidad asignada, pero el modelo foráneo no se verá afectado.
Al utilizar modelos relacionales, deben utilizarse alias (mediante establecerAlias()
o @alias
) para hacer referencia a los campos en las condiciones. teniendo()
, donde()
y sus variantes siempre asumen que los campos son del propio modelo a menos que presenten el formato alias.campo
.
Importante: Cuando esté activada la opción de eliminar relaciones (mediante
$modelo->eliminarRelacionados()
o la etiqueta@eliminar
en el campo) y el campo relacional quede en0
o, en el caso de relaciones1:n
, array vacío, se eliminarán de la tabla foránea los registros previamente asignados.
Nota: Las relaciones 1:n
no se realizan con JOIN
, sino que son procesadas luego de la consulta principal. Esto significa que en grandes conjuntos de datos puede tener un severo efecto en el rendimiento del programa. Se recomienda mantener deshabilitadas las relaciones 1:n
mediante alguno de los mecanismos mencionados (según sea más conveniente para cada caso) y procesarlas explícitamente solo cuando sea necesario.
La forma más fácil de crear clases del modelo de datos es utilizando la opción Nuevo modelo de datos del gestor de aplicaciones.
Una vez que se hayan creado las entidades con sus propiedades, o tras modificar una entidad, la opción Sincronizar base de datos creará o actualizará las tablas correspondientes.
namespace aplicaciones\ejemplo\modelo;
class usuario extends \entidad {
protected static $tipoModelo=usuarios::class;
//id y e (campo de baja lógica) son automáticos, no requieren propiedad ni comentario
/**
* @tipo cadena(50)
* @indice
* @publico
*/
public $usuario;
/**
* @tipo cadena(255)
* @publico
*/
public $contrasena;
/**
* @tipo relacional
* @modelo direcciones
* @relacion 1:n
* @campo idusuario
* @orden calle asc
* @alias direcciones
*/
public $direcciones;
/**
* @tipo relacional
* @modelo imagenes
* @relacion 1:0
* @campo idfoto
*/
public $foto;
/**
* @tipo entero
*/
public $idfoto;
}
La propiedad tipoModelo
guarda la relación con el modelo al que pertenece la entidad. La clase puede tener otras propiedades que no se correspondan con campos, simplemente evitando el uso de las etiquetas reservadas en su documentación.
Cuando la clase concreta del modelo no tenga métodos ni propiedades concretas, puede optarse por omitir su declaración. Cuando la clase del modelo no exista, puede fabricarse mediante entidad::fabricarModeloPorEntidad()
(foxtrot::fabricarModelo()
no funcionará si no existe). Para que este mecanismo funcione, la entidad debe establecer explícitamente la propiedad $nombreModelo
(estática) coincidente con el nombre de la tabla en la base de datos.
La clase de la entidad generará y mantendrá automáticamente los campos:
-
fecha_alta
Fecha de alta. -
fecha_actualizacion
Fecha de actualización o modificación (incluso cuando la modificación sea establecere
a1
). -
fecha_baja
Fecha de baja (lógica) o eliminación.
Los valores de estos campos serán en tiempo Epoch.
Puede omitirse este comportamiento definiendo en la entidad concreta:
public static $omitirFechas=true;
@tipo
Define el tipo de dato: texto
, cadena(longitud)
, entero
, entero sin signo
, entero(longitud)
, entero sin signo(longitud)
, decimal(entero.decimales)
, decimal sin signo(entero.decimales)
, logico
(booleano), relacional
, fecha
, (en el futuro, se agregarán otros tipos, como fulltext o campos geoespaciales). Puede omitirse la longitud en entero
y entero sin signo
.
Nota: En los enteros, (longitud)
es la longitud en bytes, de 1 (TINYINT
) a 8 (BIGINT
). Por defecto, es 3 bytes (MEDIUMINT
).
Nota: En las cadenas, a partir de MySQL 5.0, la longitud está dada en caracteres.
@indice
Define un índice. Puede omitirse el valor para definir un índice normal, o agregar el valor unico
para definir un índice único.
@modelo
Nombre del modelo relacionado.
@relacion
Tipo de relación: 1:1
(uno a uno), 1:0
(uno a uno, o nulo), 1:n
(uno a muchos). Es importante considerar durante el diseño de una entidad que las relaciones uno a muchos se procesan iterando sobre todo el resultado y relacionando fila por fila, luego de realizar la consulta principal, por lo que debe utilizarse con precaución en grandes conjuntos de resultados.
@alias
Establece el alias de la tabla relacionada. Si se omite, se utilizará un alias interno al cual no debe hacerse referencia ya que puede variar entre consultas.
En el ejemplo anterior, dado que
$direcciones
tiene@alias direcciones
, en las consultas podría agregarse una condición del estilodonde(['direcciones.calle'=>'San juan'])
.
@omitir
Omite el campo relacional en el procesamiento automático de relaciones. Si una propiedad tiene esta etiqueta para que se omita por defecto. Pero si, en un caso en particular, se desea obtener la entidad relacionada, se debe utilizar el método $modelo->procesarRelaciones()
.
@simple
Por defecto, los campos relacionales son de doble sentido: Se asignan al seleccionar y actualizan/crean la entidad relacionada al guardar. Utilizando la etiqueta @simple
, únicamente se seleccionará la relación, pero los cambios en el valor de la propiedad nunca afectarán la entidad original.
@eliminar
Si, al modificar un registro, el valor del campo relacional pasa de tener un valor a 0
, o se modifica el listado de elementos de una relación 1:n
, se eliminarán los registros de la tabla foránea que ya no formen parte de la relación.
@campo
(preferido) o @columna
(deprecado) Columna de la tabla. Las propiedades que reciben el elemento foráneo no deben necesariamente coincidir con los nombres de las columnas. Sin embargo, se requiere que exista una propiedad para las columnas especificadas. Por defecto, se realizará la relación por la columna especificada contra la clave primaria (id
). Nótese que cuando la relación es 1:n
, @campo
hace referencia a la columna de la tabla foránea. Cabe mencionar que la clase del modelo permite establecer relaciones más complejas en forma manual.
@campoForaneo
Similar a @campo
, permite especificar la columna de la tabla foránea.
@predeterminado
Valor predeterminado. Si se omite, será NULL
, o 0
para los campos booleanos. Nota: No es necesario utilizar comillas para el valor predeterminado; puede espeficiarse una cadena, un valor numérico o simplemente @predeterminado
(sin valor) para una cadena vacía en lugar de NULL
.
@oculto
Omitirá el campo al seleccionar filas a menos que explícitamente se invoque incluirOcultos()
previo a ejecutar la consulta. Nótese la diferencia con @omitir
(omite las relaciones) y @privado
(lo omite únicamente de cara al cliente). $entidad->obtenerObjeto()
también excluirá los campos con @oculto
. Ejemplo de caso de uso: Campo de hash de contraseña (solo lo necesitamos durante el inicio de sesión, en cualquier otra consulta es innecesario seleccionarlo).
@privado
Excluirá el campo al extraer los valores con $entidad->obtenerObjeto()
. Nótese la diferencia con @oculto
(@privado
lo omite solo al extraer los valores, pero no al efectuar la consulta).
@publico
Solo los campos que cuenten con esta etiqueta podrán ser asignados mediante establecerValoresPublicos()
. Nótese que e
y los campos de fecha automáticos nunca pueden ser públicos, como así tampoco cualquier otra propiedad de la entidad que no sea un campo.
@busqueda campo[,campo2,campo3...]
Generará una columna donde se almacenará automáticamente información (caché) para búsqueda fonética (Soundex) entre los campos especificados, con el índice correspondiente (no es necesario agregar @indice
ni @tipo
). Cuando se filtre por esta columna, por ejemplo mediante donde(['campoBusqueda'=>'texto a buscar'])
, automáticamente se realizará la búsqueda, en lugar de filtrarse por coincidencia exacta. Asimismo, la columna será actualizada automáticamente cuando el valor de cualquiera de los campos sea modificado. Nota: El valor de la columna de búsqueda sólo será actualizado cuando todos los campos comprendidos mismo estén definidas en la consulta de actualización o inserción.
Nota: Para que el valor del campo de búsqueda se cree o actualice, todas las propiedades deben estar establecidas a la vez.
@contrasena
Los campos que presenten esta etiqueta serán gestionados como contraseñas, encriptándose automáticamente al asignarles un valor y validando la contraseña al filtrar por este campo. Esto quiere decir que dado un campo:
/**
* @contrasena
*/
public $contrasena;
Tendrá los efectos:
$modelo->establecerValores(['contrasena'=>'123'])->guardar(); //Encriptará el valor '123' en el campo contrasena
$modelo->donde(['usuario'=>'prueba','contrasena'=>'123'])->obtenerUno(); //Devolverá únicamente la fila si se logra validar la contraseña
$modelo->donde(['contrasena'=>'123'])->obtenerListado(); //Devolverá únicamente las filas cuyo campo contrasena se valide contra '123'
Nota: Solo es posible filtrar una contraseña por consulta. Es decir que algo como: $modelo->donde(['contrasena'=>'123'])->oDonde(['contrasena'=>'456'])...
tendrá resultados inesperados. La presencia de un campo de contraseña en una condición implica siempre una condición Y
.
Nota: No es posible validar contraseñas en campos de modelos relacionados.
@orden
En las propiedades relacionales 1:n
, la etiqueta @orden columna asc|desc
permite configurar el orden del listado.
@recursion
Por defecto, las relaciones son procesadas solo en el primer nivel. Es decir, si una entidad está relacionada a una entidad que, a su vez, está relacionada a otras entidades, los campos relacionales de la entidad foránea no estarán asignados. Utilizando @recursion n
(donde n
es un número mayor a 1) se puede especificar la profundidad para procesar las relaciones recursivamente. No es posible hacerlo en forma ilimitada para ayudar a prevenir relaciones cíclicas.
Existen otras otras etiquetas opcionales que son utilizadas por los asistentes:
@etiqueta
Etiqueta del campo. Por defecto, se utilizará el nombre de la propiedad.
@requerido
Campo requerido (etiqueta sin valor).
@tamano
Ancho del campo en unidades de la grilla de columnas (1 a 10). Por defecto, será 10
.
El modo de uso es tan simple como instanciar el modelo e invocar algunos de sus métodos.
$usuarios=new usuarios;
$usuario=$usuarios
->donde([
'usuario'=>$nombre
])
->obtenerUno();
Es posible filtrar consultas por coincidencia exacta con objetos, o bien utilizando cadenas SQL; estas últimas aceptan parámetros con nombre precedidos por @
:
$usuarios=new usuarios;
$usuario=$usuarios
->establecerAlias('u')
->donde('u.usuario=@usr',[
'usr'=>$nombre
])
->obtenerUno();
Los campos nulos serán excluidos de las operaciones (en otras palabras, se debe dejar una propiedad sin asignar o asignar null
para evitar que se modifique). Puede utilizarse $obj->campo=new \nulo;
si desea realmente insertarse un valor NULL
.
Ejemplo con modelos relacionados:
$usuario=new usuario;
$usuario->usuario=$nombreDeUsuario,
$usuario->foto=new foto;
$usuario->foto->archivo='foto.jpg';
(new usuarios)
->establecerValores($usuario)
->guardar(); //Creará la foto y luego el usuario
Además de especificarse valores en forma segura mediante establecerValores()
y donde()
(y sus variantes), es posible añadir fragmentos de código SQL con parámetros. Estos se definen con el formato @nombre
. Métodos que aceptan este formato son:
donde('campo=@valor',['valor'=>123])
Agrega una condición mediante SQL; el segundo parámetro es un listado de valores para las variables (variantes de donde()
como oDonde()
o yDonde()
son idénticas).
relacionar(modelo::relacion10,'modeloForaneo','alias','alias.idprueba=@valor',['valor'=>1])
Agrega una relación donde el cuarto parámetro es la condición (ON ...
) y el quinto, los valores para las variables definidas en la condición.
Toda instancia del modelo de datos puede reutilizarse reemplazando los valores de los parámetros, tanto luego de realizada la primer consulta o explícitamente preparando una sentencia.
$modelo=(new productos)
->establecerValores([
'nombre'=>'Producto Uno'
])
->crear();
$modelo->reutilizar([
'nombre'=>'Producto Dos'
])
->crear();
$modelo2=(new productos)
->donde('id=@n',[
'n'=>1
]);
$item=$modelo2->obtenerItem();
$item=$modelo2->reutilizar([
'n'=>2
])
->obtenerItem();
$modelo=(new productos)
->establecerValores([
//Deben definirse cuáles serán los campos a establecer, aunque todavía no se tengan los valores reales
'nombre'=>''
])
->preparar(productos::crear); //Parámetro: operación para la cuál se prepara
//...
$modelo->reutilizar([
'nombre'=>'Producto Uno'
])
->crear();
-
Solo las consultas que utilicen
donde()
con parámetros oestablecerValores()
pueden prepararse. Esto se debe a quereutilizar()
espera exactamente los mismos parámetros o propiedades utilizados previamente (ver ejemplos anteriores). Intentar reutilizar otro tipo de consultas resultará en error. -
No pueden procesarse relaciones
1:N
mientras se mantiene una consulta preparada. Si el modelo presenta campos relacionales1:N
, pueden deshabilitarse medianteomitirRelaciones1N()
. Tampoco pueden realizarse otras consultas (ni siquiera mediante otra instancia del modelo o en una instancia de otro modelo diferente) mientras se mantiene una consulta preparada. Cualquiera de los dos casos provocará que se descarte la sentencia preparada.
-
Si existe, el método
prepararValores($operacion)
en la entidad será invocado por el modelo previo a realizarse una consulta de inserción, actualización o baja ($operacion
puede tomar los valores\modelo::crear
,\modelo::actualizar
o\modelo::eliminar
). Este método puede implementarse para realizar pre-proceso de los datos antes de guardar (formatos, conversiones, etc.). Nótese que durante una operación de inserción,id
todavía no estará definido. Debe invocarseparent::prepararValores($operacion)
desde este método. -
Si existe, el método
procesarValores($operacion)
en la entidad será invocado por el modelo luego de haber asignado sus propiedades tras una consulta. ($operacion
puede tomar los valores\modelo::crear
,\modelo::actualizar
,\modelo::seleccionar
o\modelo::eliminar
). Este método puede implementarse para realizar post-proceso de los datos (formatos, conversiones, etc.). Nótese que se ejecutará luego de realizada la consulta y asignadas todas sus propiedades, inclusoid
y las relaciones luego de una operación de inserción. Debe invocarseparent::procesarValores()
desde este método. -
Si existe, el método
instalar()
de cada modelo será invocado luego de que hayan sido creadas todas las tablas en la base de datos. -
Se han implementado métodos para bloqueo de tablas y transacciones, que se documentarán próximamente. Puede encontrarse la documentación propia de los métodos en el PHPDOC de Modelo.
¿Probaste Foxtrot? Contanos qué te pareció 🥰 contacto@foxtrot.ar
Índice
Primeros pasos
Gestor de aplicaciones
Editor de vistas
Componentes
Módulos
Comunicación cliente<->servidor
Modelo de datos - ORM
PHPDOC
JSDOC
☝ Comentarios
🤷♂️ Dudas
🤓 Ayuda
⌨ Contribuciones
Escribinos: contacto@foxtrot.ar