php Laravel属于ToMany关系,只有一个结果

a14dhokn  于 2023-04-28  发布在  PHP
关注(0)|答案(6)|浏览(161)

我有两个表之间的关系,其中一个连接表只有一个结果。
当我定义Laravel belongsToMany关系时,我希望它只返回一个元素,而不是返回一个只有一个元素的集合。
有没有办法在Laravel中建模?
先谢谢你了。
[编辑]
我将尝试使用经典的用户/角色示例来解释我想要的内容。除了de usersroles表,我们还有一个users_roles数据透视表,它将存储用户拥有的所有角色。在任何给定时间,用户只能有一个活动角色(由active属性true标识)。

class User {
    function role() {
        return $this->belongsToMany('App\Role')->wherePivot('active', 'true');
    }
}

有了这个关系定义,当我访问$user->role时,我会得到一个Roles集合(只有一个元素)。我想要的是直接拥有该角色示例。

332nm8kg

332nm8kg1#

在我的情况下,这是最直接的解决方案:

class User extends Model {

    public function services()
    {
        return $this->belongsToMany(Service::class, 'service_user')
                    ->using(ServiceUser::class)
                    ->withPivot('user_id', 'service_id', 'is_main_service');
    }

    public function mainService()
    {
 
        return $this->hasOneThrough(Service::class, ServiceUser::class, 'user_id', 'id', 'id', 'service_id')
                    ->where('is_main_service', 1);
    }
}

数据透视表类:

use Illuminate\Database\Eloquent\Relations\Pivot;

class ServiceUser extends Pivot
{
    
}
polhcujo

polhcujo2#

我不知道如果你只需要一个关系,为什么你有belongsToMany,但是下面的代码会帮助你:

public function products()
{
    return $this->belongsToMany('App\Product');
}

public function specific_product()
{
    return $this->products()
                ->where('column','value')->first();
}

public function getSpecificProductAttribute()
{
    return $this->products()
                ->where('column','value')->first();
}
hkmswyz6

hkmswyz63#

我遇到了这个问题,并找到了一个非常干净的方法来解决它。
首先,更改返回belongsToMany结果的访问器函数的名称,以反映这些返回多个结果的事实。在您的情况下,这意味着使用roles而不是role

function roles() {
  return $this->belongsToMany('App\Role')->wherePivot('active', 'true');
}

然后将以下内容添加到模型中:

protected $appends = ['role'];

public function getRoleAttribute() {
  return $this->roles()->first();
}

现在,当您调用$user->role时,您将获得第一项。

lxkprmvk

lxkprmvk4#

我也遇到了同样的问题,让我向你展示我是如何做到的。
在我的例子中,材料和customer_types之间存在belongsToMany关系,数据透视表包含特定客户类型的材料价格,因此数据透视表中的记录(价格)与customer_types一样多。

我所期望的:当请求特定customer_type的价格时,我希望获得该特定customer_type的作用域价格作为嵌套元素
**我得到的是:**一个只有1个元素的集合。

这是我在模型开始时所拥有的:

class Material extends Model
{
    public function customer_types(){
        return $this->belongsToMany('App\CustomerType', 'customertype_material', 'material_id', 'customertype_id')->withPivot('price');
    }
}

当然,当我为特定的customer_type请求customer_types时,结果并不是预期的结果:

$resources = Material::with(['customer_types' => function($query) use ($customer_type_id){
                        $query->where('customertype_id', $customer_type_id);
                       }])->get();

它返回了一个Material模型,其中包含一个customer_types嵌套集合,其中包含1个元素,这迫使我使用first()或循环1个元素。

**解决方案:**创建一个扩展透视表的模型,并为其添加一个关系。

创建了一个扩展轴的新模型:

use Illuminate\Database\Eloquent\Relations\Pivot;

class CustomertypeMaterial extends Pivot
{
    protected $table    = 'customertype_material';
    protected $fillable = ['price', 'customertype_id', 'material_id'];
}

现在,在我的材质模型中添加指向此新模型的关系:

public function scoped_price(){
    return $this->belongsTo('App\CustomertypeMaterial', 'id','material_id');
}

最后,加载这个新关系的查询:

$resources = Material::with(['scoped_price' => function($query) use ($customer_type_id){
                        $query->where('customertype_id', $customer_type_id);
                       }])->get();

结果是一个Material模型,其中嵌套了一个scoped_price元素,并通过customer_type_id进行过滤
我不确定这是不是正确的方法,但对我来说很有效。
希望有帮助!

rt4zxlrg

rt4zxlrg5#

我已经写了一个更通用的方法来解决这个问题。它的缺点是我不得不从laravel/frame供应商文件中复制代码。因此,在升级Laravel框架时,这可能会被打破。

use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Str;

/**
 * Based on laravel/framework@8.54.0
 */
trait SupportsSingleResultBelongsToMany
{
    /**
     * Get the model's relationships in array form.
     *
     * @return array
     */
    public function relationsToArray()
    {
        $attributes = [];

        foreach ($this->getArrayableRelations() as $key => $value) {
            // If the values implements the Arrayable interface we can just call this
            // toArray method on the instances which will convert both models and
            // collections to their proper array form and we'll set the values.
            if ($value instanceof Arrayable) {
                if (isset($this->forceSingleResult) &&
                    in_array($key, $this->forceSingleResult) &&
                    $value instanceof \ArrayAccess &&
                    $value instanceof \Countable
                ) {
                    $relation = count($value) > 0 ? $value[0] : null;
                } else {
                    $relation = $value->toArray();
                }
            }

            // If the value is null, we'll still go ahead and set it in this list of
            // attributes since null is used to represent empty relationships if
            // if it a has one or belongs to type relationships on the models.
            elseif (is_null($value)) {
                $relation = $value;
            }

            // If the relationships snake-casing is enabled, we will snake case this
            // key so that the relation attribute is snake cased in this returned
            // array to the developers, making this consistent with attributes.
            if (static::$snakeAttributes) {
                $key = Str::snake($key);
            }

            // If the relation value has been set, we will set it on this attributes
            // list for returning. If it was not arrayable or null, we'll not set
            // the value on the array because it is some type of invalid value.
            if (isset($relation) || is_null($value)) {
                $attributes[$key] = $relation;
            }

            unset($relation);
        }

        return $attributes;
    }
}

然后在你的模型中,只需要使用Trait并指定哪些关系是单一结果。

class MyModel extends Model
{
    use SupportsSingleResultBelongsToMany;

    protected $forceSingleResult = ["teams"];

    public function teams()
    {
        $this->belongsToMany(Team::class);
    }
}
xxhby3vn

xxhby3vn6#

Laravel Eloquent基于魔术原理工作。您可以重写神奇的方法__get。如果没有属性,则调用__get方法:
(in您的模型)

public function __get ($name)
    {
        $answer = parent::__get($name);
        if($name=='your_prop'){
            $answer=!empty($answer)?$answer[0]:null;
        }
        return $answer;
    }

如果your_prop to-many关系返回任何值,则取数组中的第一个。

相关问题