Get Product Collection Containing In-Stock Products Only

To answer the question. You would use this (not very different than your version):

$productCollection = Mage::getResourceModel('catalog/product_collection')
                        ->addAttributeToSelect(array('name', 'image', 'price'))
                        ->addAttributeToFilter('status', array('eq' => Mage_Catalog_Model_Product_Status::STATUS_ENABLED));

Mage::getSingleton('cataloginventory/stock')
    ->addInStockFilterToCollection($productCollection);

$this->_productCollection = $productCollection;

But I don't know exactly what causes your error, because it seems related to the fact that the getSubmitUrl/getAddToCartUrl product param is boolean false, and not to the collection per se.


Perhaps a day late and a dollar short, but instead of doing all this second-step work, you can simply join the cataloginventorystock_stock_status table to your collection select():

$collection = Mage::getModel('catalog/product')->getCollection();
$collection->getSelect()->join('cataloginventory_stock_status', 'cataloginventory_stock_status.product_id = e.entity_id', array('stock_status'));
$collection->getSelect()->where("`cataloginventory_stock_status`.`stock_status` = 1");

You can do this in conjunction with additional join() or where() clauses. Hope it helps you in the right direction.


Newermind answered the original question correctly; my thanks to him. For future adventurers, here's what was wrong with my code. Special thanks to Melvyn for his help.

Problem

From my code in the question, $this->_productCollection contains an object. When we iterate over that object in the view, the $_product variable is an array.

$_products = $this->_productCollection;

foreach ( $_products as $_product ) {
    echo gettype( $_product ); // <-- returns type [array]
}

The method at app/code/core/Mage/Catalog/Block/Product/Abstract.php:117 (ultimately) calls getTypeInstance(). That method is expecting an object, specifically an instance of Mage_Catalog_Model_Product.

In the view, I was calling $this->getSubmitUrl($_product) inside the foreach loop:

$_products = $this->_productCollection;

foreach ( $_products as $_product ) {
    echo $this->getSubmitUrl( $_product );
}

getSubmitUrl() is a method of Mage_Catalog_Block_Product_Abstract, line 115. On line 123 it calls getAddToCartUrl(), which calls getTypeInstance() and that is expecting an object. Problem was, I was handing it an array.

Simple OOP principle, but I guess I was too tired to see it. Thx all for the help.

The Solution

So, unfortunately the addInStockFilterToCollection method returns an inventory stock object - not what I was after. I need a product collection of in-stock products.

Also, the addInStockFilterToCollection method only works with a Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Link_Product_Collection collection.

So, how to get the collection? My approach was as follows:

  1. Get a collection of in-stock products.
  2. Create an ID array from the result set.
  3. Get the product collection, using the ID's from the in-stock results.

Here's the code:

$inStockProductIds = array();

$inStockCollection = Mage::getModel('cataloginventory/stock')
    ->getItemCollection()
    ->addFieldToFilter('is_in_stock', 
        array( 'eq' => 1 )
    )
    ->addFieldToFilter( 'entity_id', 
        array( 'in' => array ('1', '2') )
    )
;

if ( $inStockCollection) {
    foreach ($inStockCollection as $stockProduct) {
        $inStockProductIds[] = $stockProduct->getId();
    }
}

$this->_productCollection = Mage::getModel('catalog/product')
    ->getCollection()
    ->addAttributeToSelect(
        array('name', 'image', 'price')
    )
    ->addIdFilter( $inStockProductIds )
    ->setPageSize( 2 )
    ->load();
;

return $this->_productCollection;

That returns a product collection, only containing in-stock products. It's a bit heavy, but it works. I'd appreciate any improvements on this. Thanks.