2
votes

I have four Models:

  1. User
  2. Client
  3. Store
  4. Opportunity

The relationships are defined as such:

  • User hasMany Client
  • Client hasMany Store
  • Store hasMany Opportunity
  • User hasManyThrough Store, Client (this works)

The problem is that I'm attempting to access the User->Opportunity relationship via built-in Laravel relationships, but it doesn't seem as if I can do it without a custom Query or an additional user_id column on the opportunities table to allow direct access (even though one can be inferred from the Store->Client relationship). I'm also not a fan of nested foreach loops if they can be avoided.

My question:

Is there a way to go one level deeper and directly access a User's Opportunities in this scenario? The actual Model code and all relevant relationships are as follows:

User

class User extends Eloquent{
    public function clients(){
        return $this->hasMany('Client');
    }
    public function stores(){
        return $this->hasManyThrough('Store', 'Client');
    }
    public function proposals(){
        return $this->hasMany('Proposal');
    }
    public function opportunities(){ //This does the job, but I feel like it could be better
        return Opportunity::join('stores', 'stores.id', '=', 'opportunities.store_id')->
                            join('clients', 'clients.id', '=', 'stores.client_id')->
                            join('users', 'users.id', '=', 'clients.user_id')->
                            select('opportunities.*')->
                            where('users.id', $this->id);
    }
    public function getOpportunitiesAttribute(){ //This just helps mimic the hasManyThrough shorthand
        return $this->opportunities()->get();       
    }
}

Client

class Client extends Eloquent{
    public function stores(){
        return $this->hasMany('Store');
    }
    public function user(){
        return $this->belongsTo('User');
    }
    public function opportunities(){
        return $this->hasManyThrough('Opportunity', 'Store');
    }
}

Store

class Store extends Eloquent {
    public function client(){
        return $this->belongsTo('Client');
    }
    public function opportunities(){
        return $this->hasMany('Opportunity');
    }
}

Opportunity

class Opportunity extends Eloquent {
    public function store(){
        return $this->belongsTo('Store');
    }
}
2
I don't know Laravell but from my Java Persistance API experience, I can say that you need a custom query for such a problem or, as you mentioned it, a relationship between user and oportunity directly. - ITroubs
@ITroubs Yeah, that's what it's looking like so far. Still, if I can have my cake and eat it too, that would be awesome. - maiorano84
Your solution with joins is pertty much the best you can do. Otherwise you can create custom relation for this very case, that will work like hasManyThorugh but with 1 more nesting level. - Jarek Tkaczyk
Using the joins like you have is how I do distant relationships too. Haven't found another way yet, but it ought to be possible by creating a relationship in a similar way to how hasManyThrough() does it. This package does belongsToThrough() and manages multiple levels, so could serve as inspiration: github.com/znck/belongs-to-through - Jason

2 Answers

0
votes

I don't think there is such method in Laravel. You have to create your custom query. This custom query can be very expensive since multiple queries will be performed. Thus, the optimum solution for this, according to me, is to relate User and Opportunity with a foreign key.

However, if you don't desire to link User and Opportunity with a foreign key, then you can create a custom query to handle this. Simply add a "hasManyThrough" relation between Opportunity and Client model like,

    <?php
    class Client extends Eloquent{
        public function store(){
            return $this->hasMany('Store');
        }
        public function user(){
            return $this->belongsTo('User');
        }

        public function opportunity(){
            return $this->hasManyThrough('Opportunity', 'Store');
        }
    }

Then create a static function in User model.

    <?php

    class User extends Eloquent implements UserInterface, RemindableInterface {

        use UserTrait, RemindableTrait;

        public function client(){
            return $this->hasMany('Client');
        }
        public function store(){
            return $this->hasManyThrough('Store', 'Client');
        }

        public static function getOpportunityOfUser($userId)
        {
             $clients = User::find($userId)->client;

            foreach ($clients as $client) {
                $opportunities[] = Client::find($client->id)->opportunity;
            }

            return $opportunities;
        }
    }

Now you can access Opportunity realted to a User in one go like,

    Route::get('/', function()
    {   
         return $usersOpportunities = User::getOpportunityOfUser(1);
    });

This will return all opportunity of all clients related to User with id '1'.

0
votes

I created a HasManyThrough relationship with unlimited levels: Repository on GitHub

After the installation, you can use it like this:

class User extends Model {
    use \Staudenmeir\EloquentHasManyDeep\HasRelationships;

    public function opportunities() {
        return $this->hasManyDeep(Opportunity::class, [Client::class, Store::class]);
    }
}