ChatGPT解决这个技术问题 Extra ChatGPT

How to access model hasMany Relation with where condition?

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

Best way to implement role based user management in laravel is to use the sentry package. So give it a try.
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.

A
Antonio Carlos Ribeiro

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() );

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);
S
Sabrina Leggett

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();

x
xsilen T

//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);
}

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
R
Remluben

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.


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.
N
Nikunj K.

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;
    }

k
kush
 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);
}

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?
V
Vitalii

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.


S
Santo Boldizar

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

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now