Symfony Doctrine SortBy ToMany relation objects - symfony

I have entity post and points, the are connected by oneToMany relation. I want make method that will return objects with most count of related comments.
Is it possible?
Please help, i don't have any idea.
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/tutorials/ordered-associations.html - should i use this?
entities:
post:
/**
* #ORM\Entity(repositoryClass="App\Repository\PostRepository")
*/
class Post
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var Points
* #ORM\OneToMany(targetEntity="Points", mappedBy="post", fetch="EAGER")
*/
private $points;
/**
* #return Collection|Points[]
*/
public function getPoints(): Collection {
return $this->points;
}
...
points
/**
* #ORM\Entity(repositoryClass="App\Repository\PointsRepository")
*/
class Points
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var Post
* #ORM\ManyToOne(targetEntity="Post", inversedBy="points", fetch="EAGER")
*/
private $post;
public function getPost(): Post {
return $this->post;
}
public function setPost(Post $post ){
$this->post = $post;
}
...

On the assumption that you are already able to return a post with its points you might try something like this:
in App\Repository\PostRepository:
public function postsByPoints() {
return $this->getEntityManager()->createQueryBuilder()
->select('p.post, count(pt.points) N)
->from('App:Points', 'pt')
->join('pt.post', 'p')
->where('some where clause') <- delete this if you're not selecting a subset
->groupBy('p.post')
->orderBy('N')
->getQuery()->getResult();
}
In some controller:
$em = $this->getDoctrine()->getManager();
$postsByPoints = $em->getRepository('App:Post')->postsByPoints();
NB: not tested

This is an working (for me) code
return $this->createQueryBuilder('p')
->innerJoin('p.user', 'c')
->innerJoin('p.points', 'pp')
->andWhere("p.date > '".$now->format("Y-m-d H:i:s")."'")
->setMaxResults($max)
->groupBy('pp.post')
->orderBy('pp.post','DESC')
->getQuery()
->getResult();

Related

Symfony retrieve Unique values from EAV model

I'm trying to make product filters but I can't generate a correct query
А quick look at the base
db visually
here are my entities:
AttributeType:
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100, nullable=true)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity=AttributeValue::class, mappedBy="attributeType")
*/
private $attributeValue;
public function __construct()
{
$this->attributeValue = new ArrayCollection();
}
AttributeValue:
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity=Product::class, inversedBy="attributeValues")
*/
private $product;
/**
* #ORM\Column(type="string", length=100)
*/
private $value;
/**
* #ORM\ManyToOne(targetEntity=AttributeType::class, inversedBy="attributeValue")
*/
private $attributeType;
For example AttributeType(Color) has AttributeValue(Red, Blue, Green) & i retrieve hundred of red, blue, green AttributeValue for a single Color option
that query returns options with all value(not unique):
return $this->createQueryBuilder('at')
->innerJoin('at.attributeValue', 'attribute_value')
->addSelect('attribute_value')
->getQuery()
->getResult();
I tried to modify the request like this:
return $this->createQueryBuilder('at')
->innerJoin('at.attributeValue', 'attribute_value')
->addSelect('attribute_value.value')->distinct()
->getQuery()
->getResult();
(there were other attempts, but they were all not even close)
How do I get unique values for each option?
I will be grateful for any help
And thx for your time.
I get unique values for each option
public function findOptionsWithUniqueValue()
{
$result = $this->getEntityManager()->createQueryBuilder()
->addSelect('attribute_type.name, attribute_value.value')
->distinct()
->from(AttributeType::class,'attribute_type')
->from(AttributeValue::class, 'attribute_value')
->andWhere('attribute_type.id = attribute_value.attributeType')
->getQuery()
->getResult()
;
$out = [];
while( $a = array_shift($result)) {
$out[$a['name']][] = $a['value'];
}
return $out;
}

Sort a doctrine's #OneToMany ArrayCollection by field

Close question was enter link description here but I need to more deep sorting:
/**
* #var ArrayCollection[SubjectTag]
*
* #ORM\OneToMany(targetEntity="SubjectTag", mappedBy="subject")
* #ORM\OrderBy({"position" = "ASC"})
* #Assert\Valid()
*/
protected $subjectTags;
In subjectTag I have:
/**
* #var ArrayCollection[tag]
*
* #ORM\OneToMany(targetEntity="Tag", mappedBy="subject")
* #ORM\OrderBy({"name" = "ASC"})
* #Assert\Valid()
*/
protected $tags;
Now I want to sort by SubjectTag.tags. How can I do that?
EDIT:
Entity1.php:
/**
* #ORM\ManyToOne(targetEntity="Entity2", referencedColumnName="id", nullable=false)
* #Assert\Valid()
*/
protected $entity2;
Entity2.php:
/**
* #ORM\ManyToOne(targetEntity="Entity3", referencedColumnName="id", nullable=false)
* #Assert\Valid()
*/
protected $entity3;
Entity3.php:
/**
* #ORM\Column(type="integer", nullable=true)
*/
protected $position;
And now.. I want have in Entity1 Entity2 sorted by position. How can I do that by default?
As explained in my previous comment, you should do a custom query in your repository class corresponding to your base Entity (You didn't give the name of it).
So in your App\Repository\"YourBaseENtityName"Repository class, you do something like this.
public function findOrderByTags()
{
return $this
->createQueryBuilder('baseEntityAlias')
->addSelect('st')
->addSelect('t')
->leftJoin('baseEntityAlias.subjectTags', 'st')
->leftJoin('st.tags', 't')
->orderBy('st.position', 'ASC')
->addOrderBy('t.name', 'ASC')
->getQuery()
->getResult();
}
Moreover, I'm not sure about what kind of order you want to perform based on your question. Here the baseEntity->subjectTags will be ordered by their positions and then the baseEntity->subjectTags->tags will be ordered by name.
Now you can call this method from your base entity repository class
Hope it will be helpful for you.
EDIT:
Here is a way to introduce a default behavior for your queryBuilder and reuse it.
/**
* In your EntityRepository add a method to init your query builder
*/
public function createDefaultQueryBuilder(string $alias = 'a')
{
return $this
->createQueryBuilder($alias)
->addSelect('st')
->addSelect('t')
->leftJoin('baseEntityAlias.subjectTags', 'st')
->leftJoin('st.tags', 't')
->orderBy('st.position', 'ASC')
->addOrderBy('t.name', 'ASC');
}
/**
* In this example, I override the default find method. I don't recommend it thought
*/
public function find($id, $lockMode = null, $lockVersion = null)
{
return $this
->createDefaultQueryBuilder()
->where('a.id = :id')
->setParameter('id', $id)
->getQuery()
->getOneOrNullResult();
}
As you can see, I reuse the createDefaultQueryBuilder method in order to get a default behavior with subjectTags and tags init in the relation and ordered in the right way.

Symfony, Many to many with attributes self referencing

I have an entity Room that has a ManyToMany relationship with itself, and this relationship (RoomLinkRoom) bears an attribute "weight" :
a Room can be linked to several Rooms, with an ordering(weight) value (and potentially other attributes).
The code i have sofar handles the linkage of one or more RoomTo to a RoomFrom.
Let's say we have :
RoomA : 1
RoomB : 2
In the Room Form, we can add RoomB to RoomA
we have this entry in RoomLinkRoom :
RoomFromId : 1
RoomToId : 2
But this is only half of what i need : i also need the reverse part management.
When RoomFromId,1 is linked to RoomToId,2
i also want to add in this intermediate entity : RoomFromId,2, RoomToId,1
And then i also have to manage the delete part : If i remove Room A, the entry (roomFrom, roomTo) : (1, 2) will be deleted but the reverse (2, 1) must also be deleted.
How can i achieve this ? What is the best (cleanest ?) way to handle this whole problem ? Is there a "standard pattern" ("automatic" or not) for handling this case ?
I'm not sure how to procede, but maybe it involves events, like postFlush ? But will it be able to take care also of the delete for the "reverse" (To) side ?
The (relevant part of the) Entities are :
<?php
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use MyBundle\Entity\RoomLinkRoom;
/**
* Room
*
* #ORM\Table(name="room")
* #ORM\Entity(repositoryClass="MyBundle\Repository\RoomRepository")
* #ORM\HasLifecycleCallbacks()
*/
class Room
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=255, nullable=false)
*/
private $name;
/**
* #ORM\OneToMany(
* targetEntity="MyBundle\Entity\RoomLinkRoom", mappedBy="roomFrom",
* cascade={"persist", "remove"}, orphanRemoval=TRUE)
*/
private $roomFromLinks;
/**
* #ORM\OneToMany(
* targetEntity="MyBundle\Entity\RoomLinkRoom", mappedBy="roomTo",
* cascade={"persist", "remove"}, orphanRemoval=TRUE)
*/
private $roomToLinks;
public function __construct()
{
$this->roomFromLinks = new ArrayCollection();
$this->roomToLinks = new ArrayCollection();
}
public function addRoomFromLink(RoomLinkRoom $roomFromLink)
{
$this->roomFromLinks[] = $roomFromLink;
$roomFromLink->setRoomFrom($this);
return $this;
}
public function removeRoomFromLink(RoomLinkRoom $roomFromLink)
{
$this->roomFromLinks->removeElement($roomFromLink);
}
public function getRoomFromLinks()
{
return $this->roomFromLinks;
}
public function addRoomToLink(RoomLinkRoom $roomToLink)
{
$this->roomToLinks[] = $roomToLink;
$roomToLink->setRoomTo($this);
return $this;
}
public function removeRoomToLink(RoomLinkRoom $roomToLink)
{
$this->roomToLinks->removeElement($roomToLink);
}
public function getRoomToLinks()
{
return $this->roomToLinks;
}
}
And
<?php
use Doctrine\ORM\Mapping as ORM;
use MyBundle\Entity\Room;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* RoomLinkRoom.
*
* #ORM\Table(name="roomlinkroom", indexes={
* #ORM\Index(name="FK_RoomLinkRoom_roomfromId", columns={"roomfromId"}),
* #ORM\Index(name="FK_RoomLinkRoom_roomtoId", columns={"roomtoId"}),
* })
* #ORM\Entity(repositoryClass="MyBundle\Repository\RoomLinkRoomRepository")
* #UniqueEntity(
* fields={"roomFrom", "roomTo"},
* errorPath="weight",
* message="Ces salles sont déjà liées."
* )
*/
class RoomLinkRoom
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="MyBundle\Entity\Room", inversedBy="roomFromLinks")
* #ORM\JoinColumn(name="roomFromId", referencedColumnName="id", nullable=false)
*/
private $roomFrom;
/**
* #ORM\ManyToOne(targetEntity="MyBundle\Entity\Room", inversedBy="roomToLinks")
* #ORM\JoinColumn(name="roomToId", referencedColumnName="id", nullable=false)
*/
private $roomTo;
public function getId()
{
return $this->id;
}
public function setRoomFrom(Room $roomFrom)
{
$this->roomFrom = $roomFrom;
return $this;
}
public function getRoomFrom()
{
return $this->roomFrom;
}
public function setRoomTo(Room $roomTo)
{
$this->roomTo = $roomTo;
return $this;
}
public function getRoomTo()
{
return $this->roomTo;
}
}
I've tried in the controller to add after the flush :
if ($form->isSubmitted() && $form->isValid()) {
//...
$em->persist($room);
$em->flush();
//look for existing relationship between To and From
foreach ($room->getRoomFromLinks() as $rfl){
$res = $em->getRepository('MyBundle:RoomLinkRoom')
->findBy(['roomFrom' => $rfl->getRoomTo(), 'roomTo' => $room]);
//add the reverse side
if (count($res) === 0){
$rlr = new RoomLinkRoom();
$rlr->setRoomFrom($rfl->getRoomTo());
$rlr->setRoomTo($room);
$em->persist($rlr);
$em->flush();
}
}
//redirect
}
This adds the reverse/complementary entry, but i still don't know if this is clean/bugproof. But i'm sure this doesn't take care of the delete.
So does anyone have used this kind of relationship ?
I've seen other discussions here (for instance, this one) but, they don't seem to care for this "reverse" management.
Thank you in advance.

Notice: Trying to get property of non-object in vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php line 481

I have a really strange case related to doctrine, loggable (DoctrineExtension) and listeners.
I will explain the situation I am having here and below is all the code I think is related to the problem.
I have two entities (Agreement and Template) where an agreement is based on a specific Template version. Template entity has the DoctrineExtension Loggable annotation. So I can revert an Agreement template to the specific version using the LogEntryRepository->revert() method. (I am using a postLoad listener to do that, so each time an agreement is retrieved, the right Template version is loaded for that Agreement).
If I get a controller action where an agreement is retrieved using a ParamConververter annotation, everything works ok and my agreement is retrieved with the right Template.
If I try to retrieve the very same agreement in the first line of the controller action using a query builder, I get the following exception
Notice: Trying to get property of non-object in /home/administrator{dir}/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php line 481
Any help would be appreciated.
Thanks.
Just copying the parts that are related to the problem:
Entities
/**
* Agreement
*
* #ORM\Table(name="agreement")
* #ORM\Entity
* #Gedmo\Loggable
*/
class Agreement
{
/**
* #var integer
* #ORM\Column(name="id", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var integer
* #ORM\Column(name="template_version", type="bigint", nullable=false)
* #Gedmo\Versioned
*/
private $templateVersion;
/**
* #var \Template
* #ORM\ManyToOne(targetEntity="Template")
* #ORM\JoinColumn(name="template_id", referencedColumnName="id")
*/
private $template;
}
/*
* Template
*
* #ORM\Table(name="template")
* #ORM\Entity
* #ORM\ChangeTrackingPolicy("DEFERRED_EXPLICIT")
* #Gedmo\Loggable
*/
class Template
{
/**
* #var integer
* #ORM\Column(name="id", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
* #ORM\Column(name="name", type="string", length=255, nullable=false)
* #Gedmo\Versioned
*/
private $name;
}
Doctrine Subscriber
*(services.yml)*
services:
ourdeal.listener.loggable:
class: App\Bundle\Listener\LoggableSubscriber
tags:
- { name: doctrine.event_subscriber }
class LoggableSubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
return array(
'prePersist',
'postLoad',
);
}
public function prePersist(LifecycleEventArgs $args)
*...Code omitted...*
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
if ($entity instanceof Agreement)
{
$agreement = $entity;
$repo = $entityManager->getRepository('Gedmo\Loggable\Entity\LogEntry');
$repo->revert($agreement->getTemplate(), $agreement->getTemplateVersion());
}
}
}
Actions
With this action, I get the desired agreement without problems.
/**
* #Route("/agreement/send/{id}", name="agreement/send")
* #ParamConverter("agreement", class="Bundle:Agreement")
* #Template()
*/
public function sendAction(Request $request, Agreement $agreement) {
*...Code omitted...*
}
Using this code, I get the exception (the hardcoded id and this code is just for test)
/**
* #Route("/agreement/send", name="agreement/send")
* #Template()
*/
public function sendAction(Request $request) {
$em = $this->get('doctrine')->getManager();
$qb = $em->createQueryBuilder()->select('a')->from('AppBundle:Agreement', 'a')->where('a.id=1378');
$agreements = $qb->getQuery()->getResult();
}
use setParameter()
$em->createQueryBuilder()
->select('a')
->from('AppBundle:Agreement', 'a')
->where('a.id = :id')
->setParameter('id', $request->get('id'));
There is a known bug #52083 that affects PHP versions before 5.3.4, which fails randomly with "Notice: Trying to get property of non-object".
If that is your case, try upgrading PHP will solve your issue. Hope that helps

One-To-Many Relation in Symfony 2 with Doctrine

I've looked at literally tons of questions/answers on Stack Overflow and other places on the web, but cannot find a resolution to this problem.
Before I explain the problem, I have:
stripped back my entities so that they only have the minimum attributes and methods
have cleared the doctrine query cache / metadata cache
dropped and recreated the schema
checked my spelling
I have the following two entities:
<?php
namespace Docker\ApiBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Source
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="integer")
* #ORM\ManyToOne(targetEntity="Project",inversedBy="sources")
* #ORM\JoinColumn(referencedColumnName="id")
*/
private $project;
}
<?php
namespace Docker\ApiBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Project
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Source", mappedBy="project")
*/
private $sources;
public function __construct() {
$this->sources = new ArrayCollection();
}
public function getSources() {
return $this->sources;
}
}
So a many 'sources' can belong to one 'project'.
In my controller I have:
$em = $this->getDoctrine()->getManager();
$project = $em->find('Docker\ApiBundle\Entity\Project', 1);
$sources = $project->getSources()->toArray();
I have tried lots of things but I always get:
Notice: Undefined index: project in /.../www/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php line 1577
Like I say, I know there are a lot of questions going around about this, but none of the accepted answers fix my problem.
This all looks pretty fundamental to using Doctrine2 so not sure what I am doing wrong - it could be something really obvious.
Any help would be appreciated.
You have:
/**
* #ORM\Column(type="integer")
* #ORM\ManyToOne(targetEntity="Project",inversedBy="sources")
* #ORM\JoinColumn(referencedColumnName="id")
*/
private $project;
Remove:
#ORM\Column(type="integer")
from annotation.
If this is exactly your code, i dont see a namespace in your Project class. Try adding the namespace line "namespace Docker\ApiBundle\Entity;".
If this is the same folder no need for "use" but if it s part of other bundle or folder try putting a line like "use Docker\ApiBundle\Entity\Project;"
in your Source class. I Hope it helps..
Otherwise :
<?php
namespace Azimut\ApiBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Source
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
private $name;
/**
* #ORM\Column(type="integer")
* #ORM\ManyToOne(targetEntity="Azimut\ApiBundle\Entity\Project", inversedBy="sources")
* #ORM\JoinColumn(onDelete="CASCADE")
*/
private $project;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set project
*
* #param integer $project
* #return Source
*/
public function setProject($project)
{
$this->project = $project;
return $this;
}
/**
* Get project
*
* #return integer
*/
public function getProject()
{
return $this->project;
}
}
<?php
namespace Azimut\ApiBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
*/
class Project
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Azimut\ApiBundle\Entity\Source", mappedBy="object", cascade={"all"})
*/
private $sources;
public function __construct() {
$this->sources = new ArrayCollection();
}
public function getSources() {
return $this->sources;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Add sources
*
* #param \Azimut\ApiBundle\Entity\Source $sources
* #return Project
*/
public function addSource(\Azimut\ApiBundle\Entity\Source $sources)
{
$this->sources[] = $sources;
return $this;
}
/**
* Remove sources
*
* #param \Azimut\ApiBundle\Entity\Source $sources
*/
public function removeSource(\Azimut\ApiBundle\Entity\Source $sources)
{
$this->sources->removeElement($sources);
}
}
and little part of the controller:
public function helloAction()
{
$id = 1;
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository('Azimut\ApiBundle\Entity\Project')->find($id);
$source1 = $em->getRepository('Azimut\ApiBundle\Entity\Source')->find(3);
$source2 = $em->getRepository('Azimut\ApiBundle\Entity\Source')->find(5);
$project->addSource($source1);
$sources = array();
$sources = $project->getSources();
var_dump($sources);
return ....
}
and this works just fine for me.

Resources