Laravel: Get root class in nested relationship query - laravel

How can I get the root class in a nested eager relationship? Basically, I want to get something like this:
$entities = Entity::with(['attribute.valueable' => function ($query) {
$root = $query->getRootParent();
$query->where('entity_id', $root->id);
}])->get();
SQL-queries should be like
select * from `entities`;
select * from `entity_types_attributes` where `entity_types_attributes`.`entity_type_id` in (?);
select * from `entity_attribute_value_int` where `entity_attribute_value_int`.`attribute_id` in (?, ?, ?)
AND `entity_attribute_value_int`.entity_id` = ? // this part should be appended

Related

laravel eloquent with and join conflict

I am quite new to Laravel... I have a posts table that has a relation with articles and users tables. In the controller I either get all the posts matching a search criteria or get all posts. In both cases, I learned how to add the related fields to the collection $posts.
// get all posts matching criteria
if (request('search') && request('search') != '') {
$posts = Post::orderBy($orderBySortField,$sortOrder)
->with(['user:id,name','article:id,title'])
->where('post','like','%'.request('search').'%')
->join('users', 'posts.user_id', '=', 'users.id')
->orWhere('users.name','like','%'.request('search').'%')
->join('articles', 'posts.article_id', '=', 'articles.id')
->orWhere('articles.title','like','%'.request('search').'%')
->get();
// get all posts
} else {
$posts = Post::orderBy($orderBySortField,$sortOrder)
->with(['user:id,name','article:id,title'])
->get();
}
// add related fields
foreach ($posts as $post) {
$post->title = '['.$post->article->title.']';
$post->user = '['.$post->user->name.']';
}
When I get all the posts, the related fields are correct in the list displayed in the view page.
However, if I search for the name of a specific user, I get a list where the $posts->user are incorrectly related.
I could figured that by displaying the last queries.
For all the posts:
select * from `posts` order by `posts`.`id` desc
select `id`, `name` from `users` where `users`.`id` in (1, 11)
select `id`, `title` from `articles` where `articles`.`id` in (1)
For the posts written by user 'paul' (user_id = 1):
select * from `posts` inner join `users` on `posts`.`user_id` = `users`.`id` inner join `articles` on `posts`.`article_id` = `articles`.`id` where `post` like ? or `users`.`name` like ? or `articles`.`title` like ? order by `posts`.`id` desc [where ? = %paul% %paul% %paul% ]
select `id`, `name` from `users` where `users`.`id` in (11)
select `id`, `title` from `articles` where `articles`.`id` in (1)
So why is Laravel last queries look for user_id = 11 which is another user?
Am I doing something wrong?
Change the join code the whereHas
// get all posts matching criteria
if (request('search') && request('search') != '') {
$search = request('search');
$posts = Post::orderBy($orderBySortField,$sortOrder)
->with(['user:id,name','article:id,title'])
->where('post','like','%'.$search.'%')
->orWhereHas('user', function($userQuery) use($search){
$userQuery->Where('users.name','like','%'.$search.'%');
})
->orWhereHas('article', function($articleQuery) use($search){
$articleQuery->Where('articles.title','like','%'.$search.'%');
})
->get();
}
If you have define model relation, you don't have to use join in your query, just use with function.
Using the two create duplicate data, that why you getting incorrectly related error.

Eloquent with query on relations with nested WHERE

I'm struggling with Eloquent with query on relation.
For example, I'm looking for only the client John who doesn't have transaction.
How can I do this with Eloquent?
Client model relation
public function transactions()
{
return $this->hasMany(Transaction::class);
}
$results = Client::whereDoesntHave('transactions', function ($query) use ($inputFirst, $period) {
$query->where('transactions.period_id', '=', $period->id)
->where('firstname', '=', $inputFirst);
})
->orderBy('id', 'desc')
->get();
A little help would be great.
Thanks
The issue with your code is that you are nesting the statements. The way you are doing Laravel is generating a SQL like this:
select * from `clients` where not exists
(select * from `transactions`
where `clients`.`id` = `transactions`.`client_id`
and `name` = John)
But the actual SQL code you're looking for is:
select * from `clients` where not exists
(select * from `transactions`
where `clients`.`id` = `transactions`.`client_id`)
and `name` = John)
For that your code should be:
$results = Client::whereDoesntHave('transactions')
->where('firstname', '=', $inputFirst)
->orderBy('id', 'desc')
->get();
*I didn't include the transactions.period_id, coz I wasn't sure if where you're looking to have it. But if it's meant to be inside the second select, leave in the nested statement, if not leave outside.

Laravel: Property [user] does not exist on this collection instance

In Laravel, I've Orders model where each order belongs to only one user,
I want to return only user names and orders ids when I fetch the orders, So in my OrdersController I've this function
public function getAllOrders()
{
$orders = Order::all();
return new OrderResource($orders);
}
and in my OrderResource I've
public function toArray($request)
{
return [
'user' => $this->user,
'id' => $this->id,
];
}
I'm getting an error saying Property [user] does not exist on this collection instance.
I think the reason is coming from the fact that $orders is a collection and I should loop through it and get for each order get the user associated with it, but I've no idea how to do that.
Note: I'm using oneToMany and belongsTo on Users and Orders Model. so orders table doesn't have a user column, I want to get the user from the relationship.
When using a collection use like this;
public function getAllOrders()
{
return OrderResource::collection(Order::all());
}
When using a model use like this;
public function getOrder($id)
{
return new OrderResource(Order::find($id));
}
More information in the documentation: https://laravel.com/docs/7.x/eloquent-resources#concept-overview
Note: To avoid N+1 queries, you should get your orders like this; Order::with('user')->get(); instead of Order::all()
The difference is that Order::with('user')->get(); will perform two queries.
select * from orders
select * from users where id in (?, ?, ?, ?, ...)
Whereas Order::all() will perform N+1 queries (N = number of orders)
select * from orders
select * from users where id = ?
select * from users where id = ?
select * from users where id = ?
... and so on

Eloquent whereHas Relationship with constraint on particular related Entity

Consider having two models User, and Book the last one has a status column that can obtain different string values active, inactive, deleted, so the user can have multiple books and the book belongs to the user.
how could I get only users that have their last book status = 'inactive'?
The SQL Query for the behavior is given below:
SELECT
*
FROM
`users`
WHERE EXISTS
(
SELECT
*
FROM
`books`
WHERE
`books`.`user_id` = `users`.`id` AND `books`.`status` = 'inactive' AND `books`.`id` =(
SELECT
nested.`id`
FROM
`books` AS nested
WHERE
nested.`user_id` = `users`.`id`
ORDER BY
nested.`created_at` DESC
LIMIT 1
)
)
I'm using Laravel 5.6
Create additional relationship in User model that returns wanted result. Basically you need 1-1 relationship for this.
/**
* #return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function inactiveBookStillLatestPerUser()
{
return $this->hasOne(Book::class)->where(['status' => 'inactive', 'id' => function (\Illuminate\Database\Query\Builder $nested) {
$nested->from('books as nested')
->selectRaw('max(id)')
->whereRaw('nested.user_id = books.user_id');
}]);
}
Then in somewhere in code (i.e. controller) you call it with
$users = User::has('inactiveBookStillLatestPerUser')->get();
// or if books are needed too
// $users = User::has('inactiveBookStillLatestPerUser')->with(['inactiveBookStillLatestPerUser'])->get();
I used id latest order [max(id)] in subquery to avoid unwanted result if one user made multiple books batch insert at same point of time and when all those books would have same time of insert so latest per created_at wouldn't be most accurate, maybe. But you can do that similarly, instead:
/**
* #return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function inactiveBookStillLatestPerUser()
{
return $this->hasOne(Book::class)->where(['status' => 'inactive', 'created_at' => function (\Illuminate\Database\Query\Builder $nested) {
$nested->from('books as nested')
->selectRaw('max(created_at)')
->whereRaw('nested.user_id = books.user_id');
}]);
}
Maybe second example is ok, but first example with id would work fine, though.
User::where('your-conditions')
->whereHas('books', function($query) {
$query->where('books.status', '=', 'inactive')
->orderBy('id', 'desc')
->first();
})->get();

Laravel dynamic where clauses

I would like to create a dynamic query with laravel ORM. In my example:
$query = Task::orderBy('customer_family_name');
if (array_key_exists('date', $request->filter)) {
if($request->filter['date'] != null) {
$query = $query->whereDate(....);
}
}
..... more filters
if (array_key_exists('search', $request->filter)) {
if(strlen($request->filter['search']) > 1) {
$searchText = $request->filter['search'];
$query = $query->where('customer_family_name', $searchText.'%');
}
}
}
$Tasks = $query->get();
But it is not working as expected. I thought that laravel will push all where elements, so that ALL where clauses must be met.
But the search for a family name is not working. Do i something wrong? How can i combine it correct? Is there a way to display the SQL Query with orm?
Edit: ok found out that is another problem, my query looks like:
"select * from [tasks] where [state_id] in (?, ?, ?, ?, ?, ?) and [customer_family_name] = ? and [regulator_user_id] in (?, ?, ?) or [regulator_user_id] is null order by [customer_family_name] asc"
And my filter:
if (array_key_exists('regulator', $request->filter)) {
$regulator_array = explode(",", $request->filter['regulator']);
if(is_array($regulator_array)) {
$query->whereIn('regulator_user_id', $regulator_array)->orWhereNull('regulator_user_id');
}
}
It looks like that the orWhereNull is the problem. I would like to return every row where
regulator_user_id is in that array
AND all where regulator_user_id IS NULL
But all other filters should work as expected.
How can i do that?
You should not be re-saving to your $query variable:
$query = $query->whereDate(....);
Change it to this (and for your other filters):
$query->whereDate(....);
Your final command :
$Tasks = $query->get();
Will then return the proper data according to the extra queries that were added to your $query object.
Ok ill found the correct solution:
Ill need my own query function to combine an Where with an orWhereNull:
if(is_array($regulator_array)) {
$query = $query->where(function($query) use($regulator_array) {
$query->whereIn('regulator_user_id', $regulator_array)
->orWhereNull('regulator_user_id');
});
}
Now the query works fine. Thanks for all the answers.
As mentioned from Jhonny Walker you shouldn't save every time the $query but use the $query->where each time.
One line before the $Tasks = $query->get();
try to debug the mysql command that is generated with the following command.
dd($query->toSql());
If you want paste the result in order to help you with the where clauses.
Or you can use this sweet eloquent method when(), so you don't have to use if statement and break the method chaining, instead you got fluent query building.
Eloquent when()

Resources