Home > Magento > Showing all reviews and ratings on a page in Magento

Showing all reviews and ratings on a page in Magento

August 6th, 2009

Finally, a new post, and at last it is about Mangeto Commerce (http://www.magentocommerce.com/). In my first Magento how-to you’ll learn how to retrieve all product reviews and show them on a single page, together with the average rating. For this, I assume you have already created a new module and are able to view the page. You’ll only need to manipulate a block and a template.

First, let’s retrieve the reviews collection (this method will go into the block):

	function getReviews() {
		$reviews = Mage::getModel('review/review')->getResourceCollection();
		$reviews->addStoreFilter( Mage::app()->getStore()->getId() )
						->addStatusFilter( Mage_Review_Model_Review::STATUS_APPROVED )
						->setDateOrder()
						->addRateVotes()
						->load();        
		
		return $reviews;
	}

We’re using Mage_Review_Model_Mysql4_Review_Collection, which is a resource model. First setup the collection, filtering by store – we only want to retrieve the product reviews in the current store -, by status – show only approved reviews -, and ordering by date in reverse order, then load the collection.

addRateVotes() helps loading all the ratings/votes for that review. We’re gonna use this collection to compute the average rating.

Next, let’s move on to the template for a second. We’re gonna call the getReviews() method, then iterate through all the reviews. For each review you would probably want to display the title, nickname, date and details, but also the product associated and the user rating. For the first four, things are pretty easy, all you have to do is call getTitle(), getNickname(), getDetail(), getCreatedAt() on each review object.

To display the product name & link, we need to retrieve the product associated with each review – unfortunately I wasn’t able to find a way to join the product tables inside the query for retrieving all the reviews. So, we need to create a helper method inside our block, called getProduct(). We’re gonna use a storage/registry variable called _loadedProducts, so that we avoid loading the same product multiple times.

	function getProduct( Mage_Review_Model_Review $review ) {
		if( !isset($this->_loadedProducts[ $review->getEntityPkValue() ]) ) {
			$this->_loadedProducts[$review->getEntityPkValue()] = Mage::getModel('catalog/product')->load( $review->getEntityPkValue() );
		}
		
		return $this->_loadedProducts[ $review->getEntityPkValue() ];
	}

And inside the template:

<?php $_prod = $this->getProduct( $review ); ?>
<a href="<?php echo $_prod->getProductUrl(); ?>"><?php echo $_prod->getName(); ?></a>

One last thing, if you intend to display the average rating of each review, add another helper method inside the block:

	function getAverageRating( Mage_Review_Model_Review $review ) {
		$avg = 0;
		if( count($review->getRatingVotes()) ) {
			$ratings = array();
			foreach( $review->getRatingVotes() as $rating ) {
				$ratings[] = $rating->getPercent();
			}
			$avg = array_sum($ratings)/count($ratings);
		}
		
		return $avg;
	}

And then call it in the template (in this example we’re using Magento’s default styling):

<div class="rating-box">
	<div class="rating" style="width: <?php echo ceil($this->getAverageRating( $review )); ?>%;"></div>
</div>

That is all! You now have a page where all product reviews can be display.
Things to consider: pagination and cache!

Update: Seems that Magento comes prepared for reviews on products, categories and customers. We only need to load product reviews, so it would be wise to filter by entity. Unfortunately, the current version of Magento doesn’t allow filtering for a certain entity, only by entity and entity PK (which is the product ID in this case). Of course, we could write a decorator and write a method to just add a filter for entity_code = 'product', but the quickest (and dirtiest) way of doing it is by adding a check inside the template foreach loop (or adding a helper method in the block):


if( $review->getEntityId() == 1 ) { continue; }
//1 is the id of the 'product' entity - if you write a method, use a class constant

Note: This has only been tested on Magento 1.3.x – 1.5.x.

  1. Martin
    | #1

    Thank you for the above code.It was very helpful for me.

  2. Cristi
    | #2

    Not sure what images are not showing up. Could you be a little more specific?

  3. | #3

    Hello just wanted to give you a quick heads
    up and let you know a few of the pictures aren’t loading correctly. I’m not sure why but I think its a
    linking issue. I’ve tried it in two different web browsers and both show the same outcome.

  4. | #4

    Way cool! Some very valid points! I appreciate
    you penning this article plus the rest of the website is also really good.

  5. | #5

    Must have for 1.7!!

  6. Cristi
    | #6

    @Ayush & @desbest – I’ve just checked the Mage_Review_Model_Review class and getEntityPkValue() still exists and should work. Moreover, since all models inherit Varien_Object, a call like getEntityPkValue() should return null in the worst case, not throw a fatal error. Thus, my guess is that you’re not instantiating the block correctly OR calling getProduct() from the wrong place – getProduct() is the only method calling getEntityPkValue().

  7. | #7

    This is not working for me in magento 1.6.2
    Getting the following error:-
    “Fatal error: Call to a member function getEntityPkValue() on a non-object in /opt/lampp/htdocs/mageModule/app/code/local/Review/Review/Block/Review.php on line 30”
    Any one can have solution. 🙂
    Thanx

  8. Cristi
    | #8

    Yeah, I guess the structure of those tables changed or something. I haven’t checked this code against 1.7 yet, I think I’m still on a 1.5. Try figuring out how the tables changed and replace that function call with the new replacement.

  9. | #9

    This doesn’t work for me on Magento 1.7
    I get this error when I am fetching an id of an actual review.

    Fatal error: Call to a member function getEntityPkValue() on a non-object in /home/desbest/public_html/clients/magentofull/app/code/local/Desbest/Showdown/Block/Showdown.php on line 17

  10. Michael C.
    | #10

    Just wanna say thanks for posting this. It saved me from potentially hours of fruitless work. The code worked perfectly. I plan to use this to display reviews inline with a product, and also add an AJAXified rating/review widget.

  11. Cristi
    | #11

    I suppose you would like to only show the reviews of a certain list of products. You could try to filter by the product id. Quickly checking the Mage_Review_Model_Mysql4_Review_Collection class, there’s no way to do that, so I went up the class hierarchy and found the method addFieldToFilter in Varien_Data_Collection_Db. This should probably do the trick: $reviews->addFieldToFilter('entity_pk_value', array('in' => array( ... list of product ids ... )))

  12. Aknal
    | #12

    How to get reviews for group of product ids

  13. Cristi
    | #13

    i think $_prod->getName() and $_prod->getImage() (or getSmallImage()) should work fine inside that loop.

  14. | #14

    Hi Guys,

    Thank you for this post.

    However, it works displaying the reviews but I can’t get the product name and product image. Here is my php file:
    getResourceCollection();
    $reviews->addStoreFilter( Mage::app()->getStore()->getId() )
    ->addStatusFilter( Mage_Review_Model_Review::STATUS_APPROVED )
    ->setDateOrder()
    ->addRateVotes()
    ->load();

    return $reviews;
    }

    function getProduct( Mage_Review_Model_Review $review ) {
    if( !isset($this->_loadedProducts[ $review->getEntityPkValue() ]) ) {
    $this->_loadedProducts[$review->getEntityPkValue()] = Mage::getModel(‘catalog/product’)->load( $review->getEntityPkValue() );
    }

    return $this->_loadedProducts[ $review->getEntityPkValue() ];
    }

    function getAverageRating( Mage_Review_Model_Review $review ) {
    $avg = 0;
    if( count($review->getRatingVotes()) ) {
    $ratings = array();
    foreach( $review->getRatingVotes() as $rating ) {
    $ratings[] = $rating->getPercent();
    }
    $avg = array_sum($ratings)/count($ratings);
    }

    return $avg;
    }

    }

    And here is the phtml file:

    Title
    Details
    Nickname
    Date
    Product Name
    Product Image
    Avg. Rating

    getReviews() as $review) {
    $review_prod = $this->getProduct($review);
    $_prod = $this->getProduct( $review );
    ?>

    getTitle()?>
    getDetail()?>
    getNickname()?>
    getCreatedAt()?>

    <div class="rating" style="width: getAverageRating( $review )); ?>%;”>

  15. Cristi
    | #15

    @daan & @paul – Hope this helps: http://mandagreen.com/anatomy-of-a-magento-extension/

  16. Daan
    | #16

    Any update on that ZIP? I’m just beginning with magento, but would love to study the structure, I don’t know how to add a custom module, and the tutorials on other sites doesn’t get me in the right direction..

  17. | #17

    @Cristi
    I fully understand where your coming from Cristi, to prove my request wasn’t just laziness I did follow up and investigate creating blocks and templates etc. and discovered the ModuleCreator Ext. unfortunately I wouldn’t run and documentation is limited. If you can get a zip to download it will be more appreciated than you can imagine. Thanks.

  18. Cristi
    | #18

    I’ve removed the links, I think the url field should be enough to link to your website(s).
    Regarding your problem, I agree that most of the time a zip would save a lot, I also prefer to check full code too. However, in this case I didn’t have enough time to “extract” the files from the project that it was developed on.
    On the other hand, if you’re not comfortable with creating a block & template in magento, I advise hiring a pro or at least someone who knows what they’re doing. there are freelancers all over the place now (elance, odesk, rent a coder), more or less reliable and more or less expensive. i’ll see if i can zip this the following days and will leave a comment if I do.

  19. | #19

    Sounds all good, unfortunately most of it went over my head (I’m getting old and simplicity reigns supreme).

    Personally I would love to have a nice zip file to download so I could study the structure with clarity. Instructions like “You’ll only need to manipulate a block and a template.” are although simple very confusing to us whom have little tinkering knowledge within magentos’ code.

    Before I spend some hard earned on a widget I wonder if any indian givers can put me out of my misery and send the code for me to hack about.

  20. | #20

    Very interesting input. 🙂

  21. | #21

    was hoping to find this content here for a long time! cheers mate!

  22. | #22

    thanks for the suggestion.

  23. Cristi
    | #23

    I would try rewriting some parts of the Reviews module and adding some checkpoints like isLoggedIn, hasPreviousReview(customer ID or email), hasOrder(customer ID or email). It requires some coding, but I don’t see why it wouldn’t work…

  24. Andy
    | #24

    Hi,
    Thanks for this post helped me alot.
    Has anyone here been able to limit the reviews made by customers. i.e. they can only review a product once and also he needs to have purchased/ordered that product before he can actually review/rate it.

    Any help on this would be highly appreciated.

    Cheers

  25. Cristi
    | #25

    Try using Mage_Page_Block_Html_Pager – its purpose is to paginate collections. You’ll have to write some code in your controller to pass the page (and maybe limit too) to the block and/or model.
    Magento Collections have a cool feature – it comes with two methods: getSize() and count(). The first one returns the total number of items while the second one returns the number of items withing the current collection (after limit) 😉 This is useful for pagination, although the class I mentioned does all the work for you.

  26. Raul
    | #26

    Thanks for your reply…

    /*
    $toolbar = this->getLayout()->createBlock(‘catalog/product_list_toolbar’, microtime());
    $toolbar->setCollection($yourCollection);
    echo $toolbar->toHtml();
    */

    I have used the above code and got the pagination tool bar, but still want the paramater to be passed to the reviews collection so that it works….Right now its displaying all the reviews with the pagination toolbar..

    Might be some parameter need to be passed to he review collection so that it works and shows records according to pagination toolbar…

  27. Cristi
    | #27

    Thanks Raul – glad it was useful. Regarding pagination, I must admin I haven’t tried it yet, but off the top of my head I’d say there are a couple of ways of doing it:
    – using the limit() method which applied to all Varien_Collection_Db
    – using some Paginator class – I know there are classes for pagination (including the default one, shipped with ZF)
    … and it just hit me, maybe it also works by using the toolbar classes (catalog product list toolbar)

  28. Raul
    | #28

    Hello firewizard,
    Thanks for the above code.It was very helpful.But my concern is regarding the pagination. I have tried it from my side.No success yet.If you have any solution please let me know.

    Thanks

  29. Cristi
    | #29

    To define a class attribute you should just write something like protected $_loadedProducts; in your class declaration (in app/code/core/Mage/Catalog/Block/Product/View.php).

    The warning you receive happens when the argument supplied to foreach is not an array (could be null, int, string, float, but not an array).

  30. neluz19
    | #30

    @Blake
    Hi,

    I’ve pasted the code of Blake in app/code/core/Mage/Catalog/Block/Product/View.php and I saw in the post above that I have to define $_loadedProducts. Unfortunately I don’t know how to do this.

    A sample of my error:
    Warning: Invalid argument supplied for foreach() in /var/www/vhosts/***.nl/subdomains/***/httpdocs/app/design/frontend/default/default/template/page/html/***.phtml on line 2

    Line 2 in this file is this part:

    Is there anyone who can help me with this problem?

    Thanks in advance!

  31. | #31

    That was it – don’t know why I didnt see that before – my bad. Thanks again for your help.

  32. Cristi
    | #32

    I think I’ve reproduced the issue – try defining $_loadedProducts in AP_CustReview_Block_View – ideally as protected or private.

  33. | #33

    I did google that with no success. Here is my code.

    View.php (block):

    <?php class AP_CustReview_Block_View extends Mage_Core_Block_Template
    {
    	function getReviews() {
    	  $reviews = Mage::getModel('review/review')->getResourceCollection();
    	  $reviews->addStoreFilter( Mage::app()->getStore()->getId() )
    	          ->addStatusFilter( Mage_Review_Model_Review::STATUS_APPROVED )
    	          ->setDateOrder()
    	          ->addRateVotes()
    	          ->load();       
    	 
    	  return $reviews;
    	}
    	function getProduct( Mage_Review_Model_Review $review ) {
    	  if( !isset($this->_loadedProducts[ $review->getEntityPkValue() ]) ) {
    	    $this->_loadedProducts[$review->getEntityPkValue()] = Mage::getModel('catalog/product')->load($review->getEntityPkValue());
    	  }	 
    	  return $this->_loadedProducts[$review->getEntityPkValue()];
    	}
    }
    ?>
    

    view.phtml (template):

    <?php $reviews = $this->getReviews() ?>
    <?php foreach ($reviews as $review) { ?>
    	<?php if( $review->getEntityId() == 1 ) { ?>
    		<?php $_prod = $this->getProduct( $review ); ?>
    		<a href="<?php echo $_prod->getProductUrl(); ?>"><?php echo $_prod->getName(); ?></a>
    <?php }
    } ?>
    

    I am using a cms page to call everything in. I know the mod setup works – already tested that. Once I have a review on the site, it breaks and gives me that error I sent in my last post. Thank you for your help.

  34. Cristi
    | #34

    Have you tried googling for that notice? It’s a PHP error, not Magento.
    It’s hard to say what the cause of your problem is without having the full code and its purpose on my screen. See if this helps

  35. | #35

    I am trying to install this but I am getting an error (see below). I think I am real close, so any help is much appreciated. AP_CustReview_Block_View is my mod obviously – seems to be an issue with the _loadedProducts var:

    Notice: Indirect modification of overloaded property AP_CustReview_Block_View::$_loadedProducts has no effect in …

  1. No trackbacks yet.