Mysql
 sql >> Base de données >  >> RDS >> Mysql

Relation d'appartenances à plusieurs dans Laravel sur plusieurs bases de données

Très simplement :

public function bs()
{
    $database = $this->getConnection()->getDatabaseName();
    return $this->belongsToMany('B', "$database.a_bs", 'a_id', 'b_id');
}

J'obtiens le nom de la base de données de manière dynamique car ma connexion est configurée en fonction d'une variable d'environnement. Laravel semble supposer que le tableau croisé dynamique existe dans la même base de données que la relation cible, ce qui le forcera à rechercher à la place la base de données correspondant au modèle dans lequel se trouve cette méthode, votre domaine "A".

Si vous n'êtes pas préoccupé par les bases de données SQLite, c'est-à-dire dans le cadre d'un test unitaire, c'est tout ce dont vous avez besoin. Mais si c'est le cas, continuez à lire.

Tout d'abord, l'exemple précédent n'est pas suffisant à lui seul. La valeur de $database finirait par être un chemin de fichier, vous devez donc l'aliaser à quelque chose qui ne cassera pas une instruction SQL et le rendre accessible à la connexion actuelle. "ATTACH DATABASE '$database' AS $name" voici comment procéder :

public function bs()
{
    $database = $this->getConnection()->getDatabaseName();
    if (is_file($database)) {
        $connection = app('B')->getConnection()->getName();
        $name = $this->getConnection()->getName();
        \Illuminate\Support\Facades\DB::connection($connection)->statement("ATTACH DATABASE '$database' AS $name");
        $database = $name;
    }
    return $this->belongsToMany('B', "$database.a_bs", 'a_id', 'b_id');
}

Avertissement :Les transactions compliquent la tâche : Si la connexion en cours utilise des transactions, l'instruction ATTACH DATABASE échouera. Vous pouvez utiliser les transactions dessus après en exécutant cette instruction cependant.

Alors que, si le lié connexion utilise des transactions, les données résultantes seront silencieusement rendues invisibles pour l'actuelle. Cela m'a rendu fou plus longtemps que je ne voudrais l'admettre, car mes requêtes se sont déroulées sans erreur, mais n'ont pas abouti. Il semble que seules les données réellement écrites dans la base de données attachée soient réellement accessibles à celle à laquelle elle est attachée.

Ainsi, après avoir été forcé d'écrire dans votre base de données attachée, vous voudrez peut-être que votre test se nettoie après lui-même. Une solution simple serait d'utiliser simplement $this->artisan('migrate:rollback', ['--database' => $attachedConnectionName]); . Mais si vous avez plusieurs tests qui ont besoin des mêmes tables, ce n'est pas très efficace, car cela les oblige à les reconstruire à chaque fois.

Une meilleure option serait de tronquer les tableaux, mais de conserver leur structure intacte :

//Get all tables within the attached database
collect(DB::connection($database)->select("SELECT name FROM sqlite_master WHERE type = 'table'"))->each(function ($table) use ($name) {
        //Clear all entries for the table
        DB::connection($database)->delete("DELETE FROM '$table->name'");
        //Reset any auto-incremented index value
        DB::connection($database)->delete("DELETE FROM sqlite_sequence WHERE name = '$table->name'");
    });
}

Cela effacera toutes les données de cette connexion , mais il n'y a aucune raison pour que vous ne puissiez pas appliquer un filtre de ce type comme bon vous semble. Alternativement, vous pouvez profiter du fait que les bases de données SQLite sont des fichiers facilement accessibles et copier simplement celui joint dans un fichier temporaire et l'utiliser pour écraser la source après l'exécution du test. Le résultat serait fonctionnellement identique à une transaction.