The reason you're getting that error is the collection's query returns two rows with the same ID, and Magento collections are programmed to only allow one unique ID for each item
#File: lib/Varien/Data/Collection.php
public function addItem(Varien_Object $item)
{
$itemId = $this->_getItemId($item);
if (!is_null($itemId)) {
if (isset($this->_items[$itemId])) {
throw new Exception('Item ('.get_class($item).') with the same id "'.$item->getId().'" already exist');
}
$this->_items[$itemId] = $item;
} else {
$this->_addItem($item);
}
return $this;
}
As for the why, you're extending Mage_Reports_Model_Resource_Order_Collection
which already extends the Mage_Sales_Model_Resource_Order_Collection
class. This later class initialized the collection with the class alias sales/order
.
#File: app/code/core/Mage/Sales/Model/Resource/Order/Collection.php
protected function _construct()
{
$this->_init('sales/order');
$this
->addFilterToMap('entity_id', 'main_table.entity_id')
->addFilterToMap('customer_id', 'main_table.customer_id')
->addFilterToMap('quote_address_id', 'main_table.quote_address_id');
}
If you look at what the _init
method actually does for a collection
#File: app/code/core/Mage/Core/Model/Resource/Db/Collection/Abstract.php
protected function _init($model, $resourceModel = null)
{
$this->setModel($model);
if (is_null($resourceModel)) {
$resourceModel = $model;
}
$this->setResourceModel($resourceModel);
return $this;
}
It sets the model and resource model. Why is this important? Because the abstract db collection that most collections inherit from calls an _initSelect
method at the end of its __construct
method (which is also after _constrct
has been called).
#File: app/code/core/Mage/Core/Model/Resource/Db/Collection/Abstract.php
public function __construct($resource = null)
{
parent::__construct();
$this->_construct();
$this->_resource = $resource;
$this->setConnection($this->getResource()->getReadConnection());
$this->_initSelect();
}
The _initSelect
method sets the main table of the collection's select query.
#File: app/code/core/Mage/Core/Model/Resource/Db/Collection/Abstract.php
protected function _initSelect()
{
$this->getSelect()->from(array('main_table' => $this->getMainTable()));
return $this;
}
And getMainTable
works by asking the resource model for a table name,
public function getMainTable()
{
if ($this->_mainTable === null) {
$this->setMainTable($this->getResource()->getMainTable());
}
return $this->_mainTable;
}
...
public function getResource()
{
if (empty($this->_resource)) {
$this->_resource = Mage::getResourceModel($this->getResourceModelName());
}
return $this->_resource;
}
So, all that happens when the constructor is called, irrespective of whether you call it explicitly or not.
Now, in your code, after calling the constructor, you recall _init
public function __construct()
{
parent::__construct();
$this->_init('sales/order_item');
}
As a reminder, here's what _init
looks liks
#File: app/code/core/Mage/Core/Model/Resource/Db/Collection/Abstract.php
protected function _init($model, $resourceModel = null)
{
$this->setModel($model);
if (is_null($resourceModel)) {
$resourceModel = $model;
}
$this->setResourceModel($resourceModel);
return $this;
}
So, you're changing the model and (more importantly) the resource model to use sales/order_item
. However, the query's main_table
was still set with the sales/order
resource model derived table name.
This means in your custom collection, post __construct
, anytime the Magento code calls
$this->getMainTable();
It will get the main table for the sales/order_item
resource model. With your extra _init
in the constructor, calls to getMainTable
would have returned the main table for the sales/order
resource model. Any other query generating code that relies on resource model behavior would also act differently.
Without tracing the code further, I'm sure you can see how this creates the opportunity for subtle but significant differences in the generated queries.
Re: the question in the comments.
No, it's not a sensible thing to do. The collection system in Magento is designed to load an array of models which have one main table, and several optional support tables (either via joins or extra selects). When you call _init
a second time in the PHP __construct
method, you're creating a collection with two main tables, and while that might allow you (or your tutorial writer) a way to quickly and cleverly implement a feature, it seems bound to cause confusion, and possible fall afoul of a future update.
Also, you're already heading into not-sensible territory since you're extending a class which already extends a concrete Magento collection. While this is standard OOP, it does fall outside general usage patterns, which means you're on your own when it comes to tracking down weird bugs or behavior you don't understand.
Like anything though, it's all about time/benefit ratios and tradeoffs. Sensible for one team isn't sensible for a different team.
Best Answer
I am assuming that you know how to get id as you said , here is logic :
1- Initialize an array for ex: $productids[] = $product->getId().
2- Store all product id into single array (you can also use array merge if you have id in different array's).
3- Then array_unique($productid);
create model class for product collection for example: app/code/Vendor/ModuleName/Model/Product.php