How to dynamically query an Eloquent model? - laravel-4

Synopsis
I am trying to filter my record results. I searched google and stumbled accross this post.
I have a users table with one record that is active.
// Example 1:
$model = with(new User);
$model->where('is_active', '=', 0);
dd($model->count()); // Output: 1 (Expected: 0)
// Updated (for argument sake):
dd($model->get()->count()); // Output: 1 (Expected: 0)
However when I do the following I get the right result:
// Example 2:
dd(User::where('is_active', '=', 0)->count() ); //Output: 0 (As expected);
What i'm trying to do (pseudo code):
// Example 3:
public static function getFiltered()
{
$model = with(new static);
// This is obviously populated but for argument sake.
$filters = array(
'is_active' => 1,
);
foreach ( $filters as $filter => $value ) {
$model->where($filter, '=', $value);
}
return $model;
}
I guess my question is why doesn't example 1 work?
Bearing in mind the linked question has an accepted answer that suggests my first example should work.

I guess my question is why doesn't example 1 work? Bearing in mind the linked question has an accepted answer that suggests my first example should work
No - the linked accepted answer is NOT the same as your example 1.
Your example 1 should be this:
$model = new User;
$newmodel = $model->where('is_active', '=', 0)->get();
dd($newmodel->count());
I guess my question is why doesn't example 1 work?
Because you have applied the where() query, but you did not get() the results.

Related

Laravel: How to query a Model Relationship only if it exists

I have a model Survey with a column installer_id which is related to hasOne another model Installation which is related to hasMany another model Assignment.
I want to write a query to fetch all Survey where the installer_id is not null, and where IF an Assignment exists check all the assignments if any status is != 2 ie status = 0 or 1 return the survey.
I tried this query but it does not capture the "if an assignment exists then check the status" part
$surveys = Survey::whereNotNull('installer_id')
->orWhereHas('installation',function ($query) {
return $query->whereHas('assignments',function ($q){
return $q->where('status','!=', 2 );
});
})->get();
I also tried to define a hasManyThrough relationship in the model.
public function assignments()
{
return $this->hasManyThrough(Assignment::class,Installation::class);
}
and then using this query
$schedulables = Survey::whereNotNull('installer_id')
->orWherehas('assignments',function ($query){
return $query->where('assignments.status','!=', 2 );
})->get()
Please any suggestions and help would be appreciated
I think you had the correct approach, however whereNotNull is not necessary if you're already going to check with whereHas, so this way is sufficient I think:
$surveys = Survey::whereHas('installation',function ($query) {
return $query->whereHas('assignments',function ($q){
return $q->where('status','!=', 2 );
});
})->get();
and I think you're missing the ->get(); in your code, thats why you were not getting the results
Original answer
What i needed was the whereDoesntHave method.
$surveys = Survey::whereNotNull('installer_id')
->whereDoesntHave('installation.assignments', fn ($query) => $query->where('status', 2))
->get();

Get object value on an array iteration with foreach

In the following very simplified example and study case, we get the list of posts and the count of comments for each post.
However, I intended to send to client side only the value, i.e. 2, and not the array object [{total: 2}]
Is there a simplified way to do this with the laravel?
Note: DB::select must be used
public function readPosts(Request $request) {
$posts = DB::select("SELECT * FROM posts");
foreach ($posts as $key => $post) {
$idComment = $post->id_comment;
$post->nComments = DB::select('SELECT COUNT(id_comment) As total FROM post_comments WHERE id_comment = ? ', [$idComment]);
}
if($posts){
return response()->json(['success'=>true, "output"=>$posts);
}else{
return response()->json(['success'=>false]);
}
}
response
data:
0:
id_post: 1
post: "post text"
nComments: [{total:2}]
1:
id_post: 2
post: "post text 2"
nComments: [{total:1}]
Expected
data:
0:
id_post: 1
post: "post text"
nComments: 2
1:
id_post: 2
post: "post text 2"
nComments: 1
I suggest that you can use Eloquent with eager loading to get the count.
However you need to reconstruct your tables, and build relationship with your models.
If you really want to use DB::select(),
You can just use subquery
$posts = DB::select('
SELECT posts.*, (
SELECT COUNT(id_comment) AS total
FROM post_comments
WHERE id_comment = posts.id_comment) AS total
FROM posts')
you can try this
public function readPosts(Request $request) {
$posts = DB::select("SELECT * FROM posts");
foreach ($posts as $key => $post) {
$idComment = $post->id_comment;
$total = DB::select('SELECT COUNT(id_comment) As total FROM post_comments WHERE id_comment = ? ', [$idComment]);
$post->nComments = $total[0]['total'];
}
if($posts){
return response()->json(['success'=>true, "output"=>$posts);
}else{
return response()->json(['success'=>false]);
}
}
Cleanest way would be to use the relations comments of the post
public function readPosts(Request $request) {
$posts = DB::withCount('comments')->get();
if($posts){
return response()->json(['success'=>true, "output"=>$posts);
}else{
return response()->json(['success'=>false]);
}
}
response
data:
0:
id_post: 1
post: "post text"
comments_count: 2
1:
id_post: 2
post: "post text 2"
comments_count: 1
Plus: this will take advantage of the eager loading in Eloquent and the results will be way faster and performant.
Try this simple
$posts = Post::withCount('comments');
dd($post);
You can use count() for the collections count.
foreach ($posts as $key => $post) {
$idComment = $post->id_comment;
$comments = DB::select('SELECT COUNT(id_comment) As total FROM post_comments WHERE id_comment = ? ', [$idComment]);
$post->nComments = $comments[0]['total'];
}

Laravel alternative to paginate on collection?

On my website, I have Submissions, and submissions can have comments.
Comments can have upvotes and downvotes, leading to a total "score" for the comment.
In this example, before passing the comments to the view, I sort them by score.
$comments = Comment::where('submission_id', $submission->id)->where('parent_id', NULL)->get();
$comments = $comments->sortByDesc(function($comment){
return count($comment['upvotes']) - count($comment['downvotes']);
});
This works fine. The higher the score of a comment, the higher it is sorted.
However, I want to paginate these results.
If I do ->paginate(10) instead get(), the following sortByDesc will only sort those 10 results.
So logically I would want to add the paginator after the sortByDesc like so:
$comments = $comments->sortByDesc(function($comment){
return count($comment['upvotes']) - count($comment['downvotes']);
})->paginate(10);
However this will return the error:
Method Illuminate\Database\Eloquent\Collection::paginate does not
exist.
as expected.
My question is, what is the alternative to using paginate in this situation?
EDIT:
When trying the response of #party-ring (and switching the double quotes and single quotes) I get the following error:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an
error in your SQL syntax; check the manual that corresponds to your
MariaDB server version for the right syntax to use near '["upvotes"])
- count($comment["downvotes"]) desc limit 10 offset 0' at line 1 (SQL: select * from comments where submission_id = 1 and parent_id is
null order by count($comment["upvotes"]) -
count($comment["downvotes"]) desc limit 10 offset 0)
You are trying to paginate after the get, the solution i try on my website is this and it works
$users = User::where('votes', '>', 100)->get();
$page = Input::get('page', 1); // Get the ?page=1 from the url
$perPage = 15; // Number of items per page
$offset = ($page * $perPage) - $perPage;
return new LengthAwarePaginator(
array_slice($users->toArray(), $offset, $perPage, true), // Only grab the items we need
count($users), // Total items
$perPage, // Items per page
$page, // Current page
['path' => $request->url(), 'query' => $request->query()] // We need this so we can keep all old query parameters from the url
);
You could add a macro:
if (!Collection::hasMacro('paginate')) {
Collection::macro('paginate', function ($perPage = 25, $page = null, $options = []) {
$options['path'] = $options['path'] ?? request()->path();
$page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
return new LengthAwarePaginator(
$this->forPage($page, $perPage)->values(),
$this->count(),
$perPage,
$page,
$options
);
});
}
Then you can use a collection to paginate your items:
collect([1,2,3,4,5,6,7,8,9,10])->paginate(5);
See Extending Collections under Introduction
Give this a try:
$comments = Comment::where('submission_id', $submission->id)
->where('parent_id', NULL)
->orderBy(DB::raw("count($comment['upvotes']) - count($comment['downvotes'])"), 'desc')
->paginate(10);`
SortBy returns a Collection, whereas you can only call paginate on an instance of QueryBuilder. OrderBy should return an instance of QueryBuilder, and you should be able to do the subtraction using a DB::raw statement.
** edit
I have just read about orderByRaw, which might be useful in this scenario:
$comments = Comment::where('submission_id', $submission->id)
->where('parent_id', NULL)
->orderByRaw('(upvotes - downvotes) desc')
->paginate(10);`
You might have to play around a bit with your subtraction above as I don't know the structure of your comments table.
A couple of links which might be useful:
laravel orderByRaw() on the query builder
https://laraveldaily.com/know-orderbyraw-eloquent/

Laravel simplePaginate() for Grouped Data

I have the following query.
$projects = Project::orderBy('created_at', 'desc');
$data['sorted'] = $projects->groupBy(function ($project) {
return Carbon::parse($project->created_at)->format('Y-m-d');
})->simplePaginate(5);
When I try to paginate with the simplePaginate() method I get this error.
stripos() expects parameter 1 to be string, object given
How can I paginate grouped data in this case?
The created_at attribute is already casted as a Carbon Object (by default in laravel models). that's why you are getting that error. Try this:
$projects = Project::orderBy('created_at', 'desc')->get();
$data['sorted'] = $projects->groupBy(function ($project) {
return $project->created_at->format('Y-m-d');
})->simplePaginate(5);
this answer is just for the error you're getting. now if you want help with the QueryBuilder, can you provide an example of the results you're expecting to have and an example of the database structure ?
The pagination methods should be called on queries instead of collection.
You could try:
$projects = Project::orderBy('created_at', 'desc');
$data['sorted'] = $projects->groupBy('created_at');
The problem was solved. I was create custom paginator via this example:
https://stackoverflow.com/a/30014621/6405083
$page = $request->has('page') ? $request->input('page') : 1; // Use ?page=x if given, otherwise start at 1
$numPerPage = 15; // Number of results per page
$count = Project::count(); // Get the total number of entries you'll be paging through
// Get the actual items
$projects = Project::orderBy('created_at', 'desc')
->take($numPerPage)->offset(($page-1)*$numPerPage)->get()->groupBy(function($project) {
return $project->created_at->format('Y-m-d');
});
$data['sorted'] = new Paginator($projects, $count, $numPerPage, $page, ['path' => $request->url(), 'query' => $request->query()]);
simplePaginate Method is exist in the path below:
Illuminate\Database\Eloquent\Builder.php::simplePaginate()

Laravel Eloquent query with optional parameters

I am trying to learn whether or not there is a simple way to pass a variable number of parameters to a query in Eloquent, hopefully using an array.
From what I can find, there doesn't seem to be a way to do this without looping through the Input to see what was set in the request.
Examples here: Laravel Eloquent search two optional fields
This would work, but feels non-Laravel to me in its complexity/inelegance.
Here is where I am, and this may not be possible, just hoping someone else has solved a similar issue:
$where = array("user_id" => 123, "status" => 0, "something else" => "some value");
$orders = Order::where($where)->get()->toArray();
return Response::json(array(
'orders' => $orders
),
200
);
That returns an error of course strtolower() expects parameter 1 to be string, array given.
Is this possible?
Order::where actually returns an instance of query builder, so this is probably easier than you thought. If you just want to grab that instance of query builder and "build" your query one where() at a time you can get it like this:
$qb = (new Order)->newQuery();
foreach ($searchParams as $k => $v) {
$qb->where($k, $v);
}
return $qb->get(); // <-- fetch your results
If you ever want to see what query builder is doing you can also execute that get() and shortly after:
dd(\DB::getQueryLog());
That will show you what the resulting query looks like; this can be very useful when playing with Eloquent.
You can try this:
Method 1:
If you have one optional search parameter received in input
$orders = Order::select('order_id','order_value',...other columns);
if($request->has(user_id)) {
$orders->where('orders.user_id','=',$request->user_id);
}
//considering something_else as a substring that needs to be searched in orders table
if($request->has('something_else')) {
$orders->where('orders.column_name', 'LIKE', '%'.$request->something_else.'%');
}
$orders->paginate(10);
Method 2:
If you have multiple optional parameters in input
$orders = Order::select('columns');
foreach($input_parameters as $key => $value) {
//this will return results for column_name=value
$orders->where($key, $value);//key should be same as the column_name
//if you need to make some comparison
$orders->where($key, '>=', $value);//key should be same as the column_name
}
return $orders->paginate(15);

Resources