Installation:
composer require cymo/entity-rating-bundle
Add the bundle to config/bundles.php (Symfony 4+) or AppKernel.php (Symfony 3):
Cymo\Bundle\EntityRatingBundle\CymoEntityRatingBundle::class => ['all' => true],
Import Routes:
Add to config/routes.yaml:
cymo_entity_rating:
resource: "@CymoEntityRatingBundle/Resources/config/routing.yml"
prefix: /rating
First Use Case:
Extend BaseEntityRate for your rating entity (e.g., ProductRate, ArticleRate):
use Cymo\Bundle\EntityRatingBundle\Entity\EntityRate as BaseEntityRate;
#[ORM\Entity(repositoryClass: CymoEntityRatingBundleRepository::class)]
class ProductRate extends BaseEntityRate
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
private ?int $id = null;
}
Annotate Your Entity:
Add the #[Rateable] annotation to any entity you want to rate (e.g., Product):
use Cymo\Bundle\EntityRatingBundle\Annotation\Rateable;
#[Rateable]
#[ORM\Entity]
class Product { ... }
Run Migrations:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
Rating Submission:
Use the RatingService to submit ratings via the API or form:
$ratingService = $this->container->get('cymo_entity_rating.rating_service');
$rating = $ratingService->rateEntity(
$product, // Your rated entity (must be #[Rateable])
$user, // User submitting the rating
4, // Rating value (e.g., 1-5)
'Great product!' // Optional comment
);
$entityManager->persist($rating);
$entityManager->flush();
Displaying Ratings: Fetch ratings for an entity via its repository:
$ratings = $product->getRatings(); // Assumes $product is #[Rateable]
$average = $ratingService->getAverageRating($product);
Frontend Integration: Use the Twig extension to render ratings in templates:
{% for rating in product.getRatings() %}
{{ rating.value }}/5 - {{ rating.comment }}
{% endfor %}
{{ cymo_entity_rating.getAverageRating(product) }} ({{ product.getRatings().count() }} votes)
Custom Rating Logic:
Extend RatingService to add business rules (e.g., prevent duplicate ratings):
class CustomRatingService extends RatingService
{
public function rateEntity($entity, User $user, $value, $comment = null)
{
if ($this->hasRatedAlready($entity, $user)) {
throw new \RuntimeException('User already rated this entity.');
}
return parent::rateEntity($entity, $user, $value, $comment);
}
private function hasRatedAlready($entity, User $user): bool
{
return $this->entityManager
->getRepository(EntityRate::class)
->countBy(['entity' => $entity, 'user' => $user]) > 0;
}
}
Register the service in services.yaml:
services:
App\Service\CustomRatingService: ~
Cymo\Bundle\EntityRatingBundle\CymoEntityRatingBundle:
arguments:
$ratingService: '@App\Service\CustomRatingService'
Event-Driven Extensions: Listen to rating events to trigger actions (e.g., notifications):
// src/EventListener/RatingListener.php
class RatingListener
{
public function onRatingAdded(RatingEvent $event)
{
$rating = $event->getRating();
// Send email, update analytics, etc.
}
}
Register the listener in services.yaml:
services:
App\EventListener\RatingListener:
tags:
- { name: kernel.event_listener, event: cymo_entity_rating.rating_added, method: onRatingAdded }
API Endpoints:
Use Symfony’s AbstractController to expose rating endpoints:
#[Route('/api/products/{id}/rate', name: 'api_rate_product', methods: ['POST'])]
public function rateProduct(Product $product, Request $request, RatingService $ratingService): JsonResponse
{
$data = json_decode($request->getContent(), true);
$rating = $ratingService->rateEntity($product, $this->getUser(), $data['value'], $data['comment'] ?? null);
return $this->json(['success' => true, 'rating' => $rating]);
}
Bulk Rating Updates: Use Doctrine batch operations for performance:
$conn = $entityManager->getConnection();
$conn->executeStatement(
'UPDATE entity_rate SET value = :value WHERE entity_id = :entityId AND user_id = :userId',
['value' => 5, 'entityId' => $product->getId(), 'userId' => $user->getId()]
);
Annotation Misconfiguration:
#[Rateable] to your entity will cause getRatings() to return null.composer dump-autoload and clearing cache (php bin/console cache:clear).Circular Dependencies:
EntityRate extends BaseEntityRate but doesn’t properly map the entity and user fields, ratings won’t persist.BaseEntityRate fields are correctly mapped in your child class:
#[ORM\ManyToOne(targetEntity: Product::class, inversedBy: "ratings")]
private ?Product $entity = null;
#[ORM\ManyToOne(targetEntity: User::class)]
private ?User $user = null;
Permission Issues:
RatingService to handle this:
public function rateEntity($entity, $user = null, $value, $comment = null)
{
$user = $user ?: $this->getAnonymousUser(); // Custom logic
// ...
}
Performance with Large Datasets:
getRatings() can be slow. Use pagination:
$ratings = $product->getRatings()->setMaxResults(10)->getQuery()->getResult();
Doctrine Proxy Conflicts:
EntityRate is not properly initialized, you may encounter proxy errors.EntityRate class is fully hydrated before use:
$rating = $entityManager->find(EntityRate::class, $id);
if (!$rating) {
throw new \RuntimeException('Rating not found.');
}
Enable SQL Logging:
Add to config/packages/dev/doctrine.yaml:
doctrine:
dbal:
logging: true
profiling: true
Check logs for SQL queries to verify rating persistence.
Check Event Dispatching: Add a debug listener to verify events are fired:
public function onKernelRequest(GetResponseEvent $event)
{
if ($event->isMasterRequest()) {
$this->container->get('debug.stopwatch')->lap('rating_events');
}
}
Validate Entity State: Use Symfony’s validator to ensure entities are valid before rating:
$errors = $validator->validate($product);
if (count($errors) > 0) {
throw new \RuntimeException('Entity must be valid to rate.');
}
Custom Rating Values:
Override the getAllowedValues() method in RatingService to support non-numeric ratings (e.g., stars, thumbs):
public function getAllowedValues(): array
{
return ['⭐', '⭐⭐', '⭐⭐⭐', '⭐⭐⭐⭐', '⭐⭐⭐⭐⭐'];
}
Multi-Entity Support:
Extend the bundle to support rating multiple fields of an entity (e.g., Product has quality and delivery ratings):
How can I help you explore Laravel packages today?