Distant HasManyThrough - php

I have four Models:
User
Client
Store
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');
}
}

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'.

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

Related

Cannot access Intermediate table in HasManyThrough relationship

My DB schema looks like this.
Now, in artisan tinker mode, When I try to query Details table from user Model, it shows me the records of the details table but I cannot access the the Cases Model for some reason, it always returns NULL in tinker.
This is my User Model
public function details()
{
return $this->hasManyThrough('App\Models\Detail', 'App\Models\Cases', 'user_id', 'case_id', 'id', 'id');
}
What am I doing wrong?
If for convenience you want to access Details directly from the User model then you can define relations as - (may seem like a little duplication but worth if it results in ease)
class User extends Model
{
public function cases()
{
return $this->hasMany(Case::class);
}
public function details()
{
return $this->hasManyThrough(Detail::class, Case::class);
}
}
class Case extends Model
{
public function details()
{
return $this->hasMany(Detail::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
class Detail extends Model
{
public function case()
{
return $this->belongsTo(Case::class);
}
}
Now both cases and details can be directly accessed via User record
$user->cases;
$user->details;
The idea of hasManyThrough is to skip the intermediate table. If you need to look at the cases and the details maybe you should define other relations for it.
// User model
public function cases()
{
return $this->hasMany(Cases::class, 'user_id');
}
// Cases model
public function details()
{
return $this->hasMany(Detail::class, 'user_id');
}
$users = User::with('cases.details')->get();
foreach ($users as $user) {
// an user
foreach ($user->cases as case) {
// a case
foreach ($case->details as $detail) {
// the details of a case
}
}
}

Laravel Eloquent elation for pivot table

I'm currently working on a laravel project, but I'm kind of stuck finding the right eloquent relations.
My tables and the connections (should) look like this:
Project Relations
My model relations look like this:
User
public function team()
{
return $this->hasMany(Team::class, 'user_id');
}
public function evaluation()
{
return $this->hasMany(Evaluation::class, 'user_id');
}
Team
public function user()
{
return $this->belongsTo(User::class);
}
public function survey()
{
return $this->hasMany(Survey::class, 'team_id');
}
Evaluation
public function user()
{
return $this->belongsTo(User::class);
}
public function survey()
{
return $this->hasMany(Survey::class, 'evaluation_id');
}
Survey
public function team()
{
return $this->belongsTo(Team::class);
}
public function evaluation()
{
return $this->belongsTo(Evaluation::class);
}
public function surveyresponse()
{
return $this->hasMany(SurveyResponse::class, 'survey_id');
}
SurveyResponse
public function survey()
{
return $this->belongsTo(Survey::class);
}
public function testquestion()
{
return $this->belongsTo('App\TestQuestion');
}
Is this the way to go? Do I need a "Has Many Through" relation here? Or a "Polymorphic Relationship"?
Seems correct to me, i just didnt see the TesteQuestion model (your last relation).
Answering your question:
The HasManyThrough relation is just a shortcut for accessing distant relations via an intermediate relation, in your case: Users has many evaluations that has many surveys. With this relationship you could get all surveys from a user.
Your relation would look like this:
/**
* Get all of the surveys for the user.
*/
public function surveys()
{
return $this->hasManyThrough('App\Survey', 'App\Evaluation');
}
You can access this relation like this:
$user->surveys();
But you can achieve the same (without using the HasManyThrough) by doing:
$user->evaluations()->surveys();
Beware that this will return the evaluations too, not just the surveys and it requires more processing.
So i recommend you doing the HasManyThrough relationship if you pretend to access the surveys a lot.

Problem with Laravel Eloquent - relation not working

I'am beginner in Laravel. I have project in Laravel 5.8.
I have User model:
class User extends Authenticatable implements MustVerifyEmail
{
use Notifiable;
use psCMS\Presenters\UserPresenter;
use scopeActiveTrait;
public static $roles = [];
public $dates = ['last_activity'];
// ...
public function scopeHistory()
{
return $this->hasMany('App\UserLoginHistory');
}
// ...
}
and UserLoginHistory:
class UserLoginHistory extends Model
{
protected $quarded = ['id'];
public $timestamps = false;
protected $fillable = ['user_id', 'date_time', 'ip'];
public function user()
{
return $this->belongsTo('App\User');
}
}
I want show user login history by this code:
User::history()->where('id', $idAdmin)->orderBy('id', 'desc')->paginate(25);
but it's not working.
This function not working - I haven't got results.
How can I fixed it?
First of all, you are defining your relationship as a scope (prefixing the relationship with the scope keyword). Try updating your model relationship to this:
public function history()
{
return $this->hasMany('App\UserLoginHistory');
}
Then, given your query, it seems that you want to get all the UserLoginHistory
records for a given User. You could accomplish this in two ways (at least).
From the UserLoginHistory model itself, constraining the query by the foreign key value:
$userId = auth()->id(); // get the user ID here.
$results = UserLoginHistory::where('user_id', $userId)->paginate(15);
// ^^^^^^^ your FK column name
From the User model using your defined relationship:
$userId = auth()->id(); // get the user ID here.
$results = User::find($userId)->history;
The downside of the second approach is that you'll need to paginate the results manually.
in your User model you should define your relation by this way :
public function history()
{
return $this->hasMany('App\UserLoginHistory');
}
then if you would like to select with history model you can do that with WhereHas() method :
User::whereHas(['history'=>function($q) use ($idAdmin) {
$q->where('id',$idAdmin)
}])->orderBy('id', 'desc')->paginate(25);
You must be do this changes
public function history()
{
return $this->hasMany('App\UserLoginHistory');
}
usage
$user = User::find($idAdmin);
$userHistories = $user->history()->latest()->paginate(25);
or get user with all history
User::with('history')->find($idAdmin);
// Post model
namespace App;
use App\User;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public function categories()
{
return $this->belongsToMany('App\Category')->withTimestamps();
}
}
// Category model
namespace App;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
public function posts()
{
return $this->belongsToMany('App\Post')->withTimestamps();
}
}

How can I make a row of relations in laravel?

I am struggling with this for a while now, but I can't figure it out how it works.
In laravel I have a few models with relationships. I wan't to have al the accounts based on the logged in user and the passed parameter for the workspace.
This is how the models looks like: (I only coppied the methods to keep it short)
The user Model:
class User extends Eloquent implements UserInterface, RemindableInterface {
public function workspaces()
{
return $this->hasMany('Workspace', 'user_id');
}
public function account()
{
return $this->hasManyThrough('account', 'Workspace', 'id', 'workspace_id');
}
}
The workspace model:
class Workspace extends Eloquent implements UserInterface, RemindableInterface {
public function account()
{
return $this->hasMany('account', 'workspace_id', 'id');
}
public function user()
{
return $this->belongsTo('User', 'user_id', 'id');
}
}
The account model
class account extends Eloquent implements UserInterface, RemindableInterface {
public function account_url()
{
return $this->hasOne('acountUrl', 'id', 'account_url_id');
}
public function workspace()
{
return $this->belongsTo('Workspace', 'workspace_id', 'id');
}
}
The account_url model
class account_url extends \Eloquent implements UserInterface, RemindableInterface {
public function account()
{
return $this->belongsToMany('account', 'id', 'account_url_id');
}
}
So I want from the logged-in user with a specific workspace all the account with the account_urls
something like this: user->workspace->account->account_url
I tried the following things but it don't work:
$account_urls = user::find( Auth::user()->id)->first()->workspaces()->where('id', '=', 1)->account()->account_url()->select('url')->get();
and:
$account_urls = account::where('workspace_id', '=', '1')->account_url()->select('url')->get();
Only when I do it like this:
$account_urls = account::find(1)->account_url()->select('url')->get();
But then I get only 1 url, but when I replase find(1) for all() I get an error?
Is there someone who can help me with this?
Tanks,
Your relations are wrong, change them to:
// User
public function account()
{
return $this->hasManyThrough('Account', 'Workspace', 'user_id', 'workspace_id');
}
// Account
// use camelCase for relations
public function accountUrl()
{
// I assume you have account_url_id on accounts table
// If it's opposite, then use hasOne
return $this->belongsTo('AcountUrl', 'account_url_id', 'id');
}
// AccountUrl (use camelCase)
public function account()
{
// if above is hasOne, then here belongsTo instead.
return $this->hasOne('account', 'account_url_id', 'id');
}
Now, fetching models:
// this part is .. amazing ;)
user::find( Auth::user()->id )->first();
// it does this:
Auth::user()->id // fetch user and get his id
user::find( .. ) // fetch user with given id, you have this user already above...
->first() // fetch first row from users table (not the one with id provided before)
so you want:
$account_urls = Auth::user()->workspaces()
->where('id', '=', 1)->first() // first fetches the result
// or simply
// ->find(1)
->accounts()->first()->accountUrl()
->pluck('url'); // this does 'SELECT url' and returns only this field instead of model
Just remember that:
$user->workspaces
$workspace->accounts
these are collections, so you can't call anything of the model on them, you need to get single model first.

Laravel: hasMany() with take() only working on first result

I have the following models.
class User extends Eloquent {
public function comments() {
return $this->hasMany('Comment');
}
}
class Comment extends Eloquent {
public function user() {
return $this->belongsTo('User');
}
}
For the sake of this example, a user could have 1,000s of comments. I am trying to limit them to just the first 10. I have tried doing it in the User model via
class User extends Eloquent {
public function comments() {
return $this->hasMany('Comment')->take(10);
}
}
and via UserController via closures
$users = User::where('post_id', $post_id)->with([
'comments' => function($q) {
$q->take(10);
}
]);
Both methods seem to only work on the first record of the result. Is there a better way to handle this?

Categories