принадлежит нескольким отношениям в Laravel для нескольких баз данных

У меня есть модель A и модель B которые лежат в двух разных базах данных.

Теперь у меня есть pivot_table, называемая a_bs в той же базе данных, что и модель A

Я установил reltoMany relatinoship как это в модели A

 public function bs() { return $this->belongsToMany('B', 'a_bs', 'a_id', 'b_id'); } 

Когда я пытаюсь получить доступ к этим отношениям так:

 $a = A::find($id); print_r($a->bs->lists('id')); 

Я получаю сообщение об ошибке, что моя сводная таблица не существует в базе данных модели B. Это очевидно правильно, поскольку сводная таблица находится в базе данных модели A. Как я могу позволить Ларавелу это знать?

Не предлагайте размещать сводную таблицу в базе данных модели B

Очень просто:

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

Я получаю имя базы данных динамически, потому что мое соединение настроено на основе переменной среды. Кажется, что Laravel полагает, что сводная таблица существует в той же базе данных, что и целевое отношение, поэтому это заставит ее вместо этого искать базу данных, соответствующую модели, в которой находится этот метод, вашей области «A».

Изменить: если вас не беспокоят базы данных SQLite, то есть в рамках модульного теста, это все, что вам нужно. Но если да, продолжайте читать.

TL; DR: Следите за ссылкой GitHub, которую я собираюсь добавить в свой профиль. В какой-то момент в следующем месяце я собираюсь предоставить пакет поддержки Legacy Support, который помогает Laravel справиться с нетрадиционными базами данных, а также MockDatabases которая выполняет все следующие для вас, будет частью этого.


Во-первых, предыдущий пример недостаточен сам по себе. Значение базы данных $ будет в конечном итоге являться файловым путем, поэтому вам нужно сгладить его, чтобы не нарушить SQL-запрос и сделать его доступным для текущего соединения. "ATTACH DATABASE '$database' AS $name" – это то, как вы это делаете:

 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'); } 

Предупреждение: транзакции устраняют это: если текущее соединение использует транзакции, оператор ATTACH DATABASE завершится с ошибкой. Вы можете использовать транзакции на нем после выполнения этого оператора. Если связанное соединение использует транзакции, результирующие данные будут бесшумно отображаться на текущем. Это заставило меня замочить дольше, чем я хотел бы признать, потому что мои запросы бегали без ошибок, но продолжали оставаться пустыми. Кажется, что только данные, действительно записанные в прикрепленную базу данных, фактически доступны для того, к которому она привязана.

Таким образом, после того, как вы будете вынуждены писать в свою прикрепленную базу данных, вам все равно может потребоваться, чтобы ваш тест очистился после себя. Простое решение было бы просто использовать $this->artisan('migrate:rollback', ['--database' => $attachedConnectionName]); , Но если у вас есть несколько тестов, которые нуждаются в одних и тех же таблицах, это не очень эффективно, так как это заставляет их перестраивать их каждый раз.

Лучшим вариантом было бы усечение таблиц, но оставить их структуру в такте:

 //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'"); }); } 

Это уничтожит все данные из этого соединения , но нет причин, по которым вы не можете применить к нему какой-то фильтр, который, как вы считаете, подходит. В качестве альтернативы вы можете воспользоваться тем фактом, что SQLite DB – это легкодоступные файлы и просто скопируйте прикрепленный файл в файл temp и используйте его для перезаписывания источника после выполнения теста. Результат будет функционально идентичен транзакции.

Это возможно только в том случае, если две базы данных находятся на одном и том же соединении (сервере).

Его невозможно сделать на двух разных соединениях (серверах), так как вам понадобятся все три таблицы на одном сервере, которые будут выполнять команду соединения.

Вы можете установить базу данных таблицы в классе модели:

 protected $table = 'A.a_s'; 

И вы должны использовать единую форму, когда создаете сводную таблицу.

/app/model/A.php

 class A extends Eloquent { // Set table name (plural) with database name protected $table = 'A.a_s'; // Many to many relation public function b_s() { return $this->belongsToMany('B'); } } 

/app/model/B.php

 class B extends Eloquent { // Set table name (plural) with database name protected $table = 'B.b_s'; } 

запрос

 print_r(A::with('b_s')->where('id', 1)->get()->toArray()); 

MySQL

 CREATE TABLE `A`.`a_s` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (`id`) ); CREATE TABLE `B`.`b_s` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (`id`) ); CREATE TABLE `A`.`a_b` ( `a_id` int(10) unsigned NOT NULL, `b_id` int(10) unsigned NOT NULL, PRIMARY KEY (`a_id`,`b_id`) ) ENGINE=InnoDB; INSERT INTO A.a_s VALUES (NULL); INSERT INTO A.a_s VALUES (NULL); INSERT INTO B.b_s VALUES (NULL); INSERT INTO A.a_b VALUES (1,1); INSERT INTO A.a_b VALUES (1,2); 

Если базы данных / схемы находятся на том же хост-сервере, что и @NiRR, просто сделайте это, чтобы переопределить схему по умолчанию для второго соединения:

 return $this->belongsToMany('B', 'real-schema-name.a_bs'); 

или альтернативно

 return $this->belongsToMany('A', 'real-schema-name.a_bs'); 

В зависимости от того, какая модель (A или B) определена с соединением, которое не использует схему по умолчанию.

Помните, что невозможно выполнить запрос соединения, который охватывает два сервера; на каком сервере он будет выполняться? В каждом из них отсутствуют некоторые необходимые данные, необходимые для предварительного запроса.

Я сделал это следующим образом


Определены константы на env

 DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=system DB_USERNAME=root DB_PASSWORD=1234 

Моя database.php выглядит ниже

 'connections' => [ 'mysql' => [ 'driver' => 'mysql', 'host' => env('DB_HOST', 'localhost'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', 'strict' => false, 'engine' => null, ], 'tenant' => [ 'driver' => 'mysql', 'host' => 'localhost', 'database' => '', 'username' => 'root', 'password' => '1234', 'prefix' => '', 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', 'strict' => false, 'engine' => null, ], ], 'migrations' => 'migrations', ], ]; 

Я установил базу данных арендаторов, как показано ниже.

 public static function SetTenantDatabase(){ //get company name from session $company=Company::findOrFail(Session::get('selected_company_id')); Config::set('database.connections.tenant.host', 'localhost'); Config::set('database.connections.tenant.username', 'root'); Config::set('database.connections.tenant.password', '1234'); Config::set('database.connections.tenant.database',$company->database); DB::reconnect('tenant'); } - public static function SetTenantDatabase(){ //get company name from session $company=Company::findOrFail(Session::get('selected_company_id')); Config::set('database.connections.tenant.host', 'localhost'); Config::set('database.connections.tenant.username', 'root'); Config::set('database.connections.tenant.password', '1234'); Config::set('database.connections.tenant.database',$company->database); DB::reconnect('tenant'); } 

У меня есть одна таблица «security_group» в базе данных арендатора и одна таблица «пользователь» в главной базе данных, и у меня есть одна таблица security_group_user в основной базе данных со многими и многими отношениями между пользователем и группой безопасности.

Моя модель пользователя выглядит ниже

 class User extends Authenticatable { public function SecurityGroup() { return $this->belongsToMany('App\SecurityGroup', env('DB_DATABASE').'.security_group_user', 'user_id', 'security_group_id'); } } 

Моя модель SecurityGroup выглядит ниже

 class SecurityGroup extends Model { protected $connection = 'tenant'; } 

И я сделал следующее в пользовательском контроллере

 $user->save(); //attach security group $security_group=$input['security_group_id']; if(is_array($security_group)){ foreach($security_group as $key=>$val){ $user->SecurityGroup()->attach($val,['company_id' => Session::get('selected_company_id')]); } } 

Явное определение или определение соединения для обеих моделей очень просто.

 protected $connection = 'connection1'; //inside model A protected $connection = 'connection2'; //inside model B 

РЕДАКТИРОВАТЬ

Когда Laravel извлекает модель из БД, если база данных / соединение определено (как свойство) в модели, Laravel будет использовать имя базы данных для этого соединения при построении SQL. Поэтому при работе с несколькими соединениями лучше всего определить соединение для каждой модели.