首页 文章

Laravel与非默认本地密钥有很多关系

提问于
浏览
1

我的架构如下:

Clients (hasMany Accounts)

  • id

  • 名字

Accounts (hasMany Holdings,belongsTo Clients)

  • id(int)

  • account_id(字符串,唯一键)

  • 名字

Holdings (从属于账户)

  • id

  • account_id(字符串)

  • holding_date ......等

所以,客户有很多账户有很多控股 . 需要注意的是,帐户的本地密钥是 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关系(将关系添加到我的模型中),但似乎只有一种方法来定义外键,而不是帐户的本地键 . 有什么建议?

4 回答

  • 0

    假设你在账户表上有 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需要再查询一次 .

  • 0

    您需要在帐户模型中更改您的关系

    // Account.php
    public function client()
    {
       return $this->belongsTo('Client','account_id');
    }
    

    但是,更适合在 Accounts 表中将列名更改为 client_id

  • 0

    我认为您可以使用load方法为每个帐户获取相应的结果查询 . 就像是:

    Client::find($id)->load('accounts.holdings');
    

    这意味着 accounts 存在于 accounts 中,而 holdings 也存在 account_id .

    PS:我不确定这在这种情况下会如何起作用 . 但我希望这可以引导你找到实现它的方法 .

  • 4

    你必须稍微改写Eloquent . 我刚刚遇到了与BelongsToMany关系非常相似的东西 . 我试图执行多对多查询,其中相关的本地密钥不是主键 . 所以我将Eloquent的BelongsToMany扩展了一点 . 首先为BelongsToMany关系类构建一个override类:

    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个参数来指定要使用的本地键,而不是自动假设为主键 .

相关问题