Plusieurs préfixes de table MYSQL avec Laravel
Note de contexte
En gestion d'une application multi-servicielle, j'ai pour mission de créer un hub client permettant à ses utilisateurs d'accéder à la donnée provenant d'une même base de données, mais avec des préfixes de tables différents.
L'usage des préfixes de tables est très pratique pour segmenter les applications ou les fonctionnalités au sein d'une même base de données. Cela permet de conserver une architecture de données unifiée tout en isolant les accès et les données propres à chaque service ou module.
La configuration
Au sein de votre application Laravel, il existe le fichier config/database.php
qui permet de dresser la liste des connexions SQL
nécéssaires, par défaut sur mysql
, la config est la suivante :
return [
...,
"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' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
...,
];
Notez la présence du préfixe
à vide dans le tableau. Si un préfixe
est défini ici, le builder de query de Laravel
l'ajoutera automatiquement dans les requêtes SQL
générées.
C'est donc dans ce fichier qu'on va définir l'accès à nos tables, en ajoutant autant de connexions que de préfixes.
Factoriser la configuration des connexions
Je ne vais pas répéter la config de base à chaque fois, je sors la config de base du tableau renvoyé par la config :
$baseConnection = [
'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' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
];
Surcharger nos préfixes
et dans ma config, je n'ai plus qu'à sur-charger en fonction du besoin en utilisant l'opérateur de décomposition (...) .
$baseConnection = [/* ma config de base */];
return [
'mysql_pref_1' => [
...$baseConnection, 'prefix' => 'pref_1_'
],
'mysql_pref_2' => [
...$baseConnection, 'prefix' => 'pref_2_'
]
]
Notez que cette méthode vous permet de surcharger ce que vous voulez au sein d'un tableau PHP
.
Comment accèder à la connexion en fonction du Model
Par défaut, Laravel utilise la connexion définit dans votre fichier .env soit mysql
.
Laravel nous permet simplement de spécifier la connexion au sein du model via la propriété protégée :
protected $connection = 'mysql_pref_2';
Cela nous oblige à spécifier la connexion dans chaque model, ce n'est pas très intéressant, pour modifier ce comportement, nous allons utiliser l'héritage de classe
, PHP
ne propose pas d'héritage multiple, (cette manière part du principe 1 pour 1 qu'un model = 1 connexion).
Créons un classe, MonPref2Model qui étendera Model.
namespace App\Models\NamespacePref2;
use Illuminate\Database\Eloquent\Model;
abstract class MonPref2Model extends Model
{
protected $connection = 'mysql_pref_2';
}
Ainsi dans votre model, vous n'avez plus qu'à étendre ce model :
namespace App\Models\NamespacePref2;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use App\Models\NamespacePref2\OrderLine;
class Order extends MonPref2Model
{
use HasFactory;
protected $table = 'orders';
...
}
Et utiliser votre model comme bon vous semble :
$orders = Order::paginate(12);
...
Voilà c'est tout, j'espère que cet article vous a intéressé, si vous voyez des points d'optimisation et une façon plus élégante de le faire, n'hésitez pas à les partager en commentaire.