Buenas tardes,
Tengo que filtrar desde un array de objetos (Entidad
) el contenido de una propiedad asociada con otra Entidad
con una relación ManyToMany.
Mi problema es que lo que yo obtengo con get()
de esa propiedad es una instancia de Doctrine\ORM\PersistentCollection, y esta clase:
- No me permite hacer una consulta con la función
marching(Criteria $criteria)
, al menos no cuando se trata de una relaciónManyToMany
. - No tiene una función con la que obtener el
ArrayCollection
con las entidades relacionadas al que poder hacerle esa consulta. Lo máximo que me devuelve es un array de objetos (Entidad).
Edito: Sí que tiene una función para obtener la propiedad coll
que contiene el ArrayCollection
con los objetos, es unwrap()
. Pero en este caso contiene un array vacío
¿Cuál sería la forma más eficiente y "elegante" de hacerlo?
Pongo un resumen del código por si aclara más el caso:
class Anuncio { /** * @var ArrayCollection * * @ORM\ManyToMany(targetEntity="Promocion", mappedBy="anuncios") */ private $promociones; } class Promociones { /** * @var ArrayCollection * * @ORM\ManyToMany(targetEntity="Anuncio", inversedBy="promociones") * @ORM\JoinTable(name="promociones_anuncios") */ private $anuncios; } class AnuncioManager { /** * @return array */ public function obtenerAnuncios(){ $anuncios = $this->repository->findAllOrderedByPosicion(); foreach ($anuncios as $anuncio){ $anuncio = $this->descartarPromocionesCaducadas($anuncio); $anuncios[$anuncio->getPosicion()] = $anuncio; } return $anuncios; } /** * @param Anuncio $anuncio * @return Anuncio */ public function descartarPromocionesCaducadas(Anuncio $anuncio){ //Código que obtiene las promociones vigentes y las almacena en el objeto $anuncio return $anuncio; } }
Como ya digo la cuestión no es resolverlo si no saber cuál es la mejor forma de resolver una situación así.
Muchas gracias de antemano :)
Respuestas
He encontrado el método que me devuelve el atributo coll
con un ArrayCollection
, es la función unwrap()
pero contiene un array vacío.
@hm_sergio
Buenas Sergio, desde mi punto de vista la solución puede estar enfocada en dos aspectos:
Facilidad de Desarrollo
Para lograr una implementación facil de realizar lo mejor es obtener los elementos de la colección directamente desde la entidad, haciendo algo como:
Class Anuncio { public function getPromocionesActivas() { // filtramos la colección, y devolvemos los elementos que cumplen el filtro. return $this->getPromociones()->filter(function(Promociones $promocion){ return $promocion->isActiva(); }); } }
Asi, donde sea necesario obtener las promocionas activas de un anuncio, será muy facil de hacer, solo llamando al método getPromocionesActivas
de la entidad Anuncio
.
La desventaja de esta implementación es el rendimiento, ya que al ejecutar $this->getPromociones()
estamos obteniendo todas las promociones del anuncio (tanto activas como caducadas), y si por ejemplo un anunció tiene muchas promociones (pongamos unas 50 o puede ser mucho más) y solo 4 están activas, estamos siempre obteniendo un monton de registros y gastando memoria sin ningún sentido.
Rendimiento
Si lo que se quiere es mantener el rendimiento de la aplicación, se me ocurre que lo mejor es crear un método en un Repositorio para las promociones:
Class PromocionesRepository { public function findPromocionesActivasByAnuncio($anuncio) { return $this->createQueryBuilder('p') ->where('p.anuncios = :anuncio') ->andWhere('p.activa = true') ->setParameter('anuncio', $anuncio) ->getQuery() ->getResult(); } }
Así, cuando necesitemos obtener las promociones activas de un anuncio, solo debemos llamar al método findPromocionesActivasByAnuncio
del repositorio de la entidad Promociones
pasandole el anuncio, y obtendremos solo las promociones que necesitamos sin desperdicio de memoria.
La desventaja que veo desde mi punto de vista al implementar esta solución, es que es un poco más complejo su uso, teniendo que obtener y usar la clase PromocionesRepository, donde tengamos que consultar las promociones activas de un anuncio.
Bueno espero sirva de algo esta explicación, Saludos!
@manuel_j555
@manuel_j555: Gracias por tu respuesta.
De las dos posibilidades que planteas me gusta más la segunda, por el tema de no meter más lógica en la clase de la Entidad.
En cuanto al acceso a la función del repositorio, no lo veo un inconveniente si usas servicios con inyección de dependencias, y los repositorios personalizados sobre Entidades que te permite usar Doctrine.
De esta forma podría añadir la función al repositorio personalizado de Anuncio y acceder directamente desde el mismo servicio AnuncioManager
, o añadirlo al repositorio personalizado de Promocion
y meter como dependencia el servicio PromocionManager
que que me daría acceso al mismo.
El problema ahí es la consulta a la base de datos (en el repositorio), que sería algo más complicada de la que has puesto (estoy viendo la forma de hacerlo, porque a mí el dql se me atraganta), y habría que repetirla por cada anuncio, y lo que yo pretendía era que me la hiciera Doctrine a través de las funciones de sus colecciones.
La Entidad
/** * Anuncio * * @ORM\Table(name="anuncios") * @ORM\Entity(repositoryClass="AppBundle\Repository\AnuncioRepository") */ class Anuncio{ }
El Repositorio Personalizado
class AnuncioRepository{ /** * @param Anuncio $anuncio * @return array */ public function findPromocionesActivasByAnuncio(Anuncio $anuncio){ //la consulta de marras } }
El Servicio
class AnuncioManager { /** * @var EntityManager */ private $entityManager; /** * @var ProductoRepository */ private $repository; /** * */ function __construct(EntityManager $em ,$class) { $this->entityManager = $em; $this->repository = $em->getRepository($class); } /** * @return array */ public function obtenerAnuncios(){ return $this->repository->findAllOrderedByPosicion(); } /** * @param $anuncios * @return array */ public function obtenerPromocionesActivas($anuncios){ $promocionesActivas = array(); foreach ($anuncios as $anuncio){ $promocionesActivas[$anuncio->getId()] = $this->repository->findPromocionesActivasByAnuncio($anuncio); } return $promocionesActivas; } }
@hm_sergio
Al final lo resolví como me proponía @manuel_j555.
La consulta ha quedado así:
Repositorio:
/** * @param Anuncio $anuncio * @return array */ public function findPromocionesVigentesByAnuncio(Anuncio $anuncio) { $now = new \DateTime('now'); $qb = $this->createQueryBuilder('p') ->join('p.anuncios', 'a') ->where('p.caducidad > :now') ->andWhere('p.estado = 0') ->andWhere('a.id = :anuncio') ->orderBy('p.orden', 'ASC') ->setParameter('anuncio', $anuncio->getId()) ->setParameter('now', $now, Type::DATE); return $qb->getQuery()->getResult(); }
El Servicio:
/** * @param array $anuncios * @return array */ public function obtenerPromocionesVigentes($anuncios){ $promocionesVigentes = array(); foreach ($anuncios as $anuncio){ $promocionesVigentes[$anuncio->getId()] = $this->promocionManager->getRepository()->findPromocionesVigentesByAnuncio($anuncio); } return $promocionesVigentes; }
@hm_sergio