🔷 Doctrine ORM en modulo PS 1.7+ — entidad completa
Actualizado: 2025-01-15
#Cuando usar Doctrine vs ObjectModel
| Criterio | ObjectModel | Doctrine ORM |
|---|---|---|
| Compatibilidad | PS 1.5+ (todas) | PS 1.7.6+ (Symfony) |
| Complejidad | Sencillo, array $definition | Mas complejo, annotations |
| Relaciones | Manual (JOINs) | Automaticas (@ManyToOne, etc.) |
| Queries | DbQuery builder | DQL, QueryBuilder, Criteria |
| Cache | Manual | Doctrine cache integrado |
| Recomendado para | Modulos simples, tablas pocas | Modulos complejos, muchas relaciones |
#Estructura de archivos
Arbol de archivos Doctrine en modulo
text
modules/ecom_reviews/
├── ecom_reviews.php
├── config/
│ └── services.yml
├── src/
│ ├── Entity/
│ │ ├── Review.php # Entidad Doctrine
│ │ └── ReviewVote.php # Entidad relacionada
│ ├── Repository/
│ │ └── ReviewRepository.php # Repositorio custom
│ └── Service/
│ └── ReviewService.php # Logica de negocio
└── upgrade/
└── upgrade-2.0.0.php
#Entity con anotaciones
src/Entity/Review.php
php
<?php
namespace EcomReviews\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use DateTime;
/**
* @ORM\Table(name="PREFIX_ecom_review")
* @ORM\Entity(repositoryClass="EcomReviews\Repository\ReviewRepository")
* @ORM\HasLifecycleCallbacks()
*/
class Review
{
/**
* @ORM\Id()
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(type="integer")
*/
private int $id;
/**
* @ORM\Column(name="id_product", type="integer")
*/
private int $productId;
/**
* @ORM\Column(name="id_customer", type="integer", nullable=true)
*/
private ?int $customerId;
/**
* @ORM\Column(name="customer_name", type="string", length=128)
*/
private string $customerName;
/**
* @ORM\Column(type="string", length=255)
*/
private string $title;
/**
* @ORM\Column(type="text")
*/
private string $content;
/**
* @ORM\Column(type="smallint")
*/
private int $rating;
/**
* @ORM\Column(type="boolean")
*/
private bool $validated = false;
/**
* @ORM\Column(name="date_add", type="datetime")
*/
private DateTime $dateAdd;
/**
* Relacion One-to-Many: una review tiene muchos votos
* @ORM\OneToMany(targetEntity="ReviewVote", mappedBy="review", cascade={"persist","remove"})
*/
private Collection $votes;
public function __construct()
{
$this->votes = new ArrayCollection();
$this->dateAdd = new DateTime();
}
/**
* Lifecycle callback: auto-set date_add
* @ORM\PrePersist()
*/
public function prePersist(): void
{
if (!isset($this->dateAdd)) {
$this->dateAdd = new DateTime();
}
}
// --- Getters ---
public function getId(): int { return $this->id; }
public function getProductId(): int { return $this->productId; }
public function getCustomerName(): string { return $this->customerName; }
public function getTitle(): string { return $this->title; }
public function getContent(): string { return $this->content; }
public function getRating(): int { return $this->rating; }
public function isValidated(): bool { return $this->validated; }
public function getDateAdd(): DateTime { return $this->dateAdd; }
public function getVotes(): Collection { return $this->votes; }
public function getUpvotes(): int
{
return $this->votes->filter(fn(ReviewVote $v) => $v->isPositive())->count();
}
// --- Setters ---
public function setProductId(int $id): self { $this->productId = $id; return $this; }
public function setCustomerId(?int $id): self { $this->customerId = $id; return $this; }
public function setCustomerName(string $name): self { $this->customerName = $name; return $this; }
public function setTitle(string $title): self { $this->title = $title; return $this; }
public function setContent(string $content): self { $this->content = $content; return $this; }
public function setRating(int $rating): self { $this->rating = max(1, min(5, $rating)); return $this; }
public function validate(): self { $this->validated = true; return $this; }
public function reject(): self { $this->validated = false; return $this; }
}
#Repository personalizado
src/Repository/ReviewRepository.php
php
<?php
namespace EcomReviews\Repository;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use EcomReviews\Entity\Review;
class ReviewRepository extends EntityRepository
{
/**
* Reviews validadas de un producto, ordenadas por fecha
*/
public function findValidatedByProduct(int $productId, int $limit = 20): array
{
return $this->createQueryBuilder('r')
->where('r.productId = :productId')
->andWhere('r.validated = :validated')
->setParameter('productId', $productId)
->setParameter('validated', true)
->orderBy('r.dateAdd', 'DESC')
->setMaxResults($limit)
->getQuery()
->getResult();
}
/**
* Media de rating de un producto
*/
public function getAverageRating(int $productId): ?float
{
$result = $this->createQueryBuilder('r')
->select('AVG(r.rating) as avg_rating')
->where('r.productId = :productId')
->andWhere('r.validated = true')
->setParameter('productId', $productId)
->getQuery()
->getSingleScalarResult();
return $result ? round((float) $result, 1) : null;
}
/**
* Reviews pendientes de moderacion (para admin)
*/
public function findPendingReviews(int $page = 1, int $perPage = 20): array
{
return $this->createQueryBuilder('r')
->where('r.validated = false')
->orderBy('r.dateAdd', 'ASC') // Las mas antiguas primero
->setFirstResult(($page - 1) * $perPage)
->setMaxResults($perPage)
->getQuery()
->getResult();
}
/**
* Estadisticas de reviews por producto
*/
public function getProductStats(int $productId): array
{
return $this->createQueryBuilder('r')
->select(
'COUNT(r.id) as total',
'AVG(r.rating) as average',
'SUM(CASE WHEN r.rating = 5 THEN 1 ELSE 0 END) as five_stars',
'SUM(CASE WHEN r.rating = 4 THEN 1 ELSE 0 END) as four_stars',
'SUM(CASE WHEN r.rating = 3 THEN 1 ELSE 0 END) as three_stars',
'SUM(CASE WHEN r.rating = 2 THEN 1 ELSE 0 END) as two_stars',
'SUM(CASE WHEN r.rating = 1 THEN 1 ELSE 0 END) as one_star'
)
->where('r.productId = :productId')
->andWhere('r.validated = true')
->setParameter('productId', $productId)
->getQuery()
->getSingleResult();
}
}
#Servicio con inyeccion de dependencias
src/Service/ReviewService.php
php
<?php
namespace EcomReviews\Service;
use Doctrine\ORM\EntityManagerInterface;
use EcomReviews\Entity\Review;
use EcomReviews\Repository\ReviewRepository;
class ReviewService
{
private EntityManagerInterface $em;
private ReviewRepository $repository;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->repository = $em->getRepository(Review::class);
}
public function createReview(
int $productId,
?int $customerId,
string $name,
string $title,
string $content,
int $rating
): Review {
$review = new Review();
$review->setProductId($productId)
->setCustomerId($customerId)
->setCustomerName($name)
->setTitle($title)
->setContent($content)
->setRating($rating);
$this->em->persist($review);
$this->em->flush();
return $review;
}
public function validateReview(int $reviewId): bool
{
$review = $this->repository->find($reviewId);
if (!$review) return false;
$review->validate();
$this->em->flush();
return true;
}
public function deleteReview(int $reviewId): bool
{
$review = $this->repository->find($reviewId);
if (!$review) return false;
$this->em->remove($review);
$this->em->flush();
return true;
}
}
config/services.yml
yaml
services:
_defaults:
public: true
autowire: true
ecom_reviews.service.review:
class: EcomReviews\Service\ReviewService
arguments:
- '@doctrine.orm.entity_manager'
#Usar en un controller
Usar el servicio en un hook del modulo
php
<?php
// En el modulo principal
public function hookDisplayProductExtraContent($params)
{
// Obtener el servicio via container de Symfony
/** @var ReviewService $reviewService */
$reviewService = $this->get('ecom_reviews.service.review');
// O acceder directamente al EntityManager
$em = $this->get('doctrine.orm.entity_manager');
$repo = $em->getRepository(Review::class);
$reviews = $repo->findValidatedByProduct((int) $params['product']->id);
$avgRating = $repo->getAverageRating((int) $params['product']->id);
// ...
}
#Migraciones de schema
Doctrine no ejecuta migraciones automaticamente en PS. Debes crear las tablas en install() y actualizarlas en scripts de upgrade/. Puedes usar el Schema Tool de Doctrine para generar el SQL desde las entidades.
install() con Doctrine Schema Tool
php
<?php
public function install()
{
if (!parent::install()) return false;
// Generar SQL desde entidades Doctrine
try {
$em = $this->get('doctrine.orm.entity_manager');
$schemaTool = new \Doctrine\ORM\Tools\SchemaTool($em);
$classes = [
$em->getClassMetadata(Review::class),
$em->getClassMetadata(ReviewVote::class),
];
$schemaTool->createSchema($classes);
} catch (\Exception $e) {
// Fallback: SQL manual
return $this->installSqlFallback();
}
return $this->registerHook('displayProductExtraContent');
}
Descargar en Markdown
Pensado para pegar en ChatGPT, Claude u otra IA. Incluye solo el contenido de esta pagina.