Adding conditions to relationships in Laravel

Currently playing with the Laravel php framework.

It’s been said by many already, it’s a really cool framework. Well designed and documented. Coming from a CodeIgniter background, it’s easy to jump in and refreshing to get all the new cool features.

The Eloquent ORM has a nice minimalist approach to writing database abstraction. If you’re coming from Propel or Doctrine it’s crazy how little you actually need to get Eloquent going.

Eloquent supports relationships between models out of the box, like so:

class Post extends Eloquent {

     public function comments()
     {
          return $this->has_many('Comment');
     }

}

Very simple.

What if I need to add conditions to that relationship?

For instance, I’ve got a system with lot of meta info. Multiple tables have meta data.

Rather than having a meta table for each entity I would rather have a single meta table with a (target_type / target_id) set of columns. The target_type column is the table it refers to and target_id the id in that table. (so for users, target_type='users', target_id = <some_user_id>)

To load meta info for any entity as a One-To-Many relationship, Laravel would automatically link the target_id for me but I would also need to enforce the target_type field in the relationship (otherwise I may load meta data from other entities).

It’s actually quite easy to do in Laravel.

The has_many method returns the relationship object itself (not the actual data, you still need to call get() on it). What that means is that you can call additional methods on it before returning it.

Looking at the class hierarchy, relationships are actually subclasses of Query so you can call the usual Eloquent filtering methods on it.

Here’s how it would work for a User->Meta relationship:

class User extends Eloquent {

     public function meta()
     {
          return $this->has_many('Meta','target_id')->where('target_type','=',$this->table());
     }

}

Too easy.

9 comments on “Adding conditions to relationships in Laravel

  1. Tobia

    Brilliant! Thanks a lot. Now Eloquent is much clearer.

    Reply
    1. bendog

      Thanks for the feedback.

      Good to hear this is still relevant after nearly 4 years.

      Reply
  2. Pip Jones

    Yes still relevant and helpful. Can I ask how to achieve a variant of this, where the metadata for the relationship is stored in each row in the primary table? So I’d want to do something like:

    public function meta() {
              return $this->hasmany('Meta','targetid')->where('targettype','=',$this->usertype);
    }
    

    and each User record specifies its own “type”. However $this isn’t instantiated when the relationship is run/defined, so I don’t understand how to pass in the per-row values in to the relationship (like you can a join). Feel like I’m missing some crucial info, which I can’t find in the eloquent docs. TIA

    Reply
    1. bendog

      Hey Pip,

      Good question. I’ve never tried that.

      $this should be instantiated, you can do $this->hasmany(...)

      Are you sure about that?

      Reply
  3. Pip Jones

    Ah sorry, used the term wrong! It’s instantiated as a Model instance, but it’s not populated with the model properties.

    I know I’m thinking about it wrong… I keep imagining the relationship is like a per-row callback, but I know it’s not, it’s a definition of the structure. I can get it working using joins in the caller, but just trying to “do things right” by eloquent and define it in the model structure itself.

    Thanks for the quick response (I’ve had a stack overflow open for months :)

    Reply
    1. bendog

      Well if it’s not populated with the model properties, then it doesn’t make sense to do it like this.

      Maybe don’t define it as a relationship per-se, more like a utility method of the model, that you can use to retrieve the related model, something like this.

      public function meta() {
                return Meta::where('targettype','=',$this->usertype);
      }
      

      Maybe post a link to your stack overflow question here to, someone might come across it from here.

      Reply
  4. Pip Jones

    Thanks again, that’s given me something to think about. I did try something like this using ‘scopes’ which can have extra parameters.

    This is a more complex version of my question above, I guess it’s at the core of my knowledge gap. http://stackoverflow.com/questions/33871337/how-to-scope-an-eloquent-relation-or-perform-traditional-join

    Reply
    1. bendog

      Thanks for the SO link, i’ve +1’ed it.

      The withDevice in user() doesn’t make sense to me, the device() and user() relations to me seem independent (from the point of view of the SessionLogModel model).

      The main issue seems that belongsTo cannot express a relation on multiple fields.. yeah?

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *