I have developed a custom search engine for our Magento store and I am trying to load the product collection in a very specific order (I have ranked the results according to an algorithm I designed).
I can load the product collection correctly, however it is not in the order that I would like it to be in. Here is basically how it is working now:
My database query basically comes back with a PHP array of product IDs. For this example lets say it looks like this:
$entity_ids = array(140452, 38601 );
Now I can transpose the 140452 and the 38601 and the product collection comes back in the same order each time. I would like the product collection to be in the same order as the ID of the entity ids.
The code I am using to create my collection is as follows:
$products = Mage::getModel('catalog/product')
->getCollection()
->addAttributeToSelect('*')
->addAttributeToFilter('entity_id', array('in' => $entity_ids))
->setPageSize($results_per_page)
->setCurPage($current_page)
->load();
Is there a way to set the sort order to be the order of the $entity_ids array?
Collections inherit from the class
Varien_Data_Collection_Db
There's a method named addOrder on that class.
public function addOrder($field, $direction = self::SORT_ORDER_DESC)
{
return $this->_setOrder($field, $direction);
}
So, you'd think something like this should work for basic ordering
Mage::getModel('catalog/product')
->getCollection()
->addAttributeToSelect('*')
->addOrder('entity_id');
However, it doesn't. Because of the complex joining involved in EAV Collections, there's a special method used to add an attribute to the order clause
Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection::addAttributeToSort
However again, this can only be used to add simple attributes. To create an arbitrary sort, you'll need to manipulate the Zend_Select object directly. I'm not a big fan of this, and I'm not a big fan of using custom mysql functions to achieve things, but it appears it's the only way to do this
I tested the following code on a stock install and got the desired results. You should be able to use it to get what you want.
$ids = array(16,18,17,19);
$products = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('*')
->addAttributeToFilter('entity_id',$ids);
//shakes fist at PDO's array parameter
$ids = array_map('intval', $ids);
$products->getSelect()->order("find_in_set(e.entity_id,'".implode(',',$ids)."')");
foreach($products as $product)
{
var_dump($product->getEntityId());
var_dump($product->getSku());
}
There is no way to sort arbitrarily in SQL so you would have to sort the results in PHP afterwards. Then the bigger problem is you are using page sizing to limit the number of results being returned, some of the records you want might not be returned because of this.
The better solution is to add an attribute to products which you can then use to sort by. Products in categories already have a 'position' value which is used in this way. Then you only need to use the addOrder()addAttributeToSort() method that Alan suggested but with your custom attribute.
(Explanation is hurried, let me know if not clear enough)
Related
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();
Given a product id, I can query the product using
Mage::getModel('catalog/product')->load($id);
What I have is a list of ids (comma separated), I can explode it, loop through each id, and run load($id) like above. I am concern a bit about the performance. Is this a different way to handle it, something like where clause, with an IN(id1,id2,id3,id4) kind of syntax. I google around, and I see this
Mage::getModel('catalog/product')->getCollection()->addAtributeToSelect('*')
I think I can add a filter to this, right? Had anyone solve a similar problem? Thank you very much.
1) Filter your collection using Product Ids you have :
$productIds = explode(',', "1,2,3,4,5,6");
$collection = Mage::getModel('catalog/product')->getCollection()-
>addAttributeToFilter('entity_id', array('in' => $productIds));
2) If you want to retrive only specific information like name & sku etc, you can add attribute to select, this means collection will only fetch the name from database tables, rather than whole product information, you can select with below code
$collection->addAttributeToSelect(array('name','sku'));
3) Make Sure All this code is written in blocks or models and not in Phtmls, or else it can definitely affect the page speed.
As par r requirement you can use finset function of magento which accepts array as parameter
Try to use addAttributeToFilter with or condition
$collection->addAttributeToFilter($attribute,
array(
array('finset'=> array('237')),
array('finset'=> array('238')),
array('finset'=> array('239')),
)
);
Or
$collection->addAttributeToFilter(
array(
array('attribute'=> 'attributecode','finset' => array('237')),
array('attribute'=> 'attributecode','finset' => array('237')),
array('attribute'=> 'attributecode','finset' => array('237')),
)
);
I'm manually building up a report in Magento using collections and I'm trying to make this use the order completion date instead of the order created date.
My current code is:
$orders = Mage::getModel('sales/order')->getCollection()
->addAttributeToFilter('created_at', array('from'=>$fromDate, 'to'=>$toDate))
->addAttributeToFilter('status', array('eq' => Mage_Sales_Model_Order::STATE_COMPLETE))
->addAttributeToFilter('store_id', array('eq' => 2));
I've found the following question where the user suggest manually logging all of the status changes for each order manually.
Finding out when an order status has been set to completed
As I'm wanting to use this in a collection I don't believe this is a suitable way, also I somewhat expect this sort of information to be accessible in Magento.
Thanks,
You can retrieve a collection of the order-status-history updates:
$collection = Mage::getResourceModel('sales/order_status_history_collection')
->addAttributeToSelect('created_at', 'parent_id')
->addAttributeToFilter('status', array('eq'=>'complete'))
->load();
After which you can iterate the $collection and extract the parent_id and the created_at for the information you want.
Hiho everybody! I hope you'll give me a clue about this because I'm still noob with Magento.
I try to display a list of products I get in an array. In Mage/Catalog/Block/Product/List.php, I created a new Varien_Data_Collection() in which I pushed my products objects (with ->addItem($product)).
Then I return my custom collection and List.php class does his work with it to display the list of products.
When I call the page in my browser, I had the right number of displayed products and when I click on it to see the product page, I get the right page.
However, all the data (like the product name, the price, etc) are empty. I guess that the methods used by List class to catch these data fail with my Varien_Data_Collection object.
To illustrate, here is my code sample :
// getting a particular product
$mainProduct = Mage::getModel('catalog/category')->load($currentCat->getId());
$mainProduct = $mainProduct->getProductCollection();
$mainProduct = $mainProduct->addAttributeToFilter('product_id', $_GET['cat_id']);
// creating a custom collection
$myCollection = new Varien_Data_Collection();
foreach ($mainProduct as $product) {
// getting my particular product's related products in an array
$related = $product->getRelatedProductIds();
if ($this->array_contains($related, $_GET['cat_id'])) {
// if it suits me, add it in my custom collection
$myCollection->addItem($product);
}
}
return $myCollection;
And this is what I get in my list page :
When I var_dump($myCollection), I can see that ['name'], ['price'], etc fields are not referenced. Only ['product_id'] and many other fields I don't care about.
My very ultimate question is : how can I return a collection containing these products data to my List class ? I know that it is poorly explained but my English is very limited and I try to do my best :(
Calling ->getProductCollection() against a category only returns skeleton data for each product in the created collection. If you want full data for each of the products, you need to then load them, so in your foreach loop you would have:
$product = Mage::getModel('catalog/product')->load($product->getId());
However the way in which you are building the collection is not the best working practice - you should never have to create your own Varien_Data_Collection object, instead you should be creating a product collection as follows:
$collection = Mage::getModel('catalog/product')->getCollection();
Then before you load the collecion (which the foreach loop or calling ->load() against the collection will do as 2 examples), you can filter the collection according to your requirements. You can either do this using native Magento methods, one of which you are already using (addAttributeToFilter()) or I prefer to pull the select object from the collection and apply filtering this way:
$select = $collection->getSelect();
You can then run all of the Zend_Db_Select class methods against this select object to filter the collection.
http://framework.zend.com/manual/1.12/en/zend.db.select.html
When the collection has been loaded, the products inside it will then contain full product data.
first of all pelase do not use $_GET variable, use Mage::app()->getRequest()->getParams();
second why not try to build your collection correctly from the start?
here is what your code does:
$mainProduct = Mage::getModel('catalog/category')->load($currentCat->getId());
$mainProduct = $mainProduct->getProductCollection();
$mainProduct = $mainProduct->addAttributeToFilter('product_id', $_GET['cat_id']);
get one product, I mean you load a category then load a product collection, then filter by product id.. why not:
$mainProduct = Mage::getModel('catalog/product')->load($yourSearchedId);
I aslo do not see why you filter products by $_GET['cat_id'] which looks like a category id...
To conclude you can get more help if you explain exactly what you are trying to find. It looks like you are trying to find all products that have a given product as related. So why not set for that given product the related product correctly and get the related products collection.
$_product->getRelatedProductCollection();
UPDATE:
now that you cleared your request try this:
$relatedIds = $product->getRelatedProductIds();
$myCollection = Mage::getModel('catalog/category')
->load($currentCat->getId())
->getProductCollection();
$myCollection->addAttributeToFilter('product_id', array("in",$relatedIds));
//also addAttributeToSelect all attributes you may need like name etc
$myCollection->load(); //maybe you don't actualy need to load here
Please bear in mind I did not test this code it was written from teh top of my head, test it. But I hope you got the idea.
I hope someone can help me puzzle this one out. I'm trying to load some data out of a Magento catalog model using a collection. The code looks like this:
$model = Mage::getModel('catalog/product');
$collection = $model->getCollection();
$collection->addAttributeToSelect('short_description');
$collection->addFieldToFilter('SKU',array('like' => array('%EBOOK%')));
$collection->load();
var_dump($collection->getData());
This produces a dump of objects with all the fields in the flat catalog product table, but not the field that I have requested with the $collection->addAttributeToSelect() method. No matter what field I specify with this method (even '*'), I cannot get the collection to return anything other than its standard set of fields. I also can't unset any fields using $collection->removeFieldFromSelect(NULL) which is supposed to work.
Am I doing something stupid/wrong/both?
Thanks in advance.
This is because you call getData() on the collection, but not on a product of this collection.
I never really analyzed why this happens, but if you use
foreach ($collection as $product) {
var_dump($product->getData());
}
instead of
$collection->load();
var_dump($collection->getData());
you'll get the data you're expecting.