You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel关联查询:如何屏蔽无关的Product模型?

问题:Laravel关联查询时过滤无关联数据

我在Laravel项目中通过getProductListAll方法查询Product列表,关联了property和prices_roket(对应ProductPrice模型的hasMany关联)。现在发现没有关联prices_roket数据的Product也被包含在返回结果里,需要过滤掉这类产品。之前尝试用map方法移除prices_roket中的product属性,但没达到过滤无关联产品的目的。


相关代码

Controller代码

public function getProductListAll()
    {
        $products = Product::with(['property'=>function($q){
            $q->select(['features','for_who','product_id']);
        }])
            ->without('products')
            ->whereStatus(1)
            ->where('show', 1)
            ->where('code', '!=', 1)
            ->where('is_roketable', true)
            ->with(['prices_roket' => function ($q) {
                return $q->whereNull('user_id');
            }]);
        $products = $products->whereIn('product_type', ['education', 'consultancy', 'startup']);

        $data['products'] = $products->select([
            'id', 'name', 'image', 'commission', 'price',
            'buy_now', 'type', 'short_desc', 'pricing_desc',
            'offer_status', 'period_status', 'period', 'period_frequency',
            'endpoint', 'product_type', 'unique_id', 'discount_show_status',
            'annual_payment', 'pre_register', 'privacy_status', 'privacy',
            'sg_project', 'last_pre_register_date', 'product_desc',
            'product_consulting', 'product_status',
        ])->get();

        return response()->json($data);

    }

Product模型关联方法

public function prices_roket()
    {
        return $this->hasMany(ProductPrice::class)->select(['id', 'product_id', 'price', 'user_id', 'offer_id'])
            ->orderBy('price')->whereNull('offer_id');
    }

尝试的处理代码

$data['products'] = $data['products']->map(function($product) {
    if(!$product->prices_roket) {
        return $product;
    }

    $product->prices_roket = $product->prices_roket
        ->map(function($pr) {
            $pr->forget('product');

            return $pr;
        });

    return $product;
});

Product模型完整代码

protected $table = 'products';

    protected $fillable = [
        'id',
        'code',
        'name',
        'category_id',
        'description',
        'image',
        'commission',
        'price',
        'buy_now',
        'route',
        'btn_text',
        'type',
        'status',
        'short_desc',
        'pricing_desc',
        'author',
        'author_email',
        'author_web',
        'author_logo',
        'addon_id',
        'show',
        'discount_code_status',
        'discount',
        'discount_status',
        'offer_status',
        'period_status',
        'period',
        'period_frequency',
        'sort',
        'remaning_status',
        'action',
        'subscription',
        'key',
        'endpoint',
        'product_type',
        'unique_id',
        'discount_show_status',
        'annual_payment',
        'auto_assign_user_id',
        'relational_product_status',
        'supplier_id',
        'form_id',
        'contents',
        'auto_invoice',
        'pre_register',
        'privacy_status',
        'privacy',
        'sg_crm_code',
        'sg_supplier',
        'sg_project',
        'activated_at',
        'passived_at',
        'last_pre_register_date',
        'supplier_commision',
        'popup_show',
        'earn_money',
        'product_desc',
        'start_popup_time',
        'finish_popup_time',
        'product_consulting',
        'product_status',
        'banner_one',
        'banner_two',
        'banner_three',
        'banner_four',
        'is_roketable'
    ];

    protected $hidden = [
        'auto_assign_user_id'
    ];

    protected $dates = [];

    protected $casts = [
        'contents' => 'array'
    ];

    protected static function boot()
    {
        parent::boot();

        static::creating(function ($model) {
            if ($model->category_id) {
                $category = ProductCategory::find($model->category_id);
                if ($category) {
                    $model->type = $category->name;
                }
            }
            $model->code = initials($model->name) . '01';
        });

        static::updating(function ($model) {
            if ($model->category_id) {
                $category = ProductCategory::find($model->category_id);
                if ($category) {
                    $model->type = $category->name;
                }
            }
        });

        // static::addGlobalScope(new MyProductScope);
    }

    public function scopeMyProduct($query)
    {
        $user = auth()->user();
        if ($user && !$user->isAdmin() && $user->role != 'business') {
            $ids = DB::table('user_products')->where('user_id', $user->id)->pluck('product_id');
            $query->whereIn('products.id', $ids);
        }
    }

    protected $appends = [
        'image_url',
        'author_logo_url',
        'data',
        'education',
        'unique',
        'description_string'
    ];

ProductPrice模型

protected $table = 'product_prices';

protected $fillable = [
    'price',
    'description',
    'product_id',
    'product_price_id',
    'user_id',
    'offer_id',
    'sort',
    'tax',
    'person_count_status',
    'person_price',
    'person_text',
    'free',
    'visibility',
    'currency',
    'meeting_required',
    'subscription_price',
    'annual_price',
    'is_analysis',
    'invoice_required',
    'product_question_group_id',
    'send_mail',
    'price_type',
    'discounted_price',
    'project_code',
    'person_count_accept',
    'foreign_web_link',
    'foreign_web_site_status',
    'portal_product_show_name',
    'edanisman_money',
    'reportable'
];

protected $casts = [
    'free'                => 'boolean',
    'person_count_status' => 'boolean',
    'meeting_required'    => 'boolean',
    'is_analysis'         => 'boolean',
    'invoice_required'    => 'boolean',
    'invoice_required'    => 'boolean',
    'reportable'           => 'boolean',
    // 'annual_price'        => 'decimal',
];

protected $dates = [];

protected $hidden = [];

protected $appends = [
    'real_price',
    'real_person_price',
    'main_price',
    'text_desc',
    'package_name',
    'remaning_price',
    'remaning_months',
    'discount_status',
    'discount_type',
    'annual_discount_rate',
    'currency_symbol',
    'currency_rate',
    'user_has_price',
];

解决方案

要过滤掉无prices_roket关联的Product,不能在查询后用map处理(map仅修改关联数据,不会过滤模型本身),需要在查询阶段通过whereHas约束关联存在且符合条件:

修改后的Controller代码:

public function getProductListAll()
{
    $products = Product::with(['property' => function($q){
            $q->select(['features','for_who','product_id']);
        }])
        ->without('products')
        ->whereStatus(1)
        ->where('show', 1)
        ->where('code', '!=', 1)
        ->where('is_roketable', true)
        // 新增:过滤存在符合条件的prices_roket关联的产品
        ->whereHas('prices_roket', function ($q) {
            $q->whereNull('user_id')->whereNull('offer_id');
        })
        ->with(['prices_roket' => function ($q) {
            return $q->whereNull('user_id');
        }]);

    $products = $products->whereIn('product_type', ['education', 'consultancy', 'startup']);

    $data['products'] = $products->select([
        'id', 'name', 'image', 'commission', 'price',
        'buy_now', 'type', 'short_desc', 'pricing_desc',
        'offer_status', 'period_status', 'period', 'period_frequency',
        'endpoint', 'product_type', 'unique_id', 'discount_show_status',
        'annual_payment', 'pre_register', 'privacy_status', 'privacy',
        'sg_project', 'last_pre_register_date', 'product_desc',
        'product_consulting', 'product_status',
    ])->get();

    // 如需移除prices_roket中的product属性,可保留这段处理
    $data['products'] = $data['products']->map(function($product) {
        $product->prices_roket = $product->prices_roket->map(function($pr) {
            $pr->forget('product');
            return $pr;
        });
        return $product;
    });

    return response()->json($data);
}

说明

  • whereHas用于筛选存在指定关联且关联数据满足条件的模型,这里的条件和prices_roket关联定义、with中的约束保持一致,确保返回的Product都有符合要求的关联数据。
  • 之前的map代码仅用于移除关联数据中的product属性,无法过滤无关联的Product,因此必须在查询阶段添加约束。

内容的提问来源于stack exchange,提问作者mehmet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 08:50:39