Comparte en Facebook Twitter Google+

Uno de los pasos más importantes en el diseño de una base de datos es la creación de relaciones entre las tablas, ya que nos permiten vincular y recuperar datos almacenados en múltiples tablas de una manera eficiente. Para crear este vínculo se debe especificar una clave foránea en una tabla que haga referencia a una columna en otra tabla. Sin embargo, debemos asegurarnos que la relación sea consistente y es a través de la integridad referencial de datos que lo hacemos posible. A continuación veremos algunos aspectos a tomar en cuenta al definir claves foráneas en una base de datos en Laravel para cumplir con la integridad referencial.

La integridad referencial de datos se refiere a las restricciones o reglas que se le aplican a una base de datos a fin de garantizar que los registros entre tablas relacionadas sean válidos y consistentes, esto se hace a través del uso de restricciones de claves foráneas para así prevenir cambios accidentales y su propagación entre tablas relacionadas.

En Laravel podemos implementar la integridad referencial de datos cuando creamos las migraciones de la base de datos y definimos las claves foráneas de las tablas que se encuentran relacionadas.

Definición de migraciones y claves foráneas

Antes de comenzar debemos tener creada una base de datos en MySQL y configuradas las credenciales en el archivo .env de la aplicación.

Supongamos que en una aplicación en Laravel tenemos que trabajar con productos y categorías, entonces creamos las migraciones para definir las tablas ejecutando:

Los archivos de migraciones creados se encuentran en la carpeta database/migrations.

Agregamos algunos campos a las tablas, para categories:

Por otro lado, para la tabla products agregamos una clave foránea para restringir que un producto deba pertenecer a una categoría válida de la siguiente forma:

Con esto creamos un campo tipo entero y sin signo (no serán números negativos) category_id ya que lo asignaremos como clave foránea que hace referencia al campo id de la tabla categories y estos deben coincidir en tipo (un campo definido con el método “increments” en Laravel, resulta en un entero sin signo).

Por tanto, el archivo de migración para la tabla products podría quedar de la siguiente manera:

Hay dos cosas importantes que debemos tomar en cuenta antes de ejecutar las migraciones:

Cuando trabajamos con claves foráneas debemos especificar que el motor de almacenamiento sea ‘InnoDB’ si es que no está especificado como default, ya que es el que soporta trabajar con ellas y la integridad referencial. Lo hacemos agregando a la definición de las tablas:

Adicionalmente, cuando trabajamos con claves foráneas el orden de las migraciones importa y éste viene dado por el orden en que ejecutemos php artisan make:migration en cada archivo de migración, por tanto, se debe crear primero las migraciones para las tablas que serán referenciadas y luego las migraciones para las tablas que contendrán claves foráneas. Para este ejemplo NO podríamos hacer:

ya que se crearían los siguientes archivos:

y al ejecutar php artisan migrate  con las migraciones en ese orden, obtendríamos el siguiente error:

indicándonos que tenemos un problema de integridad referencial porque la clave foránea no puede ser creada, debido a que la tabla categories, que se está referenciando en la tabla products a través de category_id, aún no existe.

Otra forma diferente de hacerlo es crear las migraciones de cada una de las tablas sin importar el orden pero sin incluir las claves foráneas que apartaremos para ejecutarlas en la última migración de esta manera:

Luego agregamos lo siguiente al archivo creado:

De esta manera, creamos todas las tablas sin claves foráneas para evitar errores de integridad referencial y luego añadimos las claves foráneas a cada tabla.

Inserción de registros con claves foráneas

Cuando agregamos un nuevo registro a una tabla que tiene una clave foránea definida usando la integridad referencial anteriormente explicada, podemos evitar que se relacione o se referencie a un registro que no existe en la otra tabla. Si esto sucede, nos aparecerá el siguiente error:

Lo que quiere decir que el valor que le estamos asignando al campo de la clave foránea no coincide con ningún registro de la tabla referenciada.

Un caso donde nos puede ocurrir esto es cuando estamos cargando datos por medio de seeders pues debemos generar datos para las tablas referenciadas antes de insertar registros a las tablas con claves foráneas. Veamos esto con un ejemplo.

Creamos los modelos Product y Category en el directorio app, ejecutando:

Ahora, creamos los seeder para cada tabla, que se ubicaran en la carpeta database/seeds:

Para CategoriesTableSeeder agregamos algunas categorías en el método run:

y para ProductsTableSeeder:

En el método run del archivo /database/seeds/DatabaseSeeder.php debemos asegurarnos que el orden sea el siguiente para que los registros se creen correctamente al ejecutar php artisan db:seed :

Si intercambias el orden de los llamados a los seeder te aparecerá el error antes mencionado. Prueba cambiándolo y ejecuta: php artisan migrate:refresh --seed

Actualización y eliminación de registros

Las restricciones que aplicamos por medio de claves foráneas nos permiten mantener la integridad de los datos de nuestra aplicación y esto también abarca, por ejemplo, si quisiéramos eliminar una categoría solo podría ser posible si ésta no está asignada a ningún producto.

Por ejemplo, usando los registros creados con los seeder podríamos eliminar satisfactoriamente la categoría con id=1, usemos tinker para probarlo:

escribimos:

y se elimina la categoría sin embargo, hacer el mismo procedimiento para la categoría con id=2 no será posible y nos devolverá este error:

Esto es debido a la integridad referencial que definimos con la clave foránea, puesto que dicha categoría está siendo usada en algunos productos y su eliminación causaría una inconsistencia en la base de datos.

Sin embargo, sí podemos eliminar un registro con clave foránea, estableciendo en la definición de la tabla una de las dos acciones referenciales: eliminación en cascada o eliminación con set null.

  • Eliminación en cascada o ON DELETE CASCADE: Cuando se elimina un registro de la tabla padre que está siendo referenciado,  automáticamente se eliminan todos los registros que coincidan en la clave foránea de la tabla relacionada.
  • Eliminación con set null o ON DELETE SET NULL: Cuando se elimina el registro de la tabla padre, se establece el campo de la clave foránea en la tabla relacionada como NULL. Si se define una acción como SET NULL, debemos estar pendiente de no declarar el campo en la tabla como NOT NULL.

Esto también es aplicable para la actualización de registros, es decir, que podemos usar las acciones ON UPDATE CASCADE y ON UPDATE SET NULL.

En las migraciones de Laravel tenemos disponibles los métodos onDelete() y onUpdate() con los que podemos aplicar las acciones referenciales a una clave foránea.

Por ejemplo si queremos que cuando se elimine una categoría se eliminen también todos los productos relacionados a ella se define en la migración de la tabla products lo siguiente:

En cambio, si queremos que al eliminar una categoría no se eliminen los productos relacionados sino que el campo se defina como NULL en migración de la tabla products colocamos:

¡Bien! esto es todo acerca de la integridad referencial de datos en Laravel, espero que este tutorial sea de utilidad para diseñar bases de datos más consistentes y por favor ayúdanos compartiéndolo en las redes sociales.

Material relacionado

Únete a la discusión

Regístrate en Styde y obtén una invitación a nuestro Slack.

Regístrate hoy en Styde y continua mejorando tus habilidades: ver planes.

Lección anterior Traits para el desarrollo de pruebas de integración en Laravel