Collections in Magento are extremely useful, they allow you to retrieve objects such as products, categories, customers, etc. Retrieving these objects in a collection is a simple case of instantiating an instance of the object entity's factory class and using the model's 'getCollection' method, as follows.
Here is your Block class's constructor, where we declare the object model's factory (in our case products):
public function __construct(
...
\Magento\Catalog\Model\ProductFactory $productFactory,
...
)
{
...
$this->_productFactory = $productFactory;
...
}
And then further down the class, we have created a method where we are retrieving the collection of product objects, which in this example will in-turn be used in our PHTML template:
public function getCustomProductCollection()
{
$products = $this->_productFactory->create()->getCollection()
->addAttributeToSelect('*');
return $products;
}
Now in our PHTML template, we will of course have access to the above method as follows:
$productCollection = $block->getCustomProductCollection();
Which we can loop through and do with as we please, for example:
foreach($productCollection as $product) {
echo "<h2>" . $product->getName() . "</h2>";
echo "<h3>" . $product->getSku() . "</h3>";
}
Loading a full collection as above is fine assuming you want the entire product collection and all its bells and whistles. But what if you ONLY wanted to retrieve products that match certain criteria? An example of this is if you wanted to only get products whose names begin with a string of characters?
Quite frequently, we see PHTML doing extra leg-work which should be done in the class it's inheriting from, eg:
foreach($productCollection as $product) {
if(strpos($product->getName(), 'test') !== false) {
echo "<h2>" . $product->getName() . "</h2>";
}
}
The above is very inefficient, as you are iterating through each product in the loop before you conduct your "if" clause. Also, it makes it more difficult than necessary to ascertain if there are any eligible products in the first place and add a message saying something along the lines of "Sorry, no products have the word 'test' in their names".
This is where we can leverage our getCustomProductCollection method to do the heavy-lifting for us. We can use addFieldToFilter on a collection of objects to filter the ones we do (or do not) in our collection. This is how we could re-do the above:
public function getCustomProductCollection()
{
$products = $this->_productFactory->create()->getCollection()
->addAttributeToSelect('*')
->addFieldToFilter('name', array('like' => '%test%'));
return $products;
}
NB: on a sidenote, we can also use addAttributeToFilter in the above example as we are grabbing all available attributes in our objects anyway.
Now that we've enhanced our collection method, we can streamline our PHTML as follows:
$productCollection = $block->getCustomProductCollection();
if($productCollection->getSize()) {
foreach($productCollection as $product) {
echo "<h2>" . $product->getName() . "</h2>";
}
} else {
echo __("Sorry, no products have the word 'test' in their names");
}
As you can see, because we've already filtered the products we want, we can immediately check whether the collection is empty or not using getSize - which is a core method all collections have access to.
Happy coding!