I am trying to get all categories that have products from the database and push them into another array.
I have four 3 categories and two of them have products.
Here is my code:
$categories = Category::all();
$count = count($categories);
$categoriesWithProducts = array();
for($i = 0; $i < $count; $i++) {
if($categories[$i]->products->count() > 0) {
array_push($categoriesWithProducts, $categories[$i]);
}
return response()->json($categoriesWithProducts);
}
I get an array with just one item instead of two.
Where am i going wrong?
Although error is obvious (mentioned in comment) you could rewrite the whole thing:
$categories = Category::withCount('products')->get(); // you load count to not make n+1 queries
$categoriesWithProducts = $categories->filter(function($category) {
return $category->products_count > 0
})->values();
return response()->json(categoriesWithProducts);
Of course you could make it even simpler:
return response()->json(Category::withCount('products')->get()
->filter(function($category) {
return $category->products_count > 0
})->values()
);
But in fact the best way would be using Eloquent relationships so you could use:
return response()->json(Category::has('products')->get());
Related
i want query a table with a json column
the object stored in json column is like this:
[
{
"title":"first",
"ids":[79,583,584]
},
{
"title":"second",
"ids":[600,601,602]
},
{
"title":"third",
"ids":[605,606,624]
}
]
and for example i want to find a row where one of its ids property contain for example 79. something like this:
Model::query()->whereJsonContains('data', ['ids[*]' => 79])->first();
i,ve searched a lot and tried some syntaxes but nothing worked. is that possible to do? how?
my database is mysql
querying nested json formats might not be available as of now, you can try filtering the eloquent collection though.
you have to cast the json data column as array on your model first and then filter the collection as per your conditions.
$id = 8;
$filtered = Model::all()->filter(function($item, $key) use($id) {
$count = 0;
foreach($item->data as $data) {
if(in_array($id, $data['ids'])) {
$count += 1;
}
}
return $count > 0;
})->all();
I'm overriding Mage_Catalog_Block_Product_List 's _getProductCollection by adding:
foreach ($this->_productCollection as $product) {
$product->setDistance(Mage::helper('myhelper')->getDistance($product));
}
Now I want the collection to be sorted by distance, I tried the following:
$this->_productCollection = Mage::helper('myhelper')->sortProductByDist($this->_productCollection);
The helper for sorting is like following (stolen from SO):
public function sortProductByDist($products) {
$sortedCollection = Mage::getSingleton('catalog/layer')
->getProductCollection()->addFieldToFilter('entity_id', 0);
$sortedCollection = $sortedCollection->clear();
$collectionItems = $products->getItems();
usort($collectionItems, array($this,'_sortItems'));
foreach ($collectionItems as $item) {
$sortedCollection->addItem($item);
}
return $sortedCollection;
}
protected function _sortItems($a, $b) {
$order = 'asc';
$al = strtolower($a->getDistance());
$bl = strtolower($b->getDistance());
if ($al == $bl) {
return 0;
}
if ($order == 'asc') {
return ($al < $bl) ? -1 : 1;
} else {
return ($al > $bl) ? -1 : 1;
}
}
The problem is the product collection is no longer paginated when this additional sort is applied.
Anyone knows how to fix this?
You are not doing it the right way, and there are no easy solutions. You need to use the database to do the sorting.
The _productCollection is not an array, it's an object that has references, the query at this point can still be updated, the pagination will be handled by the query to the database.
if you do a
Mage::log((string) $this->_productCollection->getSelect());
you will see the query in the logs
What you do is to load the products of the current page, add the distance on all products of the page, and create a new collection where you force your items in. So that collection's data is not coming from the database and only contains the elements of the current page.
Sorting using php is a bad idea, because if you have a lot of products it means you need to load them all from the database. That will be slow.
The solution
Calculate distance in the database directly by modifying the query.
You can edit the select query and do the distance calculation in the database
$this->_productCollection
->getSelect()
->columns("main.distance as distance")
Now you can add a sort on the product collection
$this->_productCollection->setOrder('distance');
The complicated part will be to write the equivalent of your getDistance method in mysql. In my example I assumed distance was in the database already.
Don't hesitate to print the query at various steps to understand what is going on.
I have a function that looks for possible boxes that can carry the article.
public static function get_possible_boxes($article,$quantity)
{
$i = 0;
$possible_boxes = array();
$total_weight = $articles->grams * $quantity;
$boxes = Boxes::all();
foreach($boxes as $box)
{
if($total_weight+ $box->grams < $box->max_weight)
{
$possible_boxes[$i] = $box;
$i++;
}
}
return collect($possible_boxes);
}
This gives me a collection with boxes that can carry my items.
Now I should check if the ID of the box selected by the customer exists. If it does not exist, it will pick the first valid one.
This is where I am stuck. I have tried to use puck:
public function someotherfunction(){
...
$boxes = get_possible_boxes($something,$number);
$valid_box = $boxes->where("id", $request->selected_box)->pluck("id");
if(!$valid_box){
$valid_box = $boxes[0]
}
...
This works if the selected box cannot be used. The function pluck only gives me the id, clearly it is not the function I am looking for and I have already read the Laravel documentation.
So the question is, how do I get the correct eloquent model ?
You're looking for the first() method.
$valid_box = $boxes->where("id", $request->selected_box)->first();
Alternatively, if you rework your get_possible_boxes() method to return a Illuminate\Database\Eloquent\Collection instead of a plain Illuminate\Support\Collection, you could use the find() method, like so:
Function:
public static function get_possible_boxes($article,$quantity)
{
$total_weight = $article->grams * $quantity;
$boxes = Boxes::all()->filter(function ($box) use ($total_weight) {
return $total_weight + $box->grams < $box->max_weight;
});
return $boxes;
}
Find:
$boxes = get_possible_boxes($something, $number);
$valid_box = $boxes->find($request->selected_box) ?? $boxes->first();
And you could probably squeeze out a little more performance by adding the weight condition as part of the SQL query instead of filtering the collection after you've returned all the boxes, but I left that up to you.
What you want is probably filter.
$valid_box = $boxes->filter(function($box) use ($request){
return $box->id == $request->selected_box;
});
if($valid_box)...
I should note that if you don't want $valid_box to be a collection, you can use first instead of filter in the exact same way to only get the object back.
It could be done in many ways but I would rather use the following approach:
$boxes = get_possible_boxes($something,$number)->keyBy('id');
$valid_box = $boxes->get($request->selected_box) ?: $boxes->first();
I'm using Laravel 5.3 to build an API and I have an model for products. Whenever I retrieve a product, I want to retrieve the product's rating and it's recommended rate. I also have a model for reviews and products have many reviews.
$product = Product::where('slug', $slug)->with('reviews')->first()->toArray();
Rating is computed by looping through $product->reviews in the controller, adding up the score of each review, then dividing it by the total number of reviews.
if (count($product['reviews']) > 0) {
$i = 0;
$totalScore = 0;
foreach ($product['reviews'] as $review) {
$totalScore = $totalScore + $review['Rating'];
$i++;
}
$product['averageReviewRating'] = $totalScore / $i;
} else {
$product['averageReviewRating'] = null;
}
Recommended rate is computed with a SQL query.
$product['recommendedRate'] = round(DB::select("
select ( count ( if (Recommend = 1,1,NULL) ) / count(*)) * 100 as rate
from Review where PrintProduct_idPrintProduct = " . $product['idPrintProduct']
)[0]->rate);
This leaves me with $product['averageReviewRating'] and $product['recommendedRate'] with the data I want but seems very sloppy. I would like to just be able to do something similar to this below and have those two values assigned to each object of a collection, than access them via $product->averageReviewRating and $product->recommendedRate or even not include them in with and have those values eagerly assigned.
$product = Product::where('slug', $slug)->with(['Reviews', 'RecommendedRate', 'AverageReviewRating'])->first();
Anyone know a way to do this with ORM? I've looked high and low and have not found anything.
You can do this way
protected $appends = [
'reviews',
'recommendedRate',
'averageReviewRating'
];
public function getReviewsAttribute() {
return $this->reviews()->get();
}
public function getRecommendedRateAttribute() {
if (count($this->reviews) > 0) {
$i = 0;
$totalScore = 0;
foreach ($this->reviews as $review) {
$totalScore = $totalScore + $review->Rating;
$i++;
}
return $totalScore / $i;
} else {
return null;
}
}
public function getAverageReviewRatingAttribute() {
return round(DB::select("
select ( count ( if (Recommend = 1,1,NULL) ) / count(*)) * 100 as rate
from Review where PrintProduct_idPrintProduct = " . $this->idPrintProduct
)[0]->rate);
}
then simply call Product::where('slug', $slug)->first()->toArray();
P.S. This is just the way you can do, I might miss part of logic or names..
The way to get the sum in Laravel Eloquent is using the Aggregate sum and for average avg
https://laravel.com/docs/5.4/queries#aggregates
If you want to add a custom property to your model for that, you can use
class Product {
function __construct() {
$this->{'sum'} = DB::table('product')->sum();
$this->{'avg'} = DB::table('product')->avg();
}
}
edit: to set the attributes, you can use the built in function https://github.com/illuminate/database/blob/v4.2.17/Eloquent/Model.php#L2551
I'm having a simple hasMany relation between order and orderItems. What I'm trying to do is fetch the count of similar order items. This is what I've done:
$orderItems = $order->orderItems();
$distinctItems = $orderItems->groupBy('item_name')->distinct('item_name')->get();
foreach($distinctItems as $i){
$i['count'] = $orderItems->where('item_name', '=', $i->item_name)->count();
}
$order['items'] = $distinctItems;
However the count is returned for only first order Item and 0 for other items. I'm not sure why this is happening. I've also checked that where() returns null for the items except the first one.
Thanks for the help.
try using the collection only, first groupBy item_name and then on each item add count and return new collection which would look something like
$orderItems = $order->orderItems()->get();
$order['items'] = $orderItems->groupBy('item_name')->map(function($item) {
$i = $item->first();
$i->count = $item->count();
return $i;
});
Try this code.
$cnt = array();
foreach($distinctItems as $i){
$cnt[$i]['count'] = $orderItems->where('item_name', '=', $i->item_name)->count();
}
print_r($cnt);