Algunas veces se presentan proyectos que nos complican las cosas, y uno de esos escenarios, sería el hacer en Laravel Testing con múltiples bases de datos y seeders y realizando integración continua en Gitlab.
Contenidos
Escenario
Nuestra aplicación necesita mas de una conexión o base de datos. Esto en local no tiene problema. EL problema se complica, porque tenemos que usar Gitlab CI con Gitlab Runners y tiene que haber mas de una base de datos, y la imagen oficial de Mysql para Docker no permite de más de una base de datos, aunque hay posibilidades de hacerlo al final, ¿no es matar la mosca a cañonazos?
En determinados escenarios, sin seeding la cosa es más sencilla. Te avisaremos a lo largo del desarrollo.
- Necesitamos N bases de datos
- En algunos casos hay que hacer seeding en alguna tabla de esas bases de datos.
- Haremos testing y CI, en local y en Gitlab
Configurando el proyecto de Laravel
Para hacer Laravel Testing en un proyecto los datos deben ser adaptados a tu conexión local, de producción, etc. No hagas copy & paste sin leer y comprender lo que haces. Si lo deseas hay un artículo anterior, Laravel Ci con gitlab, Docker Runner, MySQL 8 y PHP 7.3 que podría ayudarte a comprender el mecanismo para trabajar con Gitlab CI
Config/database
Aquí necesitamos varias cosas:
- Añadir N nombres de conexión por defecto
- Añadir esas conexiones al arreglo connections
- Añadir esas conexiones en formato testing en el arreglo connections
Para ello haremos uso de las configuración, las variables de entorno, y los prefijos, siendo estos últimos los que nos permitirán trabajar con una sola base de datos, de forma fácil.
Los elementos entre <> son para personalizar y los que no pueden personalzarse pero siendo cuidadosos y personalizarlos igual en cada lugar. Un equivoco puede dar mucho squebraderos de cabeza
return [ 'default' => env('DB_CONNECTION', 'mysql'), 'connection_1' => env('DB_CONNECTION_1', 'db1'), 'connection_n' => env('DB_CONNECTION_N', 'dbn'), 'connections' => [ 'mysql' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => env('DB_PREFIX', ''), 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], 'db1' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE_1', 'forge'), 'username' => env('DB_USERNAME_1', 'forge'), 'password' => env('DB_PASSWORD_1', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => env('DB_PREFIX_1', ''), 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], 'dbn' => ...
Ahora también necesitaremos las entradas para la conexiones de testing en memoria con sqlite.
// PHPunit testing main connection 'testing' => [ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => 'default_', ], // PHPunit testing audit connection 'testing_1' => [ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => 'db1_', ],
Fichero .env
Ahora editaremos nuestro fichero .env para nuestra app
DB_CONNECTION=mysql DB_HOST=<host> DB_PORT=<3306> DB_DATABASE=<database> DB_USERNAME=<user> DB_PASSWORD=<PaSsWoRd> DB_PREFIX=<db0_> DB_CONNECTION_VMAIL=connection_1 DB_HOST_VMAIL=<host> DB_PORT_VMAIL=<3306> DB_DATABASE_VMAIL=<database2> DB_USERNAME_VMAIL=<user2> DB_PASSWORD_VMAIL=<PaSsWoRd2> DB_PREFIX_VMAIL=<db1_> ...
.env.example (para uso con Gitlab Runners)
DB_CONNECTION=mysql DB_HOST=mysql DB_PORT=3306 DB_DATABASE=database_test DB_USERNAME=<user> DB_PASSWORD=<PaSsWoRd> DB_PREFIX=<prefixdb0_> DB_CONNECTION_VMAIL=connection_1 DB_HOST_VMAIL=mysql DB_PORT_VMAIL=3306 DB_DATABASE_VMAIL=database_test DB_USERNAME_VMAIL=<user> DB_PASSWORD_VMAIL=<PaSsWoRd> DB_PREFIX_VMAIL=<prefixdb1_>
Migraciones (migrations)
Al momento de escribir este artículo no encontré mejor forma de hacer Laravel Testing con estas características y estaba algo saturado, así que es muy posible que exista un mejor método de mejorar las modificaciones en las migraciones.
El proceso propuesto, pasa por indicar en cada clase de migraciones cual es la conexión a usar, y esto lo haremos así, usando en el constructor la asignación de la conexión y modificando la forma en la que usamos el método create(), haciéndolo desde la clase heredada Schema:
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; class CreateUsersTable extends Migration { protected $schema; public function __construct() { $this->schema = Schema::connection(config('database.default')); } public function up() { $this->schema->create('users', function (Blueprint $table) { $table->bigIncrements('id'); . . . . . . $table->timestamps(); }); } public function down() { $this->schema->dropIfExists('users'); } }
En el caso de una migración con otra conexión usaríamos el valor adecuado en la construcción de la clase
public function __construct() { $this->schema = Schema::connection(config('database.connection_1')); }
Relleno de datos (seeding)
En el relleno de datos usando seeding debemos tener en cuenta la conexion sobre la que trabajamos en cada sembrador
// Ejemplo 1 public function run() { DB::connection(config('database.default'))->table('admin')->insert([... // Ejemplo 2 public function run() { DB::connection(config('database.db1'))->table('insecondb')->insert([...
Ejemplo con seeding basado en mysql
Puede ser que por necesidad necesitemos hacer sembrado desde un fichero sql. Esto nos obliga a tener dos ficheros .sql ya que la clausula INSERT INTO en un caso deberá tener el nombre de la tabla conforme a producción, y otra conforme a testing en Gitlab CI con otro prefijo
<?php use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; //use Illuminate\Support\Facades\DB; class DbSchemasSeeder extends Seeder { public function run() { if (env('DB_HOST') == 'mysql') { $sql = file_get_contents(database_path('seeds/fichero_para_testing.sql')); DB::connection(config('database.db1'))->statement($sql); } else { $sql = file_get_contents(database_path('seeds/fichero_para_local.sql')); DB::connection(config('database.default'))->statement($sql); } } }
Enlaces interesantes
- Multiple Databases in Official MySQL Container
- Multiple MySQL Databases with one MySQL container creando una imagen docker personalizada
- docker-compose with multiple databases
Agradecimientos
Gracias a Bolduc por su imagen tomada de Unsplash y a Canva por su app para editar
Comparte este articulo en