101

I created a model Game using a condition / constraint for a relation as follows:

class Game extends Eloquent {
    // many more stuff here

    // relation without any constraints ...works fine 
    public function videos() {
        return $this->hasMany('Video');
    }

    // results in a "problem", se examples below
    public function available_videos() {
        return $this->hasMany('Video')->where('available','=', 1);
    }
}

When using it somehow like this:

$game = Game::with('available_videos')->find(1);
$game->available_videos->count();

everything works fine, as roles is the resulting collection.

MY PROBLEM:

when I try to access it without eager loading

$game = Game::find(1);
$game->available_videos->count();

an Exception is thrown as it says "Call to a member function count() on a non-object".

Using

$game = Game::find(1);
$game->load('available_videos');
$game->available_videos->count();

works fine, but it seems quite complicated to me, as I do not need to load related models, if I do not use conditions within my relation.

Have I missed something? How can I ensure, that available_videos are accessible without using eager loading?

For anyone interested, I have also posted this issue on http://forums.laravel.io/viewtopic.php?id=10470

3
  • Best way to implement role based user management in laravel is to use the sentry package. So give it a try. Commented Aug 30, 2013 at 1:06
  • As I said in my description above, the Model names are just an example, my problem has got nothing to do with a user management. I am going to edit my question and post my real wordl example. Commented Sep 3, 2013 at 19:13
  • Awesome question thank you! And great answers to the guys below. Saved me time on my project. Commented Sep 27, 2022 at 11:14

9 Answers 9

123

I think that this is the correct way:

class Game extends Eloquent {
    // many more stuff here

    // relation without any constraints ...works fine 
    public function videos() {
        return $this->hasMany('Video');
    }

    // results in a "problem", se examples below
    public function available_videos() {
        return $this->videos()->where('available','=', 1);
    }
}

And then you'll have to

$game = Game::find(1);
var_dump( $game->available_videos()->get() );
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your reply, your example perfectly works, but so does mine when calling $game->available_videos()->get(). The point is, that I would like to use $game->available_videos as I can also use $game->videos without running into trouble. As far as I know Eloquent should load the related models if not eager loaded before, or am I wrong?
update function name to availableVideos. If you only need the videos to return available videos you could do: return $this->hasMany('Video')->where('available','=', 1);
52

I think this is what you're looking for (Laravel 4, see http://laravel.com/docs/eloquent#querying-relations)

$games = Game::whereHas('video', function($q)
{
    $q->where('available','=', 1);

})->get();

1 Comment

Note: the performance is terrible because it uses and exists (select * from
49

use getQuery() to add condition

public function videos() {
    $instance = $this->hasMany('Video');
    $instance->getQuery()->where('available','=', 1);
    return $instance
}

simply

public function videos() {
    return $this->hasMany('Video')->where('available','=', 1);
}

3 Comments

Great idea but it's the same thing because Relation calls that method on the query builder for you
yes, no need to call getQuery right now. lower version needed
For v5 you could do: return $this->hasMany('Video')->where('available','=', 1); to make it cleaner
20

If you want to apply condition on the relational table you may use other solutions as well.. This solution is working from my end.

public static function getAllAvailableVideos() {
        $result = self::with(['videos' => function($q) {
                        $q->select('id', 'name');
                        $q->where('available', '=', 1);
                    }])                    
                ->get();
        return $result;
    }

1 Comment

Thanks. +1. This answer is good because with() is powerful. Better than whereHas in some occasions, since whereHas() causes an EXISTS query, so people may not want whereHas(). But, maybe this answer can avoid to have a static method that calls get() that is quite hardcoded and non-Laravel-way, and we could instead adopt a "Laravel scope", or at least still returning a query builder and not an hardcoded Collection, so you can chain other queries. So no get(), so we can later also call ->count() that will exec. an SQL COUNT() (It's simpler to do, than to comment)
17

Just in case anyone else encounters the same problems.

Note, that relations are required to be camelcase. So in my case available_videos() should have been availableVideos().

You can easily find out investigating the Laravel source:

// Illuminate\Database\Eloquent\Model.php
...
/**
 * Get an attribute from the model.
 *
 * @param  string  $key
 * @return mixed
 */
public function getAttribute($key)
{
    $inAttributes = array_key_exists($key, $this->attributes);

    // If the key references an attribute, we can just go ahead and return the
    // plain attribute value from the model. This allows every attribute to
    // be dynamically accessed through the _get method without accessors.
    if ($inAttributes || $this->hasGetMutator($key))
    {
        return $this->getAttributeValue($key);
    }

    // If the key already exists in the relationships array, it just means the
    // relationship has already been loaded, so we'll just return it out of
    // here because there is no need to query within the relations twice.
    if (array_key_exists($key, $this->relations))
    {
        return $this->relations[$key];
    }

    // If the "attribute" exists as a method on the model, we will just assume
    // it is a relationship and will load and return results from the query
    // and hydrate the relationship's value on the "relationships" array.
    $camelKey = camel_case($key);

    if (method_exists($this, $camelKey))
    {
        return $this->getRelationshipFromMethod($key, $camelKey);
    }
}

This also explains why my code worked, whenever I loaded the data using the load() method before.

Anyway, my example works perfectly okay now, and $model->availableVideos always returns a Collection.

3 Comments

No that was not the reason definitely. You should always use function name with underscore _ because, in MVC there should always become a URL structure like /controller/function_name. If you did not set proper routing for the function you will get the camel cased function name in the URL, which is not SEO friendly.
@Vineet sorry, no. MVC has nothing to do with the URL structure. The routes.php file defines the mapping from url to controller.
Not to mention that this is in the model, not the controller. It has absolutely zero correspondence to the URL.
9
 public function outletAmenities()
{
    return $this->hasMany(OutletAmenities::class,'outlet_id','id')
        ->join('amenity_master','amenity_icon_url','=','image_url')
        ->where('amenity_master.status',1)
        ->where('outlet_amenities.status',1);
}

2 Comments

Where did the amenities all of a sudden come from? This is not even close to an answer.
Could you explain your answer a little bit, please?
8

I have fixed the similar issue by passing associative array as the first argument inside Builder::with method.

Imagine you want to include child relations by some dynamic parameters but don't want to filter parent results.

Model.php

public function child ()
{
    return $this->hasMany(ChildModel::class);
}

Then, in other place, when your logic is placed you can do something like filtering relation by HasMany class. For example (very similar to my case):

$search = 'Some search string';
$result = Model::query()->with(
    [
        'child' => function (HasMany $query) use ($search) {
            $query->where('name', 'like', "%{$search}%");
        }
    ]
);

Then you will filter all the child results but parent models will not filter. Thank you for attention.

Comments

2

Model (App\Post.php):

/**
 * Get all comments for this post.
 */
public function comments($published = false)
{
    $comments = $this->hasMany('App\Comment');
    if($published) $comments->where('published', 1);

    return $comments;
}

Controller (App\Http\Controllers\PostController.php):

/**
 * Display the specified resource.
 *
 * @param int $id
 * @return \Illuminate\Http\Response
 */
public function post($id)
{
    $post = Post::with('comments')
        ->find($id);

    return view('posts')->with('post', $post);
}

Blade template (posts.blade.php):

{{-- Get all comments--}}
@foreach ($post->comments as $comment)
    code...
@endforeach

{{-- Get only published comments--}}
@foreach ($post->comments(true)->get() as $comment)
    code...
@endforeach

Comments

-1

To access a hasMany relationship with a where condition, you can use the whereHas method.

Here's an example:

$orders = Order::whereHas('items', function ($query) {
   $query->whereNotNull('merchant_referral_percentage');
})->get();

In this example, Order is the model with the hasMany relationship to items. The whereHas method is used to filter the orders based on a condition on the items relationship. The condition in this case is that the merchant_referral_percentage column should not be null.

You can modify the condition inside the where callback function to suit your specific needs.

3 Comments

Where did you get merchant_referral_percentage? That doesn't appear in the question. There is already an answer from 2014 (9.5 years ago!) using whereHas(), and it actually uses the code from the question.
My bad about out of context example but i believe logic is fine. Thanks for letting me know about 9.5 years. I really appreciate your effort for the calculation. Should i delete my answer?
I think their is mistake from your side about reading the question and their answer. If you read question and their answer carefully others guy's are also using the example of Comments and Post. i hope next time you didn't make such sort of silly mistakes. Happy dev's

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.