Symfony2 - How to fetch/join only related entities with a certain condition (one-to-many relation)? - symfony

I have a onetomany relationship between event and participant entities.
In my controller i can do the following:
$participants = $event->getParticipant();
But now i only want the Participants where the property visible has the value 1.
how can i get those collection? because participant is an arraycollection of all participants with participant.event_id = event.id in the event entity.

You can easily create a method in your EventRepository that joins only the visible participants with the event:
// YourBundle/Entity/EventRepository.php
public function getEventFilterVisibleParticipants($event_id)
{
return $repository->createQueryBuilder('event')
->where('event.id = :event_id')
->leftJoin('event.participants', 'participant', 'WITH', 'participant.visible = :visibility')
->setParameter('event_id', $event_id)
->setParameter('visibility', 1)
->orderBy('event.startDate', 'DESC')
->getQuery()
->getResult()
;
}
... Now in your controller do something like this:
$event = $this
->getDoctrine()
->getRepository('YourBundle:Event')
->getEventFilterVisibleParticipants($id)
;
$participants = $event->getParticipants(); // returns the filtered collection

Related

Find the latest plants by category doctrine

How to get the latest plants by category, ManyToMany relationship, using doctrine can help me I'm struggling a lot
Entity Plant
Entity plant_category
Entity category
$sql = "SELECT category_id, c.name, c.slug, i.name
FROM category AS c
LEFT JOIN plant_category AS pc
ON pc.category_id = c.id
INNER JOIN (SELECT id, name FROM plant
ORDER BY id DESC LIMIT 1) AS i
ON pc.plant_id = i.id
GROUP BY c.id, c.id, i.name
ORDER BY c.id ASC";
I did a search but I couldn't find anything yet with doctrine
$t = $this->createQueryBuilder("c")
->select("c")
->from(Plant::class, "p")
->leftJoin('p.categories', 'cc')
->leftJoin('(SELECT id, name FROM plant
ORDER BY id DESC LIMIT 1)
ON pc.plant_id = i.id', "i");
Doctrine ORM works a bit differently than working with a standard SQL query.
To work with the entities, the separation of concerns changes from an abstraction (non-deterministic array) to a static model (deterministic object).
In that your Category entity is expected to have an association for the i.name column of your query for the desired end-result, otherwise a partial object is implicated, which is highly discouraged due it causing an invalid state of the entity.
You're attempting to solve for a special circumstance where you want to show a single specific record from the ManyToMany association of the Category::$plants collection, which goes against the Object Model design pattern. The object models are expected to be Valid at all times, so accounting for those special circumstances from the context of the ORM breaks that core principal.
In short, the ORM should not be doing that, you're most likely looking for an array result (as given in the answer by #JeanCapelle) or a native query.
Criteria on the Object Model
The simplest workaround is to utilize the object model with a criteria applied to the collection to filter the results and obtain the desired results. However, the use of criteria on the model is also highly discouraged as it heavily impacts performance and has implications of an invalid entity state.
use Doctrine\Common\Collections\Criteria;
class Category
{
/**
* #return Collection|Plants[]
*/
private $plants;
public function __construct()
{
// ...
$this->plants = new ArrayCollection();
}
public function getPlants(): Collection
{
return $this->plants;
}
public function getLatestPlant(): ?Plant
{
$criteria = Criteria::create()->setMaxResults(1);
return $this->plants->matching($criteria->orderBy(['id', 'DESC']))->first() ? : null;
}
}
Then the query would be changed to the following.
$qb = $this->_em->createQueryBuilder();
$t = $qb->select('c')
->from(Category::class, 'c')
->addSelect('p')
->leftJoin('c.plants', 'p')
->orderBy('c.id', 'ASC');
$categories = $t->getQuery()->getResult();
foreach ($categories as $category) {
$latestPlant = $category->getLatestPlant();
echo $latestPlant->getName();
}
Object Model Association on Latest
The more favorable solution considered best-practice, would be to add the foreign key association to the Category table and a ManyToOne relationship on the Category Entity, instead of relying on a subquery to filter from the association. Where the latest plant is maintained through the application and the object model lifecycle.
Category table: id, name, description, slug, latest_plant
class Category
{
/**
* #return Collection|Plants[]
*/
private $plants;
/**
* #return null|Plant
* #ORM\ManyToOne(targetEntity=Plant::class)
* #ORM\JoinColumn(name="latest_plant", referencedColumnName="id", nullable=true)
*/
private ?Plant $latestPlant = null;
public function getPlants(): Collection
{
return $this->plants;
}
// simple example on how to maintain relationship
public function addPlant(Plant $plant)
{
if (!$this->plants->contains($plant)) {
$this->plants->add($plant);
$plant->addCategory($this);
$this->setLatestPlant($this->plants->last() ?: null);
}
}
// simple example on how to maintain relationship
public function removePlant(Plant $plant)
{
if ($this->plants->contains($plant)) {
$this->plants->removeElement($plant);
$plant->removeCategory($this);
if ($this->latestPlant && $this->latestPlant->getId() === $plant->getId()) {
$this->setLatestPlant($this->plants->last() ?: null);
}
}
}
public function getLatestPlant(): ?Plant
{
return $this->latestPlant;
}
public function setLatestPlant(?Plant $plant)
{
$this->latestPlant = $plant;
}
}
Then the query would look like the below.
$qb = $this->_em->createQueryBuilder();
$t = $qb->select('c')
->from(Category::class, 'c')
->addSelect('lp')
->leftJoin('c.latestPlant', 'lp') // Category::$latestPlant
->orderBy('c.id', 'ASC');
$categories = $t->getQuery()->getResult();
foreach ($categories as $category) {
$latestPlant = $category->getLatestPlant();
echo $latestPlant->getName();
}
If your SQL querie is what you expect, I'll suggest you to try something like :
$qb->select('c.id AS category_id', 'c.name', 'c.slug', 'i.name')
->from('Category', 'c')
->leftJoin('c.plantCategories', 'pc')
->innerJoin(
'(SELECT p.id, p.name FROM Plant p ORDER BY p.id DESC LIMIT 1)',
'i',
Join::WITH,
'pc.plant = i.id'
)
->groupBy('c.id', 'c.name', 'c.slug', 'i.name')
->orderBy('c.id', 'ASC');
$results = $qb->getQuery()->getResult();

onFlush listener, getting entity Id for a scheduled entity insertions

I have a listener for onFlush event.
public function onFlush(OnFlushEventArgs $args) {
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
$id = ...
}
}
I would like to get the Id of the $entity. If I call getId() then return null. Is there any way to finish the flushing inside this listener and get the $entity's Id?
Thank you advance
Accessing the ID before the SQL INSERT command is executed is impossible but you can access the MySQL table's auto increment value with some tricky ways like this:
$classMeta = $em->getClassMetadata($className);
if ($classMeta->rootEntityName !== $className) {
// if the Entity is inherited, only root entity table may have auto increment ID
$classMeta = $em->getClassMetadata($classMeta->rootEntityName);
}
$tableName = $classMeta->getTableName();
$conn = $em->getConnection();
$stmt = $conn->prepare("SHOW TABLE STATUS LIKE '".$tableName."'");
$stmt->execute();
$result = $stmt->fetch(\PDO::FETCH_ASSOC);
$id = $result['Auto_increment'];
Be aware using this:
this solution is hacky, auto increment value may differ from inserted ID
your MySQL user must have permission to run this SQL command

how to access annotation of an property(class,mappedBy,inversedBy)

Good morning,
Is it exist an function where I pass an entity and the propertyName and return me the mappedBy,inversedBy and absoluteClassName of an Entity.
The goal is to use the __call to create automatic getteur/setteur and addFucntion bidirectionnal.
I don't want to use generates Entities I want all getteur,setteur and add Function use __call.
But i can"t do an addBirectionnal if i don't know if the relation is many to many or one to many and if i don't know the name of the mappedBy.
my code:
public function __get($p){
return $this->$p;
}
public function __set($p,$v){
$this->$p = $v;
return $this;
}
public function __call($name,$arguments){
if(substr($name,1,3)=='et')
$name2 = substr(3);
if($name[0] == 'g'){
return $this->$name2;
}else{//substr($name,0,1) == 's'
$this->$name2 = $arguments[0];
/*for a one to one*/
/*$mappedByName= getmappedByOrInversedBy(get_class($name),$name2);
if($mappedByName){
$this->$name->$mappedByName = $this;/
}*/
return $this;
}
}
}
I need getmappedByOrInversedBy, thanks.
edit: I try this
public function test(){
$str = "AppBundle\Entity\Group";
$mapping = new \Doctrine\ORM\Mapping\ClassMetadataInfo($str);
$d = $mapping->getAssociationMappedByTargetField('trad');
var_dump($d);
return $this->render('default/index.html.twig', array(
'base_dir' => realpath($this->getParameter('kernel.root_dir').'/..'),
));
}
class Group
{
...
/**
* #ORM\OneToOne(targetEntity="Traduction",inversedBy="grp")
*/
protected $trad;
}
Result : Undefined index: trad
The ClassMetadataInfo is what you are looking for.
Creates an instance with the entityName :
$mapping = new \Doctrine\ORM\Mapping\ClassMetadataInfo($entityNamespaceOrAlias);
Then, get the informations you want :
Get all association names: $mapping->getAssociationNames();
Get the join column of an association:
$mapping->getSingleAssociationJoinColumnName($fieldName);
Get the mappedBy column of an association:
$mapping->getAssociationMappedByTargetField($fieldName);
...
Look at the class to know which method you can access.
Hopes it's what you expect.
EDIT
As you can access the EntityManager (i.e. from a controller), use :
$em = $this->getDoctrine()->getManager();
$metadata = $em->getClassMetadata('AppBundle:Group');
To be sure there is no problem with your entity namespace, try :
print $metadata->getTableName();
To retrieve the associations of the entity, use :
$metadata->getAssociationNames();
And to get the mapping informations of an existing association, use :
$metadata->getAssociationMapping($fieldName);
And to get all the association mappings of your entity, use:
$metadata->getAssociationMappings();

Get related record one to many relationships Symfony2

I want to check if the current user has already a record for the current date.The User entity has many Timerecord and Timerecord has one User. So far,
This two entities resides in two different bundles
Project
UserBundle
TimerecordBundle
controller
$user = $this->container->get('security.context')->getToken()->getUser();
$userr = $user->getUsername();
$alreadyLoggedIn = $em->getRepository('EmployeeBundle:Timerecord')->findAlreadyTimedInToday($userr);
var_dump($alreadyLoggedIn);
die();
Respository
public function findAlreadyTimedInToday($userr)
{
return $this
->createQueryBuilder('t')
->select('u.username')
->from('User u')
->join('u.Timerecord t')
->where('u.username LIKE :currentuser')
->setParameter('currentuser',$userr)
->getQuery()
->getSingleResult()
;
}
I got this exception
Warning: Missing argument 2 for Doctrine\ORM\QueryBuilder::from()
How do you fetch the related user in this case?
The alias for the from part should be given to the second argument.
Change your method to this:
public function findAlreadyTimedInToday($userr)
{
return $this
->createQueryBuilder('t')
->select('u.username')
->from('User', 'u')
->join('u.Timerecord', 't')
->where('u.username LIKE :currentuser')
->setParameter('currentuser', $userr)
->getQuery()
->getSingleResult()
;
}

EntityChangeSet in preUpdate not containing relations?

I am trying to do something when a User joins a Group. I am trying to use the preUpdate event for it and then check if the corresponding relations have changed. Unfortunately in my Group the 'users" relation is never in the changeset, as well as in my User the Usergroup is never in the changeset.
Here the two listeners:
public function preUpdate(PreUpdateEventArgs $args){
if($args->hasChangedField('users')){
$old = $args->getOldValue('users');
$new = $args->getNewValue('users');
}
}
public function preUpdate(PreUpdateEventArgs $args){
if($args->hasChangedField('userGroups')){
$old = $args->getOldValue('userGroups');
$new = $args->getNewValue('userGroups');
}
}
Thats my TestCase:
$group->addUser($user);
$em->beginTransaction();
$em->persist($group);
$em->flush();
$em->rollback();
Both listeners are called, but the relation is never in the changeset.
I want to add the user into a redis table, where I manage some specific data. Maybe the onFlush or some other events are better, since I don't need to modify the saved data. I just want to know if a there is a new Entry in my User-UserGroup Relation. I thought the easiest way to check this would be the changeset within the preUpdate function.
I solved it via the onFlush event:
public function onFlush(OnFlushEventArgs $args){
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledCollectionUpdates() as $col) {
if($this->isUserGroupUserAssociation($col->getMapping())){
$userGroup = $col->getOwner();
foreach($col->getInsertDiff() as $user){
$this->container->get('strego_user.user_manager')->triggerJoined($userGroup,$user);
}
}
}
}
protected function isUserGroupUserAssociation($association){
return($association['fieldName'] == "users" &&
$association['sourceEntity'] == "Strego\UserBundle\Entity\UserGroup"
);
}

Resources