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.
From what I understand you want to send 2 or more values and filter by them using OR
.
Something like this:
...
WHERE
FIND_IN_SET($v1, `some_field`) OR
FIND_IN_SET($v2, `some_field`) OR
...
$v1 & $v2
are the values of your checkboxes
For that you need to pass to the addAttributeToFilter
and array of arrays.
->addAttributeToFilter(
array(
array('attribute'=>'some_attribute', 'finset'=>$v1),
array('attribute'=>'some_attribute', 'finset'=>$v2),
)
);
Here is a possible implementation.
$data = THE ARRAY OF THE CHECKBOX VALUES;
$filter = array();
foreach ($data as $value) {
$filter[] = array(
'attribute' => $attribute->getAttributeCode(),
'finset' => $value
);
}
if (count($filter) > 0) {
$collection->addAttributeToFilter($filter);
}
Best Answer
Doesn't seem that hard. Basically you would have to instantiate the customer collection, join it with the sales collections and apply some conditions. The sales table has most of the information you'll need except for the dob.
I haven't tested it but this is how I would write it. Be sure to enable SQL logging in your Magento and adjust the collection according to the SQL result.
To format the dob as you wanted, I suggest using a renderer.
If you have any questions, feel free to ask.