Zayin Krige
Zayin Krige

Reputation: 3308

getting model belongsTo attributes

class Batch extends Eloquent {
    public function coupons() {
        return $this->hasMany('Coupon');
    }
}

class Coupon extends Eloquent {
    public function batch() {
        return $this->belongsTo('Batch');
    }
    public function price() {
        $batch = $this->batch;
        return $batch->price;
    }
}

$coupon->price gives me this error:-

LogicException Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation

However, $coupon->batch->price works just fine.

What am I missing?

Upvotes: 3

Views: 24549

Answers (2)

alexrussell
alexrussell

Reputation: 14202

Your issue here is that you define a non-relationship method price() but you call it as if it was a relationship method (i.e. you call it as a property and not as a method).

The code you should be using to get the Coupon's price is:

$coupon->price();

The reason the relationship thing works (i.e. $coupon->batch over $coupon->batch()) is that Laravel has some special logic in - basically it catches you trying to access a property (->batch in this case) and checked to see if there's a corresponding method (->batch()). If there is one, it calls it and expects it to return a relationship, and then it calls ->first() of ->get() on that relationship depending on whether it's a single-result or a multiple-result relationship.

So what's happening in your code here is that you call $coupon->price and Laravel, behind the scenes, decides that being as there's a ->price() method it must be a relationship. It calls the method, checks that it returns one of the Laravel relationship types, and when it doesn't, throws that LogicException.

The general rules of thumb is this:

  • If you want an actual property (i.e. anything defined on the table) or the results of a relationship query, use property access
  • If you want anything else (i.e. behaviour you're defined using a method) you must call the method directly

Also, sometimes there is a good reason to call a relationship as the method rather than the property - calling the method returns something you can add query builder constraints on to, whereas calling the property gets you all the results. So say Coupons can be enabled or disabled (for example), the following holds:

  • $batch->coupons gets you all coupons that the batch has
  • $batch->coupons()->whereEnabled(1)->get() gets you all enabled coupons for a given batch
  • $batch->coupons()->orderBy('order')->get() gets you all the coupons that the batch has, ordered by a field called order
  • $coupon->batch gets you the given coupon's batch

Hopefully that explains the difference between Eloquent's use of methods versus properties for relationships, and why all augmented behaviour (like price on coupon in your example, but not price on batch, which is inherent behaviour) must be called as a method.

Upvotes: 8

Sturm
Sturm

Reputation: 4285

Take a moment to realize what objects you actually have here.

When you call $this->batch; you're no longer chaining the relationship queries - you've actually retrieved the information from the database already. In order to define that query you'd have to do it one of several ways including:

Coupon::with('batch.price')->get();

You could of course do it with relationships but it's late and I'm not sure where exactly Price belongs in the scheme of this since I don't see a method for it associated with batch. Presumably you could do:

public function price()
{
    return $this->batch->price;
}

if Price is a derivative of Batch.

Upvotes: 0

Related Questions