I'm trying to work on a query builder in Laravel and want to search through a model's relations. So far my code looks like this:
$search = (new City)->newQuery();
// Search for a city based on its state.
if ($request->has('state')) {
$inquiry->whereHas('state', function ($query) use ($request) {
$query->whereState($request->state);
});
}
So I have a City model and a State model. The query is supposed to look through cities and then check each one's state relation and extract the model with the relevant states.
The code doesn't produce any errors, just an empty object.
Try this:
City::when(request()->has('state'), function($query){
$query->whereHas('state', function ($query){
$query->where('state', request()->input('state'));
});
})->get()
Related
This question can probably be best asked in the form of an analogy.
Let's say I have a "table" model that has many "filters" - these filters have a column "type" which has to either be "color" or "make", and an "allowed" column which contains the array of allowed values for each. I want to use these tables to display a filtered list of "cars" (which have color/make columns), and if I then add a car to the DB, I want it to figure out the set of tables to which that car needs to be added depending on every table's filters for car and make. So that means I query tables, and I join filters - but where the filters are "color", they have to contain the color of the car, and where the filters are "make", they have to match the make of the car. In this way, the table would get a list of cars that matches all of its filters. In pseudocode, this would be something like:
Table::join('filters', 'filters.table_id', '=', 'tables.id')
->when('filters.type', '=', "color", function($query) use ($car) {
$query->whereJsonContains('filters.allowed', $car->type);
})
->orWhen('filters.type', '=', "make", function($query) use ($car) {
$query->whereJsonContains('filters.allowed', $car->make);
})
->get();
And I'm trying to work out what, if possible, the correct way of writing such a query would be. I had the following before:
Table::join('filters', 'filters.table_id', '=', 'tables.id')
>where(function ($query) use ($car) {
$query->where('filters.type', "color")
->whereJsonContains('filters.allowed', $car->color);
})->orWhere(function($query) use ($car) {
$query->where('filters.type', "make")
->whereJsonContains('filters.allowed', $car->make);
})->get();
But this would return all tables where the car matched any of the filters instead of all of them. If I make it a where instead of an orWhere, then I get conflicting conditions in where('filters.filter_type', "make") and where('filters.filter_type', "color"), which will then give me no results at all. So, is it possible to write conditional when clauses that depend upon the value of columns like in my pseudocode example? Thanks very much!
Figured it out! Wasn't actually as difficult as I thought, and didn't require any raw SQL. First, I created two extra relationships for the table in addition to the filters one:
public function filters()
{
return $this->hasMany(Filter::class);
}
public function colorFilters()
{
return $this->filters()->where('type', "color");
}
public function makeFilters()
{
return $this->filters()->where('type', "make");
}
Then I was able to make the query work by using whereHas and logical grouping:
Table::where(function ($query) use ($car) {
$query->whereHas('colorFilters', function($colorQuery) use ($car) {
$colorQuery->whereJsonContains('allowed', $car->color);
})
->orWhereDoesntHave('colorFilters');
})
->where(function ($query) use ($car) {
$query->whereHas('makeFilters', function($makeQuery) use ($car) {
$makeQuery->whereJsonContains('allowed', $car->make);
})
->orWhereDoesntHave('makeFilters');
})
->get();
Consider the following:
$posts = $this->model->newQuery()
->whereIn('user_id', $user->following) // specifically this line
->orWhere('user_id', $user->id)
->get();
The problem with the above is that there are two queries:
Get following: $user->following
Get posts: Above
This would be much more efficient with the use of a subquery, however, I cannot actually remember the correct way to do it...
I have tried all of the following:
// This was a long-shot...
...->whereIn('user_id', function ($query) use ($user) {
$query->raw($user->following()->toSql());
});
// This works but pretty sure it can be done better with eloquent...
...->whereIn('user_id', function ($query) use ($user) {
$query->select('follow_id')
->from('user_follows')
->where('user_id', $user->id);
});
Is there a way that this can be achieved by using the previously defined relationship $user->following() instead of manually defining the relationship query like the last example above?
Reference
The following relationship is defined as follows:
/**
* Get the users that the user follows.
*/
public function following()
{
return $this->belongsToMany('SomeApp\User\Models\User', 'user_follows', 'user_id', 'follow_id')
->withTimestamps();
}
Use this:
->whereIn('user_id', $user->following()->getQuery()->select('id'))
sorry for the title of this question but I am not sure how to ask it...
I am working on a project where I have two Models Trains and Cars, to this model I have a belonging Route.
I want to make a query and check if the routeable_type is App\Car than with the selected routeable_id to get the data from the Car. And if the routeable_type is Train then with the ID to get the data from the Tran.
So my models go like this:
Train:
class Train extends Model
{
public function routes()
{
return $this->morphMany('App\Route', 'routeable');
}
}
Car:
class Car extends Model
{
public function routes()
{
return $this->morphMany('App\Route', 'routeable');
}
}
Route:
class Route extends Model
{
public function routeable()
{
return $this->morphTo();
}
}
And the query I have at the moment is:
$data = Route::leftjoin('cars', 'cars.id', '=', 'routes.routeable_id')
->leftjoin('trains', 'trains.id', '=', 'routes.routeable_id')
->select('routes.id', 'cars.model AS carmodel', 'trains.model AS trainmodel', 'routeable_type', 'routes.created_at');
With this query if I have the same ID in cars and trains I get the data from both and all messes up. How do I check if routeable_type is Car ... do this, if routeable_type is Train .. do that?
Will something like this be possible in a 1 single query:
$data = Route::select('routes.id', 'routeable_type', 'routes.created_at');
if(routeable_type == 'Car'){
$data = $data->leftjoin('cars', 'cars.id', '=', 'routes.routeable_id')->select('routes.id', 'cars.model AS carmodel', 'routeable_type', 'routes.created_at');
}else{
$data = $data->leftjoin('trains', 'trains.id', '=', 'routes.routeable_id')->select('routes.id', 'trains.model AS trainmodel', 'routeable_type', 'routes.created_at');
}
Maybe this is what you are looking for?
DB::table('routes')
->leftJoin('cars', function ($join) {
$join->on('cars.id', '=', 'routes.routeable_id')
->where('routes.routeable_type', 'App\Car');
})
->leftJoin('trains', function ($join) {
$join->on('trains.id', '=', 'routes.routeable_id')
->where('routes.routeable_type', 'App\Train');
})
->select('routes.id', 'cars.model AS car_model', 'trains.model AS train_model', 'routes.routeable_type', 'routes.created_at');
->get();
I think you may want to follow the morphedByMany design.
https://laravel.com/docs/5.7/eloquent-relationships#many-to-many-polymorphic-relations
This was also a neat visual for the different relation types.
https://hackernoon.com/eloquent-relationships-cheat-sheet-5155498c209
I was faced with a similar issue though I failed to follow the correct design initially and was forced to query the many possible relations then wrote custom logic after to collect the relation types and ids then do another query and assign them back through iteration. It was ugly but worked... very similar to how Eloquent does things normally.
i don't have enough repo, so i can't comment. that's why i am putting as an answer.
You should use 2 different queries, for each model.
This will be better, code wise as well as performance wise. also if both models have similar fields you should merge them to 1 table and add a 'type' column.
and put non-similar fields in a 'meta' column.
( in my opinion )
i am trying to query many to many relation for my get api call. i have three table as shown here but i am not using pivot table.
This is my Projects model class and this the function
public function projectRewd()
{
return $this
->belongsToMany('App\Rewards','rewards','project_id','reward_id');
}
And this is my Rewards model class and function
public function projectShip()
{
return $this->belongsToMany('App\Shipping_location','shipping_location','projects_id','rewards_id');
}
This is my api controller function
Route::get('projects/{id}', function($id) {
$proj = Projects::whereHas('projectRewd', function($q)
{
$q->where('id', $id);
});
return $proj;
});
i am using this link for api call
http://localhost:8000/api/projects/1
i want to extract rewards data and shipping_location data associate with project_id.
i am getting this error
"message": "Object of class Illuminate\\Database\\Eloquent\\Builder could not be converted to string"
i check and tried all related error from different post.
i also search and tried many technique. Cant solve my problem.
Please suggest me how to do this??
can i do this type of query in larvel without using pivot table??
You are getting Builder model because you forgot to add ->first() or ->get().
You should write:
$proj = Projects::whereHas('projectRewd', function($q){
$q->where('id', $id);
})->first();
Your closure-based controller returns your query-builder object. Not a project. You need to retrieve results from the query by fetching e.g. the first result (->first()) or all (->get()).
Route::get('projects/{id}', function($id) {
$proj = Projects::whereHas('projectRewd', function($q)
{
$q->where('id', $id);
})->first();
return $proj;
});
Referencing $id:
The reason why $id is unknown, is that the closure doesn't know about it.
You can pass it to the closure using use(...).
Route::get('projects/{id}', function($id) {
$proj = Projects::whereHas('projectRewd', function($q) use ($id)
{
...
Further:
Your whereHas query looks incorrect to me:
$q->where('id', $id);
Apparently $id is the project id. But the 'id' column in projectRewd is the primary key of projectRewd (unless you have modified the defaults).
I assume you want to query all projects that have at least one projectRewd:
Route::get('projects/{id}', function($id) {
$proj = Projects::has('projectRewd')->first();
return $proj;
});
And if you want to eager load the joined tables:
Route::get('projects/{id}', function($id) {
$proj = Projects::with('projectRewd. projectShips')->has('projectRewd')->first();
return $proj;
});
I have collection that created with complicated laravel query and this query's result is too big. So i think i must use algolia. As i know, algolia gets the model table data to itself as json and serve from there.
$result = User::search("UserName")->get();
It needs to some model configurations like searchAs etc.. all are related with existing model and you can make search from model with search method (above example). What i want to ask is, i have complicated query and result has too many attributes that come from another tables (joined). I want to make search on my custom query result. Is it possible ?
My example query :
$friendShips = Friend::
join("vp_users as users","users.id","=","friendships.friendID")
->leftJoin("vp_friendships as friendshipsForFriend",function($join) use ($request)
{
$join->on("friendships.friendID","=","friendshipsForFriend.userID");
$join->on("friendshipsForFriend.friendID","=",DB::raw($request->userID));
})
->leftJoin("vp_videos_friends as videosFromFriendMedias",function($join)
{
$join->on("videosFromFriendMedias.userID","=","friendships.friendID");
$join->on("videosFromFriendMedias.friendID", "=" ,"friendships.userID");
$join->on("videosFromFriendMedias.isCalled", "=" , DB::raw(self::CALLED));
})
->leftJoin("vp_videos_friends as videosToFriendMedias",function($join)
{
$join->on("videosToFriendMedias.userID", '=', "friendships.userID");
$join->on("videosToFriendMedias.friendID", '=', "friendships.friendID");
$join->on(function($join){
$join->on("videosToFriendMedias.isCalled", '=', DB::raw(self::CALLED));
$join->orOn("videosToFriendMedias.isActive", '=', DB::raw(self::ACTIVE));
});
})
->leftJoin("vp_videos_friends as
//some join rules too
})...
I believe the best way would be to use this request and chain the searchable() method. It will index the collection returned by the query to Algolia.
$friendShips = Friend::
join("vp_users as users","users.id","=","friendships.friendID")
->leftJoin("vp_friendships as friendshipsForFriend",function($join) use ($request) {
$join->on("friendships.friendID","=","friendshipsForFriend.userID");
$join->on("friendshipsForFriend.friendID","=",DB::raw($request->userID));
})
->searchable();