198

I have an Eloquent model which has a related model:

public function option() {
    return $this->hasOne('RepairOption', 'repair_item_id');
}

public function setOptionArrayAttribute($values)
{
    $this->option->update($values);
}

When I create the model, it does not necessarily have a related model. When I update it, I might add an option, or not.

So I need to check if the related model exists, to either update it, or create it, respectively:

$model = RepairItem::find($id);
if (Input::has('option')) {
    if (<related_model_exists>) {
        $option = new RepairOption(Input::get('option'));
        $option->repairItem()->associate($model);
        $option->save();
        $model->fill(Input::except('option');
    } else {
       $model->update(Input::all());
    }
};

Where <related_model_exists> is the code I am looking for.

Karl Hill
  • 12,937
  • 5
  • 58
  • 95
Tom Macdonald
  • 6,433
  • 7
  • 39
  • 59
  • 4
    Awesome question thank you! And great answers to the guys below. Saved me time on my project. – Rafael Feb 02 '15 at 00:31

11 Answers11

286

In php 7.2+ you can't use count on the relation object, so there's no one-fits-all method for all relations. Use query method instead as @tremby provided below:

$model->relation()->exists()

generic solution working on all the relation types (pre php 7.2):

if (count($model->relation))
{
  // exists
}

This will work for every relation since dynamic properties return Model or Collection. Both implement ArrayAccess.

So it goes like this:

single relations: hasOne / belongsTo / morphTo / morphOne

// no related model
$model->relation; // null
count($model->relation); // 0 evaluates to false

// there is one
$model->relation; // Eloquent Model
count($model->relation); // 1 evaluates to true

to-many relations: hasMany / belongsToMany / morphMany / morphToMany / morphedByMany

// no related collection
$model->relation; // Collection with 0 items evaluates to true
count($model->relation); // 0 evaluates to false

// there are related models
$model->relation; // Collection with 1 or more items, evaluates to true as well
count($model->relation); // int > 0 that evaluates to true
Jarek Tkaczyk
  • 78,987
  • 25
  • 159
  • 157
  • Doesn't a `Collection` already have a `count()` function? So you could just say `$model->relation->count()`? – Kousha Jul 29 '14 at 16:44
  • 1
    Read whole thing. `count($relation)` is a general solution for all relations. It will work for `Model` and `Collection`, while `Model` has no `->count()` method. – Jarek Tkaczyk Jul 29 '14 at 17:16
  • 8
    @CurvianVynes Not, it doesn't. `Collection` has its own method `isEmpty`, but generic `empty` function returns false for an object (thus won't work for empty collection). – Jarek Tkaczyk Oct 29 '14 at 11:10
  • 2
    `count($model->relation)` didn't work on `morphTo` when relationship had no association set yet. Foreign id and type are null and the db query built by Laravel is bogus and rises exception. I used `$model->relation()->getOtherKey()` as a workaround. – Jocelyn Dec 28 '14 at 13:49
  • 1
    @Jocelyn Yes, it's Eloquent bug. Unfortunately there are at least a few of them for polymorphic relations, so obviously you can't rely on them in any way. – Jarek Tkaczyk Dec 28 '14 at 19:03
  • count() fails if both (or more) relations, model->relationa->relationb do not exist. So I check each with count() first. – Curvian Vynes Jun 07 '15 at 16:56
  • Important question, if you are lazy loading `$model->relation`, does `count($model->relation)` actually query and load all data for the related model? Wouldn't `$model->relation()->value('id')` be better for performance in that case? – Captain Hypertext Mar 09 '16 at 16:18
  • 1
    @CaptainHypertext to be precise - `value('id')` is going to fail for pivot relations, so I'd rather use `exists()`. As for the question - that depends. It will run the query and load the relation indeed, so if you don't care about the relation itself, but only the fact whether it exists, then it's better. – Jarek Tkaczyk Mar 09 '16 at 17:02
  • In blade templates this doesn't work. So I used a model method: public function isOptionLoaded() { count($this->relations['option']); } – ClearBoth Aug 17 '16 at 05:29
  • @JarekTkaczyk it was normal \@if($model->isOptionLoaded()) do this \@else do that \@endif – ClearBoth Aug 17 '16 at 11:38
  • `$this->relations['option']` is brittle - it will cause error if you don't load the relation. The `count($model->relation)` works for sure – Jarek Tkaczyk Aug 17 '16 at 12:14
  • 1
    Any suggestions for how to do this in PHP 7.2 where this errors out if null? – Octoxan Dec 29 '17 at 21:22
  • A hasOne relation might return null and `count(null)` throws an exception in PHP 7.1 so this is not the generic solution. – Adam Apr 26 '18 at 12:13
  • 4
    It will break on PHP 7.2, returning: `count(): Parameter must be an array or an object that implements Countable` – CodeGodie Jun 26 '18 at 22:58
  • For single-model-relations, a count() isn't even necessary. Just `if ($model->relation)` is enough. – mark Nov 12 '18 at 21:07
  • 1
    @CodeGodie +1 also, even if it works for < PHP 7.2, I strongly disagree with calling `count()` on an object that isn't logically "countable" – andrewtweber Mar 18 '19 at 21:05
  • @andrewtweber it is perfectly fine to disagree with different opinions, especially a few years old +1 – Jarek Tkaczyk Apr 13 '19 at 07:07
99

A Relation object passes unknown method calls through to an Eloquent query Builder, which is set up to only select the related objects. That Builder in turn passes unknown method calls through to its underlying query Builder.

This means you can use the exists() or count() methods directly from a relation object:

$model->relation()->exists(); // bool: true if there is at least one row
$model->relation()->count(); // int: number of related rows

Note the parentheses after relation: ->relation() is a function call (getting the relation object), as opposed to ->relation which a magic property getter set up for you by Laravel (getting the related object/objects).

Using the count method on the relation object (that is, using the parentheses) will be much faster than doing $model->relation->count() or count($model->relation) (unless the relation has already been eager-loaded) since it runs a count query rather than pulling all of the data for any related objects from the database, just to count them. Likewise, using exists doesn't need to pull model data either.

Both exists() and count() work on all relation types I've tried, so at least belongsTo, hasOne, hasMany, and belongsToMany.

tremby
  • 9,541
  • 4
  • 55
  • 74
  • exists is not available in lumen, not sure why. – briankip Nov 02 '17 at 14:54
  • @briankip -- it should. You sure you're getting the relation object (by calling the method) rather than the collection (by using the magic property)? – tremby Nov 03 '17 at 02:41
  • 3
    At least in Laravel 6.x, `exists` doesn't work for a `morphTo` relationship that doesn't exist. It gets a SQL error. – Charles Wood Aug 26 '20 at 18:21
  • Keep in mind that calling `exists()` and `count()` on the relationship requires that the related model already be saved in the database. If you need to check for existence before the related model has been saved (for example, if you used setRelation), then you should use `is_null` or `empty`. – alexw Oct 06 '20 at 15:05
  • When we are using Query Builder we can't access to relations we defined at Model :( – Orman Faghihi Mohaddes Mar 01 '21 at 10:19
  • 1
    @OrmanFaghihiMohaddes: the text in my answer about the query builder is just part of an explanation of how this works. You're accessing a query builder via the relation you defined on the model, so yes you absolutely still are using the relations you defined on the model. – tremby Mar 01 '21 at 18:14
22

I prefer to use exists method:

RepairItem::find($id)->option()->exists()

to check if related model exists or not. It's working fine on Laravel 5.2

Hafez Divandari
  • 8,381
  • 4
  • 46
  • 63
  • 2
    +1; count($model->relation) was returning true for me in Laravel 5.2 even though there was no item in the relation table. ->exists() does the trick. – Ben Wilson Jul 20 '17 at 19:37
17

After Php 7.1, The accepted answer won't work for all types of relationships.

Because depending of type the relationship, Eloquent will return a Collection, a Model or Null. And in Php 7.1 count(null) will throw an error.

So, to check if the relation exist you can use:

For relationships single: For example hasOne and belongsTo

if(!is_null($model->relation)) {
   ....
}

For relationships multiple: For Example: hasMany and belongsToMany

if ($model->relation->isNotEmpty()) {
   ....
}
Hemerson Varela
  • 24,034
  • 16
  • 68
  • 69
  • 2
    Worked perfectly for me! If I eager-loaded relations and executed a ->count() in a foreach loop of results, even if they were eager-loaded in the model, it would generate an SQL query for each items. Using !is_null($model->relation) is the fastest and SQL friendly way to do it. Thanks so much. – raphjutras Nov 09 '20 at 14:31
6

I use for single relationships: hasOne, belongsTo and morphs

if($model->relation){ 
 ....
}

Because if condition is null, this will be false.

For multiple relationships: hasMany, belongsToMany and morphs

if ($model->relation->isNotEmpty()) {
   ....
}
Maxim Paladi
  • 91
  • 1
  • 7
4

You can use the relationLoaded method on the model object. This saved my bacon so hopefully it helps someone else. I was given this suggestion when I asked the same question on Laracasts.

Anthony
  • 1,760
  • 1
  • 23
  • 43
4

As Hemerson Varela already said in Php 7.1 count(null) will throw an error and hasOne returns null if no row exists. Since you have a hasOnerelation I would use the empty method to check:

$model = RepairItem::find($id);
if (!empty($temp = $request->input('option'))) {
   $option = $model->option;

   if(empty($option)){
      $option = $model->option()->create();
   }

   $option->someAttribute = temp;
   $option->save();
};

But this is superfluous. There is no need to check if the relation exists, to determine if you should do an update or a create call. Simply use the updateOrCreate method. This is equivalent to the above:

$model = RepairItem::find($id);
if (!empty($temp = $request->input('option'))) {  
   $model->option()
         ->updateOrCreate(['repair_item_id' => $model->id],
                          ['option' => $temp]);
}
Adam
  • 25,960
  • 22
  • 158
  • 247
3

Not sure if this has changed in Laravel 5, but the accepted answer using count($data->$relation) didn't work for me, as the very act of accessing the relation property caused it to be loaded.

In the end, a straightforward isset($data->$relation) did the trick for me.

Dave Stewart
  • 2,324
  • 2
  • 22
  • 24
  • I believe it is `$data->relation` without `$` (can't edit, because of 6 characters limit) – Zanshin13 Mar 29 '16 at 08:47
  • 2
    Ah the `$relation` would be the name of your relation, such as `$data->posts` or such like. Sorry if that was confusing, I wanted to make it clear that `relation` wasn't a concrete model property :P – Dave Stewart Mar 29 '16 at 12:11
  • This was working for a while, but it stopped working after I updated Laravel from 5.2.29 to 5.2.45. Any idea why or how to fix it? It's now causing the relational data to be loaded for some reason. – Anthony Oct 26 '16 at 07:07
  • I added an answer that has a fix for this. – Anthony Oct 26 '16 at 15:52
  • This won't work in more recent versions of Laravel. Since at least Laravel 5.8, the `Model::__isset` method is overloaded such that it returns true even if there is no related entity. You'll need to use `!is_null` to avoid the magic `isset` logic. They have confirmed that this is a known bug in Laravel that will be fixed in Laravel 8: https://github.com/laravel/framework/issues/31793 – alexw Nov 24 '20 at 15:56
1

I had to completely refactor my code when I updated my PHP version to 7.2+ because of bad usage of the count($x) function. This is a real pain and its also extremely scary as there are hundreds usages, in different scenarios and there is no one rules fits all..

Rules I followed to refactor everything, examples:

$x = Auth::user()->posts->find(6); (check if user has a post id=6 using ->find())

[FAILS] if(count($x)) { return 'Found'; } 
[GOOD] if($x) { return 'Found'; }

$x = Auth::user()->profile->departments; (check if profile has some departments, there can have many departments)

[FAILS] if(count($x)) { return 'Found'; }
[GOOD] if($x->count()) { return 'Found'; }

$x = Auth::user()->profile->get(); (check if user has a profile after using a ->get())

[FAILS] if(count($x)) { return 'Found'; }
[GOOD] if($x->count()) { return 'Found'; }

Hopes this can help, even 5 years after the question has been asked, this stackoverflow post has helped me a lot!

raphjutras
  • 105
  • 9
0

If you use the model class and use Eloquent ORM, then create a new method and return bool data. like

public function hasPosts(): bool
{
    return $this->posts()->exists();
}
0

RelationLoaded method of Model class could be useful.

if ($this->relationLoaded('prices')) {
    return;
}