Conditional Eloquent relationship can be written but can't be read (eager load)

643 views Asked by At

Using Laravel 5.3, I have a method in my Order Eloquent model that returns a belongsToMany or a hasMany relationship depending on the value of a type char attribute value.

Order is itself a child of a MasterOrder model as a one to many relationship.

                 ---------------
                 * MasterOrder *
                 -------+-------
                        |
                       O2M                             
                        |
       +----------------+------------------+
       |                |                  |
   ----+-------     ----+-------      -----+------
   *  Order   *     *  Order   *      *  Order   *
   * Type = O *     * Type = C *      * Type = S *
   -+----------     -+----------      -+----------
    |     Offer      |CreditPack       |  Shipment
    |                |                 | 
   M2M              M2M               O2M

The relation in the Order model is the following

public function items()
{
    $types = [
        'S' => Shipment::class,
        'O' => Discount::class,
        'C' => CreditPack::class
    ];

    if ($this->type == 'C') {
        return $this->belongsToMany($types['C'], 'credit_pack_order')
            ->withTimestamps();
    }

    if ($this->type == 'O') {
        return $this->belongsToMany($types['O'], 'discounts')
            ->withTimestamps();
    }

    return $this->hasMany($types[$this->type]);
}

When inserting the data I am able to attach() offers and credit packs, and create() shipments using my items() method and it works without any issue. Data is persisted in the database and everything is where I need it to be.

The problem is that I can't read through items() if I eager load my relationships starting from the MasterOrder model (even though I start from there when I insert the data).

I can read Order data eager loading it from MasterOrder, but it seems like it can't read Order attributes when it tries to load items().

It is able to read them, instead, if I start from Order.

Is there something I can do to avoid querying the database twice for every order?
Can anyone please explain me why do this happen?

1

There are 1 answers

3
phaberest On BEST ANSWER

I finally found a workaround while talking about the issue in a italian developers Facebook group.

Substantially I can tell the method what type comes out from the order as an argument of the function. This way it will know what to answer even if it didn't load the attributes.

The relation becomes:

public function items($type = null)
{
    if ($type != null) {
        $this->type = $type;
    }

    $types = [
        'S' => Shipment::class,
        'O' => Discount::class,
        'C' => CreditPack::class
    ];

    if ($this->type == 'C') {
        return $this->belongsToMany($types['C'], 'credit_pack_order')
            ->withTimestamps();
    }

    if ($this->type == 'O') {
        return $this->belongsToMany($types['O'], 'discounts')
            ->withTimestamps();
    }

    return $this->hasMany($types[$this->type]);
}

and I retrieve the data using

$masterorder = auth()->user()->orders()        // orders() is a User->MasterOrder
    ->where('reference', '=', $reference)      // hasMany relation
    ->first();

$details = $masterorder->orders()->get()
    ->map(function ($order) {
        $order->items = $order->items($order->type)->get()->toArray();
        return $order;
    });