我的架构如下:
客户(hasMany Accounts)
帐户(hasMany Holdings,belongsTo Clients)
控股(所属帐户)
所以,客户有很多账户有很多控股。需要注意的是,帐户的本地密钥为account_id
,而不仅仅是预期的id
。这是因为要求帐户具有字符串标识符。在馆藏表中,外键也是account_id
。
我已经定义了我的关系:
// Client.php
public function accounts()
{
return $this->hasMany('Account');
}
// Account.php
public function client()
{
return $this->belongsTo('Client');
}
public function holdings()
{
return $this->hasMany('Holding');
}
// Holding.php
public function account()
{
return $this->belongsTo('Account', 'account_id', 'account_id');
}
如果我想查询给定客户ID的所有馆藏,我该怎么做?如果我做了像
这样的事情Client::find($id)->accounts->holdings;
我收到此错误:
未定义属性:Illuminate \ Database \ Eloquent \ Relations \ HasMany :: $ holdings
我也尝试使用hasManyThrough关系(已经将关系添加到我的模型中),但似乎只有一种方法来定义外键,而不是帐户的本地键。有什么建议吗?
答案 0 :(得分:4)
假设您在帐户表上有client_id
,
这样做:
// Account model
public function holdings()
{
return $this->hasMany('Holding', 'account_id', 'account_id');
}
// then
$client = Client::with('accounts.holdings')->find($id);
$client->accounts // collection
->first() // or process the collecction in the loop
->holdings; // holdlings collection
HasManyThrough
仅在Account
模型具有(或将具有此目的)$ primaryKey设置为account_id
而非默认id
由于account_id
不是Account
模型的主键,因此您无法使用hasManyThrough
。所以我建议你这样做:
$accountIds = $client->accounts()->lists('account_id');
// if it was many-to-many you would need select clause as well:
// $accountIds = $client->accounts()->select('accounts.account_id')->lists('account_id');
$holdings = Holding::whereIn('account_id', $accountIds)->get();
通过这种方式,您可以按照自己的意愿获得收藏,与急切加载相比,donwside需要再查询一次。
答案 1 :(得分:0)
您需要在帐户模型中更改您的关系
// Account.php
public function client()
{
return $this->belongsTo('Client','account_id');
}
但是,更适合在client_id
表
Accounts
答案 2 :(得分:0)
我认为您可以使用load方法为每个帐户获取相应的结果查询。类似的东西:
Client::find($id)->load('accounts.holdings');
这意味着client_id
中存在accounts
,而holdings
也存在account_id
。
PS:我不确定这在这种情况下会如何起作用。但我希望这可以让你找到实现它的方法。
答案 3 :(得分:0)
你必须稍微改写Eloquent。我刚刚遇到了与BelongsToMany关系非常相似的东西。我试图执行多对多查询,其中相关的本地密钥不是主键。所以我将Eloquent的BelongsToMany扩展了一点。首先为BelongsToMany关系类构建一个覆盖类:
namespace App\Overrides\Relations;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany as BaseBelongsToMany;
class BelongsToMany extends BaseBelongsToMany
{
protected $localKey;
/**
* @var array
*/
protected $customConstraints = [];
/**
* BelongsToMany constructor.
* @param Builder $query
* @param Model $parent
* @param string $table
* @param string $foreignKey
* @param string $otherKey
* @param string $relationName
* @param string $localKey
*/
public function __construct(
Builder $query,
Model $parent,
$table,
$foreignKey,
$otherKey,
$relationName = null,
$localKey = null
) {
//The local-key binding, assumed by Eloquent to be the primary key of the model, will have already been set
if ($localKey) { //If it's intended to be overridden, that value in the Query/Builder object needs updating
$this->localKey = $localKey;
$this->setLocalKey($query, $parent, $table, $foreignKey, $localKey);
}
parent::__construct($query, $parent, $table, $foreignKey, $otherKey, $relationName);
}
/**
* If a custom local-key field is defined, don't automatically assume the pivot table's foreign relationship is
* joined to the model's primary key. This method is necessary for lazy-loading.
*
* @param Builder $query
* @param Model $parent
* @param string $table
* @param string $foreignKey
* @param string $localKey
*/
public function setLocalKey(Builder $query, Model $parent, $table, $foreignKey, $localKey)
{
$qualifiedForeignKey = "$table.$foreignKey";
$bindingIndex = null;
//Search for the 'where' value currently linking the pivot table's foreign key to the model's primary key value
$query->getQuery()->wheres = collect($query->getQuery()->wheres)->map(function ($where, $index) use (
$qualifiedForeignKey,
$parent,
&$bindingIndex
) {
//Update the key value, and note the index so the corresponding binding can also be updated
if (array_get($where, 'column', '') == $qualifiedForeignKey) {
$where['value'] = $this->getKey($parent);
$bindingIndex = $index;
}
return $where;
})->toArray();
//If a binding index was discovered, updated it to reflect the value of the custom-defined local key
if (!is_null($bindingIndex)) {
$bindgings = $query->getQuery()->getBindings();
$bindgings[$bindingIndex] = $this->getKey($parent);
$query->getQuery()->setBindings($bindgings);
}
}
/**
* Get all of the primary keys for an array of models.
* Overridden so that the call to $value->getKey() is replaced with $this->getKey()
*
* @param array $models
* @param string $key
* @return array
*/
protected function getKeys(array $models, $key = null)
{
if ($key) {
return parent::getKeys($models, $key);
}
return array_unique(array_values(array_map(function ($value) use ($key) {
return $this->getKey($value);
}, $models)));
}
/**
* If a custom local-key field is defined, don't automatically assume the pivot table's foreign relationship is
* joined to the model's primary key. This method is necessary for eager-loading.
*
* @param Model $model
* @return mixed
*/
protected function getKey(Model $model)
{
return $this->localKey ? $model->getAttribute($this->localKey) : $model->getKey();
}
/**
* Set the where clause for the relation query.
* Overridden so that the call to $this->parent->getKey() is replaced with $this->getKey()
* This method is not necessary if this class is accessed through the typical flow of a Model::belongsToMany() call.
* It is necessary if it's instantiated directly.
*
* @return $this
*/
protected function setWhere()
{
$foreign = $this->getForeignKey();
$this->query->where($foreign, '=', $this->getKey($this->parent));
return $this;
}
}
接下来,您需要让Model
课程实际使用它:
namespace App\Overrides\Traits;
use App\Overrides\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\Relation;
/**
* Intended for use inside classes that extend Illuminate\Database\Eloquent\Model
*
* Class RelationConditions
* @package App\Overrides\Traits
*/
trait CustomConstraints
{
/**
* Intercept the Eloquent Model method and return a custom relation object instead
*
* {@inheritdoc}
*/
public function belongsToMany($related, $table = null, $foreignKey = null, $otherKey = null, $relation = null, $localKey = null)
{
//Avoid having to reproduce parent logic here by asking the returned object for its original parameter values
$base = parent::belongsToMany($related, $table, $foreignKey, $otherKey, $relation);
//The base action will have already applied the appropriate constraints, so don't re-add them here
return Relation::noConstraints(function () use ($base, $localKey) {
//These methods do the same thing, but got renamed
$foreignKeyName = version_compare(app()->version(), '5.3', '>=')
? $base->getQualifiedForeignKeyName()
: $base->getForeignKey();
$relatedKeyName = version_compare(app()->version(), '5.3', '>=')
? $base->getQualifiedRelatedKeyName()
: $base->getOtherKey();
return new BelongsToMany(
$base->getQuery(),
$base->getParent(),
$base->getTable(),
last(explode('.', $foreignKeyName)),
last(explode('.', $relatedKeyName)),
$base->getRelationName(),
$localKey
);
});
}
}
在模型类中使用此特征,现在您可以添加第6个参数来指定要使用的本地密钥,而不是自动承担主要密钥。