Repository navigation
9.3.0 - Nuevo ORM
¡Nuevo ORM!
El sistema de ORM, consultas a bases de datos y sistema de clases de modelos, ha tenido algunos cambios y mejoras pero nada significativo desde que se creó en 2020.
Esta versión introduce un nuevo y rediseñado sistema de ORM, otro breaking change. Para empezar las clases de modelo han tenido un gran cambio.
Hasta la versión anterior, las clases de modelo definían en su constructor la estructura de la tabla mediante una serie de funciones y llamaban a un método de la clase padre OModel y para acceder a las propiedades de la clase antes era necesario usar una función get
class Photo extends OModel {
/**
* Configures current model object based on data-base table structure
*/
function __construct() {
$model = new OModelGroup(
new OModelField(
name: 'id',
type: OMODEL_PK,
comment: 'Unique Id for each photo'
),
new OModelField(
name: 'id_user',
type: OMODEL_NUM,
comment: 'User Id',
ref: 'user.id'
),
new OModelField(
name: 'ext',
type: OMODEL_TEXT,
size: 5,
nullable: false,
default: '',
comment: 'Photo extension'
),
new OModelField(
name: 'alt',
type: OMODEL_TEXT,
size: 100,
nullable: false,
comment: 'alt text for the photo'
),
new OModelField(
name: 'url',
type: OMODEL_TEXT,
size: 100,
nullable: false,
comment: 'URL for the photo'
),
new OModelField(
name: 'created_at',
type: OMODEL_CREATED,
comment: 'Register creation date'
),
new OModelField(
name: 'updated_at',
type: OMODEL_UPDATED,
comment: 'Last date register was modified'
)
);
parent::load($model);
}
}A partir de esta versión ya no es necesario definir así las tablas, sino que se crean objetos (que también extienden la clase OModel) pero en los que sus atributos se corresponden con columnas de la tabla.
Usando una serie de decoradores se define el tipo de columna y sus propiedades.
- OPK Indica una clave primaria, puede haber tantas como sean necesarias (para tablas con claves compuestas por ejemplo). Las claves primarias en caso de ser numéricas por defecto se consideran campos autoincrementales, pero en el caso de que haya más de una clave primaria, automaticamente se asumirá que no son autoincrementales. Es obligatorio que haya por lo menos un campo de este tipo.
- OField Indica una columna normal de la tabla. El tipo de la variable indica su tipo en la base de datos (int -> integer, string ->varchar...)
type: puede ser NUMBER (integer), TEXT (varchar), LONGTEXT (text), FLOAT (float), BOOL (tinyint con valores 0/1) o DATE (datetime).- El tipo puede ser indicado expresamente para casos ambiguos, pero en caso de omitirse el tipo del atributo definirá el tipo de columna.
- Si se quiere indicar una columna de tipo TEXT, ya que a fin de cuentas es un string, habría que indicar la propiedad
type. nullable: indica si el campo puede contener valoresnull.default: valor por defecto para el campo.max: longitud máxima para un campo.comment: comentario para definir el campo.visible: a la hora de exportar una clase de modelo los campos pueden marcarse para que no se exporten, por ejemplo si contienen datos sensibles como contraseñas.ref: indica una clave foranea, con el formatotabla.campo.
- OCreatedAt Indica un campo de tipo datetime en el que se almacena la fecha de creación de un registro. Es un campo obligatorio.
- OUpdatedAt Indica un campo de tipo datetime en el que se almacena la fecha de última modificación de un registro. Es un campo obligatorio.
- ODeletedAt Indica un campo de tipo datetime que se utiliza para un "soft delete". En el caso de que un registro no se pueda borrar fisicamente de la base de datos por mantener consistencia con otras tablas, por guardar datos históricos... no hay más que añadir un campo de este tipo, y al realizar un borrado de un objeto que lo tenga, se actualizará este campo en lugar de borrarlo.
La clase anterior ahora tiene este aspecto:
class Photo extends OModel {
#[OPK(
comment: 'Unique Id for each photo'
)]
public ?int $id;
#[OField(
comment: 'User Id',
ref: 'user.id'
)]
public ?int $id_user;
#[OField(
comment: 'Photo extension',
max: 5,
nullable: false,
default: ''
)]
public ?string $ext;
#[OField(
comment: 'alt text for the photo',
max: 100,
nullable: false
)]
public ?string $alt;
#[OField(
comment: 'URL for the photo',
max: 100,
nullable: false
)]
public ?string $url;
#[OCreatedAt(
comment: 'Register creation date'
)]
public ?string $created_at;
#[OUpdatedAt(
comment: 'Last date register was modified'
)]
public ?string $updated_at;
}No hay constructor, menos funciones y la clase resultante es mucho más expresiva, la clase es una tabla y los atributos sus campos.
// Antes
echo $photo->get('url');
// Ahora
echo $photo->url;Transformaciones
Antes para acceder a cualquier campo era necesario usar la función get. Ya que ahora los datos de los campos están disponibles directamente en los atributos del objeto, la función get ahora tiene un nuevo significado.
Esta función ahora sirve para aplicar transformaciones a campos de tipo DATE y de tipo FLOAT.
// Campos DATE
echo $user->created_at; // Devuelve "2024-10-24 17:38:15"
echo $user->get('created_at', 'd/m/Y'); // Devuelve "24/10/2024", la fecha aplicándole una mascara como a la función "date"
// Campos FLOAT
echo $invoice->amount; // Devuelve "1249.465"
echo $invoice->get('amount', 2, ',', '.'); // Devuelve "1.249,46", el valor del campo tras pasarle la función "number_format".Métodos estáticos
Ahora las clases de modelo incluyen una serie de clases estáticas auxiliares:
create
Además de crear una clase con "new" también se puede crear una instancia con el método create:
$photo = Photo::create();
// Equivalente a
$photo = new Photo();from
Anteriormente existía un método update para actualizar los datos de un objeto. Ahora en su lugar hay un método llamado from que permite crear una nueva instancia y a la vez pasarle los datos.
// Antes
$photo = new Photo();
$photo->update($res);
// Ahora
$photo = Photo::from($res);
// O también usando el constructor
$photo = new Photo($res);findOne
Este método permite hacer una búsqueda en la que se espera obtener un único resultado, por ejemplo para buscar un registro por su clave primaria.
// Antes
$photo = new Photo();
$photo->find(['id' => 1]);
// Ahora
$photo = Photo::findOne(['id' => 1]);where
Este método permite realizar una búsqueda y obtener todos los resultados en un array con un solo comando.
// Antes
$db = new ODB();
$sql = "SELECT * FROM `photo` WHERE `id_user` = ?";
$db->query($sql, [$id_user]);
$list = [];
while ($res = $db->next()) {
$photo = new Photo();
$photo->update($res);
$list[] = $photo;
}
// Ahora
$list = Photo::where(['id_user' => $id_user]);all
Este método permite obtener todos los registros de una tabla en un solo comando.
// Antes
$db = new ODB();
$sql = "SELECT * FROM `photo`";
$db->query($sql);
$list = [];
while ($res = $db->next()) {
$photo = new Photo();
$photo->update($res);
$list[] = $photo;
}
// Ahora
$list = Photo::all();Caché de resultados
El nuevo ORM también incluye un sistema de caché de resultados, de modo que al hacer una consulta su resultado se guarda en memoria, y si se vuelve a hacer la misma consulta el resultado se obtiene inmediatamente.
Esta caché es un sistema en memoria, de modo que persiste mientras dura una ejecución, pero para la siguiente ejecución la cache estará vacía de nuevo. Al guardar un nuevo objeto o borrar un objeto la cache se vaciará automáticamente para asegurar la integridad de los datos.
Validación de modelos
En este sistema de ORM, las clases de modelo al instanciarse validan su propia estructura para asegurar la integridad de los datos. Esta validación se realiza en la primera instanciación de cada clase, de modo que si se crean dos objetos del mismo tipo, la segunda instanciación no realizará la validación de nuevo.
Además, al guardar un registro usando el método save, el propio método validará la integridad de los datos introducidos para que cumplan con el modelo de datos.
Exportación
Las clases de modelo ahora incorporan 3 funciones para exportar datos:
- toArray Devuelve un array de pares clave/valor con los nombres de los campos y sus valores actuales.
- toJSON Devuelve una representación en JSON de los datos del objeto.
- toSQL Devuelve los comandos SQL necesarios para generar la tabla que representa al objeto, pero no sus datos.