Laravel - Convert array back to a collection - php

I am trying to get a collection of categories only if they are in the products table. My categories table has 300 items. I only want a collection if a category is attached in the products table. The $categories collection should only result in about 10 categories because there are only about 10 products that have different category_ids
$products = DB::table('products')->groupBy('category_id')->get();
foreach($products as $product){
$categories[] = DB::table('categories')->where('id', '=', $product->category_id)->first();
}
$categories = collect($categories)->all();
Maybe I am going about this wrong and should use a different query builder method?
The end result $categories does get the results I am after but in my blade I get the "Trying to get property of non-object" error.

If using $categories in your blade file as a collection, you will need to remove the ->all() method.
->all() is converting the collection into an array after it is being created:
$categories = collect($categories);

You get Trying to get property of non-object because one of
DB::table('categories')->where('id', '=', $product->category_id)->first();
return null value.
You can fix it this way
$products = DB::table('products')->groupBy('category_id')->get();
$categories = collect();
foreach($products as $product){
$category = DB::table('categories')->where('id', '=', $product->category_id)->first();
if ($category) {
$categories->push($category);
}
}
If you want to get collection instance you must be use collect() helper method with array argument.
For example
collect($categories); // is_array(categories) is true
You are doing many request in foreach. That is not right way. Instead it you can achieve collection instance doing only 2 request.
$categoryIds = DB::table('products')->pluck('category_id')->toArray();
$categories = DB::table('categories')->whereIn('id', $categoryIds)->get();
See docs https://laravel.com/docs/5.8/queries#retrieving-results

This can be done with one simple eloquent query. There's no need to use query builder unless you're doing something overly complex (in my opinion).
whereHas() will only return Categories that have Products.
Categories::with('products')->whereHas('products')->get();
As long as the relationships are correct on the models the above is what you're looking for. As pointed out in the comments, you need models and relationships. Laravel uses the MVC pattern, and the first letter stands for model so I'm going to guess you're using them. If not let me know and I can help set those up because you should be using them.
And if you NEED to use the query builder clean that code up and use something like this so you don't have to worry about recollecting. Also check out the hydrate() method to change these generic class instances into instances of the Categories model.
DB::table('categories')->whereIn('id', function($q){
$q->select('category_id')->from('products');
})->get();

Related

Laravel Where clause, paginate and sortByDesc

I am working a project and I would want to use a where clause, paginate and then sort in the collection in specific order. I have tried the result below but keeps throwing the errors below Method:
Illuminate\Database\Eloquent\Collection::links does not exist. (View:
/Applications/XAMPP/xamppfiles/htdocs/vermex/resources/views/equipments.blade.php)
The Product model is where I am getting the data and store in a variable called $equipment. If there is a better way of doing this, please help.
public function equipments()
{
$equipments = Product::where('product_category_id', 3)->paginate(2)-
>sortByDesc('id');
return view('equipments', compact('equipments'));
}
Try putting the orderBy before the paginate
$equipments = Product::where('product_category_id', 3)->orderBy('id', 'desc')->paginate(2);
sortByDesc is a collection method.
paginate will need to be last for links to be available in the blade view.

Laravel pagination error because of sortBy

I am trying to paginate an eloquent object but i can't get it to work. The paginate throws a error because $products is not a query builder object but a collection.
// i get this value from a $POST variable
$customOrderIds = [3,2,1,4,6,5,9,8,10,7,11,12,13,14,15,16,17,20,18,19,21,22]; // I want the products ordered in this sequence
$products = Product::get()->sortBy(function($product) use($customOrderIds)
{
return array_search($product->id, $customOrderIds);
});
$products->paginate(5); // Error is thrown here
I want to keep the order of products that is defined in the $customOrderIds
In other questions they suggest to replace get() function with the paginate function but then my custom order will be only applied to the 5 items in the pagination.
I would rather not use anything with raw sql
paginate is an Eloquent method, so it won't work on your collection. However, collections have a forPage method, which you can use:
The forPage method returns a new collection containing the items that would be present on a given page number. The method accepts the page number as its first argument and the number of items to show per page as its second argument
So what you'll need is
$products->forPage(1, 5);
You have to indeed replace the get with the paginate, but you'll have to do the sorting before you paginate. You can try something along the lines of:
Product::orderByRaw(
'FIELD(id,3,2,1,4,6,5,9,8,10,7,11,12,13,14,15,16,17,20,18,19,21,22)'
)->paginate(5);
Sources:
https://laravel.com/docs/5.8/queries#raw-expressions
https://laracasts.com/discuss/channels/eloquent/custom-orderby-in-laravel-query-builder

Cant retrieve record from large scale table row

I am using laravel framework.In my database records table have more then 400000 row. Now I want to retrieve record using some condition
logic :
need to match service id
need to match status
but my application cant retrieve data (unable to handle this request).
I am using query using this
foreach ($ven as $ven){
$data = $ven->data;
$record = $data ->records()->where('status','success')->get();
}
My status column already added in index.
Need suggestion
First you need to keep statuses in int form not as strings. It would help you in filtering records easily.
And use chunk for handling large datasets. I suppose $ven is for venue. So, follow the code below.
DB::table('venues')
->select('venues.*', 'records.*')
->join('records', 'venues.id', '=', 'records.venue_id')
->orderBy('id')->chunk(1000, function($venues) {
foreach ($venues as $venue) {
// your logic
}
});
Note: I used query builder instead eloquent. That is because query builder is faster than eloquent.
The way you are trying to access related records in loop could create N + 1 problem
As per official docs When accessing Eloquent relationships as properties, the relationship data is "lazy loaded". This means the relationship data is not actually loaded until you first access the property. However, Eloquent can "eager load" relationships at the time you query the parent model. Eager loading alleviates the N + 1 query problem
You could eager load your related data as
$results = ParentModel::with('records')
->where(...) /* If there are any filters for ParentModel */
->get();
For eager loading only 2 queries will be executed to get your data for main model and related model. Each row in $results will a collection of related records which you can iterate to list details of each related object.
To get filtered rows of related model you could modify with() method as
$results = ParentModel::with(['records' => function ($query) {
$query->where('status','=','success');
}])
->where(...) /* If there are any filters for ParentModel */
->get();
To get filtered rows of ParentModel on basis of related records you could use whereHas
$results = ParentModel::with('records')
->where(...) /* If there are any filters for ParentModel */
->whereHas('records', function ($query) {
$query->where('status','=','success');
})->get();

Overwrite paginator items in Laravel

I have a list of ids that come from a complicated query. I paginate the response of that complicated query and then use those ids to get the eloquent models. I then put it through a resource with the pagination meta data.
The laravel AbstractPaginator class protects the items attribute so you cannot easily overwrite them. I have a solution to use a ReflectionProperty but I'm after a simpler solution.
The below works but it is not particularly elegant.
// $studentIds == Long complicated query that would return 1000s of students
$data = $studentIds->paginate(); // Execute the query limited to 15.
// Use ids to get eloquent models for our students
$students = Student::whereIn('id', $data->pluck('id'))->get();
// Overwrite paginate `items` attribute so that our response contains pagination meta.
$rp = new \ReflectionProperty('Illuminate\Pagination\AbstractPaginator', 'items');
$rp->setAccessible(true);
$rp->setValue($data, $students);
return new StudentResourceCollection($data);
Use the setCollection() method:
$data->setCollection($students);

How to add a condition in Eloquent query based on related model data?

If you have a query that uses eager loading like this:
Brand::with('tags')
->where('id', $id)
->get();
A brand can have many tags.
I then also have an array of tag ids like this [2,4]. How do I add a condition to this query where it returns only those brands whose tags are in the array?
I tried the eager load constraints but that condition is then placed on the tags model, not the Brand.
I tried this also but it returns an unknown method error:
public function tagsIn($allTags){
return $this->belongsToMany('App\Tag', 'brand_tags')
->whereIn('tags.id', $allTags);
}
Brand::with('tags')
->tagsIn('[2,4]')
->get();
I suspect a possible limitation to getting it to work is the fact that Eloquent makes two separate database calls. But is there a way nevertheless?
DB::table('Brands')
->join('brand_tag','brands.id','=','brand_tag.brand_id')
->join('tags','brand_tag.tag_id','=','tags.id')
->whereIn('tags.id',$allTags)
->get();
Try this DB::table('name')->whereIn('column', array(1, 2, 3))->get();
I think you should use this package to handle tag. I used it in my projects. laravel-tagging

Categories