I have the following collection that helps me implement a custom report:
class VMR_Customreports_Model_Resource_Productsordered_Collection extends Mage_Reports_Model_Resource_Order_Collection
{
public function __construct()
{
parent::__construct();
$this->_init('sales/order_item');
}
public function setDateRange($from, $to)
{
$this->_reset();
$this->getSelect()
->joinInner(array('OI' => $this->getTable('sales/order_item')), 'OI.order_id = main_table.entity_id', NULL) //AND i.store_id = main_table.store_id
->where("main_table.created_at BETWEEN '".$from."' AND '".$to."'")
->where('main_table.status != \'canceled\'')
->where('main_table.customer_email NOT LIKE \'%@v2cigs.%\'')
->where('OI.parent_item_id IS NULL')
->columns(array(
'myname' => 'OI.name',
'ordered_qty' => 'sum(OI.qty_ordered)',
'ordered_money' => 'sum(OI.price*OI.qty_ordered)'))
->group('OI.name');
return $this;
}
public function setStoreIds($storeIds)
{
if ($storeIds)
$this->addAttributeToFilter('main_table.store_id', array('in' => (array)$storeIds));
return $this;
}
}
The above actually appears to work AFAICT yet I dont understand why I have to include the constructor; In fact I would have expected that contstructor's _init() call to break my collection because I'm assuming that main_table here is sales/order not sales/order_item and AFAIK that _init() call mainly sets $this->_model to what I tell it to. When I don't include it I get, 'Item (Nostress_Pdfprintouts_Model_Order) with the same id "855" already exist' which doesn't make much sense to me. Can someone tell me why I would get the above error and why my _init() call would fix it?
Edit:
One Clue: When I don't include the constructor and I log my sql queries it gags on:
SELECT `main_table`.*, `OI`.`name` AS `myname`, sum(OI.qty_ordered) AS `ordered_qty`, sum(OI.price*OI.qty_ordered) AS `ordered_money` FROM `sales_flat_order` AS `main_table`
INNER JOIN `sales_flat_order_item` AS `OI` ON OI.order_id = main_table.entity_id WHERE (main_table.created_at BETWEEN '2013-06-03 08:00:00' AND '2013-06-04 07:59:59') AND (main_table.status != 'canceled') AND (main_table.customer_email NOT LIKE '%@v2cigs.%') AND (OI.parent_item_id IS NULL) AND (main_table.store_id IN(12, 4, 3)) AND (main_table.store_id IN('12', '11', '13', '14', '4', '3')) GROUP BY `OI`.`name`
where the 3 results are all from that 2013/6/3 period grouped by name and 2 of the results share the same main_table.entity_id namely, 855. So order 855 has 2 invoices, and if I don't include that _init() it complains about that entity_id not being unique.
FYI: The models section of my config.xml
My config.xml's models section FYI:
....
<models>
<enterprise_admingws>
<rewrite>
<collections>VMR_Customreports_Model_Admingwscollections</collections>
</rewrite>
</enterprise_admingws>
<customreports>
<class>VMR_Customreports_Model</class>
<resourceModel>orders_collection</resourceModel>
<resourceModel>productsordered_collection</resourceModel>
<resourceModel>ordersbypaymenttype_collection</resourceModel>
<resourceModel>ordersbyshippingmethod_collection</resourceModel>
<resourceModel>accounting_collection</resourceModel>
</customreports>
<orders_collection>
<class>VMR_Customreports_Model_Resource</class>
</orders_collection>
<productsordered_collection>
<class>VMR_Customreports_Model_Resource</class>
</productsordered_collection>
<ordersbypaymenttype_collection>
<class>VMR_Customreports_Model_Resource</class>
</ordersbypaymenttype_collection>
<ordersbyshippingmethod_collection>
<class>VMR_Customreports_Model_Resource</class>
</ordersbyshippingmethod_collection>
<accounting_collection>
<class>VMR_Customreports_Model_Resource</class>
</accounting_collection>
</models>
.......
Best Answer
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
As for the why, you're extending
Mage_Reports_Model_Resource_Order_Collection
which already extends theMage_Sales_Model_Resource_Order_Collection
class. This later class initialized the collection with the class aliassales/order
.If you look at what the
_init
method actually does for a collectionIt 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).The
_initSelect
method sets the main table of the collection's select query.And
getMainTable
works by asking the resource model for a table name,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
As a reminder, here's what
_init
looks liksSo, you're changing the model and (more importantly) the resource model to use
sales/order_item
. However, the query'smain_table
was still set with thesales/order
resource model derived table name.This means in your custom collection, post
__construct
, anytime the Magento code callsIt will get the main table for the
sales/order_item
resource model. With your extra_init
in the constructor, calls togetMainTable
would have returned the main table for thesales/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.