Overview
当您使用关系数据库时,Eloquent ORM 将模型存储为与模型类对应的表中的行。 当您使用MongoDB时,Laravel 集成会将模型存储为与模型类对应的集合中的文档。
要定义关系,请在模型类中添加一个函数,用于调用适当的关系方法。 此函数允许您将相关模型作为动态属性进行访问。 动态属性允许您使用与访问模型属性相同的事务语法来访问相关模型。
以下部分描述了 Laravel 集成中可用的 Laravel Eloquent 和MongoDB特定关系,并举例说明了如何定义和使用这些关系:
一对一关系,使用
hasOne()方法及其反向方法belongsTo()创建一对多关系,使用
hasMany()及其反向belongsTo()创建多对多关系,使用
belongsToMany()方法创建嵌入式文档模式一种特定于 MongoDB 的关系,可以表示一对一或一对多关系,使用
embedsOne()或embedsMany()方法创建跨数据库关系,当您想要在 MongoDB 和 SQL 模型之间创建关系时需要
定义关系后,您可以计算相关模型的计数和其他聚合值,如本指南的 聚合相关模型 部分所述。
一对一关系
模型之间的一对一关系由与另一种类型的模型记录完全相关的模型记录组成。
当您添加一对一关系时,Eloquent 允许您使用动态属性访问模型,并将模型的文档 ID 存储在相关模型上。
在 Laravel MongoDB 中,您可以使用hasOne()方法或belongsTo()方法定义一对一关系。
当您使用belongsTo()方法添加反向关系时,Eloquent 允许您使用动态属性访问模型,但不添加任何字段。
要学习;了解有关一对一关系的更多信息,请参阅 一对一 Laravel 文档中的一对一。
一对一示例
以下示例类展示了如何使用hasOne()方法在Planet和Orbit模型之间定义HasOne一对一关系:
declare(strict_types=1);
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;use MongoDB\Laravel\Relations\HasOne;
class Planet extends Model{ protected $connection = 'mongodb';
public function orbit(): HasOne { return $this->hasOne(Orbit::class); }}以下示例类展示了如何使用belongsTo()方法定义Orbit和Planet之间的反向BelongsTo关系:
declare(strict_types=1);
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;use MongoDB\Laravel\Relations\BelongsTo;
class Orbit extends Model{ protected $connection = 'mongodb';
public function planet(): BelongsTo { return $this->belongsTo(Planet::class); }}以下示例代码展示了如何为每个类实例化模型并添加它们之间的关系。 单击VIEW OUTPUT按钮,查看通过运行代码创建的数据:
$planet = new Planet();$planet->name = 'Earth';$planet->diameter_km = 12742;$planet->save();
$orbit = new Orbit();$orbit->period = 365.26;$orbit->direction = 'counterclockwise';
$planet->orbit()->save($orbit);以下示例代码展示了如何使用示例类中定义的动态属性来访问相关模型:
$planet = Planet::first();$relatedOrbit = $planet->orbit;
$orbit = Orbit::first();$relatedPlanet = $orbit->planet;一对多关系
模型之间的一对多关系由作为父模型的模型和一个或多个相关的子模型记录组成。
当您添加一对多关系方法时,Eloquent 允许您使用动态属性访问模型,并在每个子模型文档中存储父模型的文档 ID。
在 Laravel MongoDB 中,您可以通过在父类上添加hasMany()方法以及在子类上添加belongsTo()方法(可选)来定义一对多关系。
当您使用belongsTo()方法添加反向关系时,Eloquent 允许您使用动态属性访问父模型,而无需添加任何字段。
要学习;了解有关一对多关系的更多信息,请参阅 一对多 Laravel 文档中的一对多。
一对多示例
以下示例类展示了如何使用hasMany()方法在Planet父模型和Moon子模型之间定义HasMany一对多关系:
declare(strict_types=1);
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;use MongoDB\Laravel\Relations\HasMany;
class Planet extends Model{ protected $connection = 'mongodb';
public function moons(): HasMany { return $this->hasMany(Moon::class); }}以下示例类展示了如何使用belongsTo()方法定义Moon子模型与Planet父模型之间的反向BelongsTo关系:
declare(strict_types=1);
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;use MongoDB\Laravel\Relations\BelongsTo;
class Moon extends Model{ protected $connection = 'mongodb';
public function planet(): BelongsTo { return $this->belongsTo(Planet::class); }}以下示例代码展示了如何为每个类实例化模型并添加它们之间的关系。 单击VIEW OUTPUT按钮,查看通过运行代码创建的数据:
$planet = new Planet();$planet->name = 'Jupiter';$planet->diameter_km = 142984;$planet->save();
$moon1 = new Moon();$moon1->name = 'Ganymede';$moon1->orbital_period = 7.15;
$moon2 = new Moon();$moon2->name = 'Europa';$moon2->orbital_period = 3.55;
$planet->moons()->save($moon1);$planet->moons()->save($moon2);以下示例代码展示了如何使用示例类中定义的动态属性来访问相关模型。
$planet = Planet::first();$relatedMoons = $planet->moons;
$moon = Moon::first();$relatedPlanet = $moon->planet;多对多关系
多对多关系由两种不同模型类型之间的关系组成,其中,对于每种模型类型,该模型的一个实例都可以与另一种类型的多个实例相关。
在 Laravel MongoDB 中,您可以通过将belongsToMany()方法添加到两个相关类来定义多对多关系。
在关系数据库中定义多对多关系时,Laravel 会创建一个数据透视表来追踪这些关系。 当您使用 Laravel 集成时,它会省略数据透视表的创建,并将相关文档ID 添加到从相关模型类名称派生的文档字段中。
提示
由于 Laravel 集成使用文档字段而不是数据透视表,因此请从belongsToMany()构造函数中省略数据透视表参数或设立其设置为null 。
要学习;了解有关 Laravel 中多对多关系的更多信息,请参阅 多对多 Laravel 文档中的多对多。
以下部分举例说明了如何在模型类之间创建多对多关系。
多对多示例
以下示例类展示了如何使用belongsToMany()方法在Planet和SpaceExplorer模型之间定义BelongsToMany多对多关系:
declare(strict_types=1);
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;use MongoDB\Laravel\Relations\BelongsToMany;
class Planet extends Model{ protected $connection = 'mongodb';
public function visitors(): BelongsToMany { return $this->belongsToMany(SpaceExplorer::class); }}以下示例类展示了如何使用belongsToMany()方法定义SpaceExplorer和Planet模型之间的反向BelongsToMany多对多关系:
declare(strict_types=1);
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;use MongoDB\Laravel\Relations\BelongsToMany;
class SpaceExplorer extends Model{ protected $connection = 'mongodb';
public function planetsVisited(): BelongsToMany { return $this->belongsToMany(Planet::class); }}以下示例代码展示了如何为每个类实例化模型并添加它们之间的关系。 单击VIEW OUTPUT按钮,查看通过运行代码创建的数据:
$planetEarth = new Planet();$planetEarth->name = 'Earth';$planetEarth->save();
$planetMars = new Planet();$planetMars->name = 'Mars';$planetMars->save();
$planetJupiter = new Planet();$planetJupiter->name = 'Jupiter';$planetJupiter->save();
$explorerTanya = new SpaceExplorer();$explorerTanya->name = 'Tanya Kirbuk';$explorerTanya->save();
$explorerMark = new SpaceExplorer();$explorerMark->name = 'Mark Watney';$explorerMark->save();
$explorerJeanluc = new SpaceExplorer();$explorerJeanluc->name = 'Jean-Luc Picard';$explorerJeanluc->save();
$explorerTanya->planetsVisited()->attach($planetEarth);$explorerTanya->planetsVisited()->attach($planetJupiter);$explorerMark->planetsVisited()->attach($planetEarth);$explorerMark->planetsVisited()->attach($planetMars);$explorerJeanluc->planetsVisited()->attach($planetEarth);$explorerJeanluc->planetsVisited()->attach($planetMars);$explorerJeanluc->planetsVisited()->attach($planetJupiter);以下示例代码展示了如何使用示例类中定义的动态属性来访问相关模型。
$planet = Planet::first();$explorers = $planet->visitors;
$spaceExplorer = SpaceExplorer::first();$explored = $spaceExplorer->planetsVisited;嵌入式文档模式
在 MongoDB 中,嵌入式文档模式将相关模型的数据添加到父模型中,而不是保留外键引用。 使用此模式可以满足以下一项或多项要求:
将关联的数据保存在单个集合中
对文档的多个字段和关联数据执行原子更新
减少获取数据所需的读取次数
在 Laravel MongoDB 中,您可以通过添加以下方法之一来定义嵌入式文档:
embedsOne()嵌入单个文档embedsMany()嵌入多个文档
注意
这些方法返回 Eloquent 集合,与查询构建器对象不同。
自动水合
当您访问权限嵌入式关系时,Laravel 集成默认将嵌入式数据返回为原始对象,而不是水合模型实例。当嵌入式文档作为原始对象返回时,以下行为应用:
id字段是ObjectId类型,而不是字符串。日期字段是
UTCDateTime对象,而不是格式化字符串。模型属性转换、方法和
toArray()序列化不应用。
要将嵌入式文档作为水合模型实例检索,请将关系名称添加到父模型上的 $with属性中。此属性会在每个查询中自动加载关系,以便 Laravel 集成将嵌入式文档水化为模型实例。
以下示例定义了一个 User 模型,该模型使用 $with 加载其嵌入式 addresses 关系:
declare(strict_types=1);
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;use MongoDB\Laravel\Relations\EmbedsMany;
class User extends Model{ protected $with = ['addresses'];
public function addresses() { return $this->embedsMany(Address::class); }}由于该模型包含 $with = ['addresses'],因此 Laravel 集成将嵌入式地址作为 Address 模型实例返回。toArray() 方法将 id字段序列化为字符串,并将日期字段序列化为格式化字符串。所有 Address 模型转换和方法也可用。
要学习;了解有关MongoDB嵌入式文档模式的更多信息,请参阅以下MongoDB Server教程:
以下部分举例说明如何使用嵌入式文档模式。
嵌入式文档示例
以下示例类展示了如何使用embedsMany()方法在SpaceShip和Cargo模型之间定义EmbedsMany一对多关系:
declare(strict_types=1);
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;use MongoDB\Laravel\Relations\EmbedsMany;
class SpaceShip extends Model{ protected $connection = 'mongodb'; protected $with = ['cargo'];
public function cargo(): EmbedsMany { return $this->embedsMany(Cargo::class); }}嵌入式模型类省略了关系定义,如以下Cargo模型类所示:
declare(strict_types=1);
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;
class Cargo extends Model{ protected $connection = 'mongodb';}以下示例代码展示了如何创建SpaceShip模型并嵌入多个Cargo模型,以及通过运行代码创建的 MongoDB 文档。 单击VIEW OUTPUT按钮可查看通过运行以下代码创建的数据:
$spaceship = new SpaceShip();$spaceship->name = 'The Millenium Falcon';$spaceship->save();
$cargoSpice = new Cargo();$cargoSpice->name = 'spice';$cargoSpice->weight = 50;
$cargoHyperdrive = new Cargo();$cargoHyperdrive->name = 'hyperdrive';$cargoHyperdrive->weight = 25;
$spaceship->cargo()->attach($cargoSpice);$spaceship->cargo()->attach($cargoHyperdrive);跨数据库关系
Laravel MongoDB中的跨数据库关系是存储在关系数据库中的模型与存储在MongoDB database中的模型之间的关系。
添加跨数据库关系时,Eloquent 允许您使用动态属性访问相关模型。
Laravel 集成支持以下跨数据库关系方法:
hasOne()hasMany()belongsTo()
要定义跨数据库关系,您必须在存储在关系数据库中的类中导入MongoDB\Laravel\Eloquent\HybridRelations包。
以下部分举例说明如何定义跨数据库关系。
跨数据库关系示例
以下示例类展示了如何定义存储在关系数据库中的 SpaceShip 模型与存储在MongoDB database中的 Passenger 模型之间的 HasMany 关系:
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\HasMany;use MongoDB\Laravel\Eloquent\HybridRelations;
class SpaceShip extends Model{ use HybridRelations;
protected $connection = 'sqlite';
public function passengers(): HasMany { return $this->hasMany(Passenger::class); }}以下示例类展示了如何使用belongsTo()方法定义Passenger模型与Spaceship模型之间的反向BelongsTo关系:
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;use MongoDB\Laravel\Eloquent\Model;
class Passenger extends Model{ protected $connection = 'mongodb';
public function spaceship(): BelongsTo { return $this->belongsTo(SpaceShip::class); }}以下示例代码展示了如何在MySQL数据库中创建 SpaceShip 模型和在MongoDB database中创建相关的 Passenger 模型,以及通过运行代码创建的数据。 单击VIEW OUTPUT按钮可查看通过运行以下代码创建的数据:
$spaceship = new SpaceShip();$spaceship->id = 1234;$spaceship->name = 'Nostromo';$spaceship->save();
$passengerEllen = new Passenger();$passengerEllen->name = 'Ellen Ripley';
$passengerDwayne = new Passenger();$passengerDwayne->name = 'Dwayne Hicks';
$spaceship->passengers()->save($passengerEllen);$spaceship->passengers()->save($passengerDwayne);聚合相关模型
本节介绍如何在不加载相关模型的情况下计算这些模型的聚合值。在查询父模型时,使用 with...() 聚合方法来计算聚合,或者在已检索的模型实例上使用相应的 load...() 方法来计算它。下表介绍了这些方法:
查询方法 | 实例方法 | 说明 | 如果没有相关模型时的返回值 |
|---|---|---|---|
|
| 计算相关模型 |
|
|
| 指示是否存在任何相关模型 |
|
|
| 将相关模型中指定字段的值相加 |
|
|
| 计算相关模型中指定字段的平均值 |
|
|
| 返回相关模型中指定字段的最低值 |
|
|
| 返回相关模型中指定字段的最高值 |
|
每个方法都会向父模型添加一个属性。属性名称结合了关系名称、聚合函数,对于 withSum()、withAvg()、withMin() 和 withMax() 方法,还结合了字段名称。示例,withCount('moons') 添加了一个 moons_count 属性。要设置您自己的属性名称,请在关系名称中添加别名,如本节中的 示例 所示。
提示
要学习;了解有关这些方法的更多信息,请参阅 Laravel 文档中的 聚合相关模型。
聚合行为和限制
如果您将相关模型存储在各自的集合中,Laravel Integration 无法在检索父模型的同一查询中计算聚合。相反,它会为您请求的每个聚合运行一个额外的查询,并将每个结果分配给它所属的父模型。考虑以下必须扩展的查询的替代方案:
将聚合值存储在父文档中。例如,在
planets集合的每个文档中存储一个moons_count字段,并在添加或删除卫星时对其进行更新。使用
$lookup聚合阶段,在单个查询中将集合连接起来。要了解更多信息,请参阅 聚合操作指南。
Laravel 集成可以计算嵌入式关系(例如由 embedsOne() 和 embedsMany() 方法定义的关系)的聚合,而无需从父文档运行额外的查询。
Laravel 集成不支持以下聚合行为,如果使用它们,则会引发异常:
按聚合属性对查询结果进行排序
聚合跨数据库关系
聚合
MorphTo关系将约束(包括限制)应用于嵌入式关系
聚合示例
此示例使用一对多关系部分中定义的Planet和Moon模型来检索集合中第一个行星的以下信息:
行星是否有卫星
绕行卫星数量
其卫星中最长的轨道周期
$planet = Planet::withExists('moons') ->withCount('moons') ->withMax('moons', 'orbital_period') ->first();
echo 'Has moons: ' . ($planet->moons_exists ? 'yes' : 'no') . PHP_EOL;echo 'Number of moons: ' . $planet->moons_count . PHP_EOL;echo 'Longest orbital period: ' . $planet->moons_max_orbital_period . PHP_EOL;以下示例使用别名来存储计数到 total_moons 属性中,而不是默认 moons_count 属性:
$planet = Planet::withCount('moons as total_moons')->first();以下示例使用 loadCount() 方法对代码已检索到的模型实例进行计数:
$planet = Planet::first();$planet->loadCount('moons');
echo 'Number of moons: ' . $planet->moons_count . PHP_EOL;