laravel中的belongsToMany关系跨多个数据库

use*_*811 10 php mysql laravel laravel-4

我的模型A和模型B位于两个不同的数据库中.

现在我a_bs在与模型相同的数据库中调用了一个pivot_table A.

我在模型中设置了这样的belongsToMany relatinoship A

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

当我尝试访问这种关系时:

$a = A::find($id);
print_r($a->bs->lists('id'));
Run Code Online (Sandbox Code Playgroud)

我得到一个错误,我的数据透视表在模型B的数据库中不存在.这显然是正确的,因为数据透视表位于模型A的数据库中.我怎么能让Laravel知道呢?

不建议将数据透视表放在模型B的数据库中

Cla*_*ore 11

非常简单:

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

我正在动态获取数据库名称,因为我的连接是基于环境变量配置的.Laravel似乎假设数据透视表存在于与目标关系相同的数据库中,因此这将强制它查找与此方法所在的模型相对应的数据库,即"A"域.


如果您不担心SQLite数据库,即在单元测试范围内,那就是您所需要的.但如果你是,继续阅读.


首先,前面的例子本身是不够的.$ database的值最终将成为文件路径,因此您需要将其别名为不会破坏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');
}
Run Code Online (Sandbox Code Playgroud)

警告:事务搞砸了:如果当前连接正在使用事务,则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'");
    });
}
Run Code Online (Sandbox Code Playgroud)

这将擦除该连接中的所有数据,但是没有理由你不能对你认为合适的那种应用某种类型的过滤器.或者,您可以利用SQLite DB是易于访问的文件,并将附加的文件复制到临时文件,并在测试完成后使用它覆盖源.结果在功能上与事务相同.


小智 4

您可以在模型类中设置表的数据库:

protected $table = 'A.a_s';
Run Code Online (Sandbox Code Playgroud)

创建数据透视表时必须使用单数形式。

/应用程序/模型/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');
  } 
}
Run Code Online (Sandbox Code Playgroud)

/应用程序/模型/B.php

class B extends Eloquent {

  // Set table name (plural) with database name
  protected $table = 'B.b_s';

}
Run Code Online (Sandbox Code Playgroud)

询问

print_r(A::with('b_s')->where('id', 1)->get()->toArray());
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)