How to drop row from many to many relationship table in doctrine - php

Hi I have M:M relationship between two tables, Contacts and Tags and their M:M table is called Contacts_Tags:
Contacts
------------
ID
Name
Tags
-----------
ID
Name
Contacts_Tags
--------------
Contact_ID
Tag_ID
I have entities for Contacts called Contact and for Tags called Tag but not for Contacts_Tags table.
I want to drop row from Contacts_Tags only. This is my relationship in Contact Entity.
/**
* Many Contacts have Many Tags.
* #ORM\ManyToMany(targetEntity="Tag", inversedBy="contacts", cascade={"remove"})
* #ORM\JoinTable(name="Contacts_Tags",
* joinColumns={#ORM\JoinColumn(name="Tag_ID", referencedColumnName="ID")},
* inverseJoinColumns={#ORM\JoinColumn(name="Contact_ID", referencedColumnName="ID")}
* )
*/
private $tags;
I tried this query but it didn't help
$queryBuilder = $this->entityManager->getRepository(Contact::class)->createQueryBuilder("o")
->leftJoin("o.tags", "ct")
->leftJoin(Tag::class, "t", "WITH", "t.ID", "ct.Tag_ID")
->where("t.Name = :tagName")
->delete("o.tags", "ct")
->setParameter(":tagName", $diffBeat)
;

You can just remove it from the collection.
you can define a function in your Contact class like this:
function removeTag(Tag $tag)
{
if($this->tags->contains($tag)){
$this->tags->removeElement($tag);
}
}
Afterwards persist your entity

Related

How to select a group of entities according to a condition in a related OneToMany association?

I'm using Symfony4 and I have two entities User and Car.
A Car has a field isRent which is of type bool and further a database column user_id for the relation.
class User
{
/**
* #var Collection
*
* #ORM\OneToMany(targetEntity="Car", mappedBy="user")
*
* #Serializer\Type("ArrayCollection<ListRestAPI\Entity\Cars>")
* #Serializer\Expose()
*/
private $cars;
class Car
{
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="User", inversedBy="cars")
*/
private $user;
Now I'm trying to fetch all users who have cars rented.
My current query fetches all the users regardless of whether they have cars rented or not.
$query = $this->createQueryBuilder('u')
->select()
->join('u.cars', 'c')
->andWhere(sprintf('%c.is_rent = :isRent', 'c'))
->setParameter('isRent', '1')
->groupBy('u.id')
->addOrderBy('u.id');
How can I fetch only User entities that have cars rented?
Try removing your andWhere() by this:
->having('c.isRent = 1')
Since you are using an integer in your query, no need to use bound parameters. You should also remove setParameter().

How does inner join work on a many-to-many relationship using Doctrine and Symfony2

I recently worked out an issue with querying ManyToMany relationship join tables, the solution was same as this answer and was wondering how it works.
lets say i have a simple ManyToMany relationship between groups and team, there will be a groups_team tables that will automatically be created here
groups entity
/**
* Groups
*
* #ORM\Table(name="groups")
* #ORM\Entity(repositoryClass="AppBundle\Model\Repository\GroupsRepository")
*/
class Groups {
/**
* #ORM\ManyToMany(targetEntity="Team", inversedBy="group")
*/
protected $team;
public function __construct() {
$this->team = new ArrayCollection();
}
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="groupname", type="string", length=255)
*/
private $groupname;
//obligatory getters and setters :)
team entity
/**
* Team
*
* #ORM\Table(name="team")
* #ORM\Entity(repositoryClass="AppBundle\Model\Repository\TeamRepository")
*/
class Team {
/**
* #ORM\ManyToMany(targetEntity="Groups", mappedBy="team")
*/
protected $group;
public function __construct(){
$this->group = new ArrayCollection();
}
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="teamname", type="string", length=255)
*/
private $team;
//[setters and getters here]
in order to get all the teams in a group i would have to query the groups_team table.i would have directly queried the table in just mysql but in symfony i have to do this
$groups = $em->getRepository("AppBundle\Model\Entity\Groups")->findBy(array('tournament' => $tournament->getId()));
//get all teams with group id in groups_team table
foreach ($groups as $group) {
$teamsingroup = $em->getRepository("AppBundle\Model\Entity\Team")->createQueryBuilder('o')
->innerJoin('o.group', 't')
->where('t.id = :group_id')
->setParameter('group_id', $group->getId())
->getQuery()->getResult();
echo "</b>".$group->getGroupname()."</b></br>";
foreach ($teamsingroup as $teamingroup) {
echo $teamingroup->getTeam()."</br>";
}
}
Can someone explain to me how the innerJoin is working and what is the concept behind this, maybe a few documentation to learn about this. are there better way to do this with symfony and doctrine.
Using ManyToMany between 2 entities involves a third table generally called as a junction table in this type of relation when you build a DQL (doctrine query) doctrine automatically joins junction table depending on the nature of relation you have defined as annotation so considering your query
$teamsingroup = $em->getRepository("AppBundle\Model\Entity\Team")
->createQueryBuilder('o')
->innerJoin('o.group', 't')
You are joining Team entity with Group entity in innerJoin('o.group') part o is the alias for Team entity and o.group refers to property defined in Team entity named as group.
/**
* #ORM\ManyToMany(targetEntity="Groups", mappedBy="team")
*/
protected $group;
Which has a ManyToMany annotation defined for this type of relation doctrine joins your team table first with junction table and then joins your junction table with groups table and the resultant SQL will be something like
SELECT t.*
FROM teams t
INNER JOIN junction_table jt ON(t.id = jt.team_id)
INNER JOIN groups g ON(g.id = jt.group_id)
WHERE g.id = #group_id
Another thing related your way of getting team for each group you can minimize your code by excluding createQueryBuilder part within loop, once you have defined teams property as ArrayCollection i.e $this->team = new ArrayCollection(); on each group object you will get collections of teams associated to that particular group by calling getTeam() function on group object similar to below code.
foreach ($groups as $group) {
$teamsingroup = $group->getTeam();
echo "</b>".$group->getGroupname()."</b></br>";
foreach ($teamsingroup as $teamingroup) {
echo $teamingroup->getTeam()."</br>";
}
}
I guess it's literally select statement with INNER JOIN using key columns defined entity class as mappedBy or inversedBy.
Why don't you have a look of doctrine log and see what the native sql is composed?
How to get Doctrine to log queries in Symfony2 (stackoverflow)
http://vvv.tobiassjosten.net/symfony/logging-doctrine-queries-in-symfony2/ (some code examples)
I don't know your user story behind this, but I also heard that it is recommended to use one to many relationship instead of many to many, unless there is a strong reason to do so, as most of cases can be handled by one to many by reconsidering models.

Symfony2 Get entities of an entity in another entity

This question is about Symfony2 table relationships using ORM. I have three tables/entities that are related to each other. The relationship is very similar to Wordpress Posts, Categories and Categories relationship tables.
Table 1 contains posts.
Table 2 contains categories
Table 3 contains relationships between the categories and posts.
I want to be able to have the categories property in the posts table and a posts property in the categories table. So that when I call.
Categories->posts : I should get posts in that category.
Posts->categories : I should get the categories the post belongs to.
I want to have unique categories per table and I want all posts to point to a category without having to create a new entry for the category that already exists which is what ManyToOne or OneToMany is offering this is why the third table I think is necessary.
For example here is the relationships
class Category_relationship
{
/**
* #var integer
*
* #ORM\Column(name="object_id", type="bigint")
*
* #ORM\ManyToOne(targetEntity="Worksheet", inversedBy="category_relationships")
* #ORM\JoinColumn(name="worksheet_id", referencedColumnName="id", nullable=FALSE)
*/
private $objectId;
/**
* #var integer
*
* #ORM\Column(name="category_id", type="bigint")
*
* #ORM\ManyToOne(targetEntity="Category", inversedBy="categories")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=FALSE)
*/
private $categoryId;
}
Here is the Category class:
class Category
{
/**
* #ORM\OneToMany(targetEntity="Category_relationship", mappedBy="categoryId", cascade={"persist", "remove"}, orphanRemoval=TRUE)
*/
protected $posts;
}
Here is the Category class:
class Posts
{ /**
* #ORM\OneToMany(targetEntity="Category_relationship", mappedBy="objectId", cascade={"persist", "remove"}, orphanRemoval=TRUE)
*/
protected $categories;
}
I want to create a system where I can assign posts to a category but the category table can only contain 1 entry about the category. I also want to be able to use expressions link;
Post->categories
Category->posts
or
Post->AddCategory()
Category->AddPost()
Thanks for your help.
It seems that you want a simple many-to-many relationship.
Every post can have multiple categories, and every category have list of related posts. Many to many handles pivot table by itself.
So, in Post entity you have to declare relationship that way:
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="posts")
* #ORM\JoinTable(name="PostsCategories",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="category_id", referencedColumnName="id")}
* )
**/
protected $categories;
Remember about using Doctrine\ORM\Mapping with ORM alias (you don't have to import all subclasses separately):
use Doctrine\ORM\Mapping as ORM;
After that, you need to create a new ArrayCollection in class constructor:
public function __construct()
{
$this->categories = new ArrayCollection();
}
And add proper methods, like addCategory:
public function addCategory(Category $category)
{
$this->categories[] = $category;
return $this;
}
You can also add them automatically with:
php app/console doctrine:generate:entities BundleName:EntityName
Same thing in Category entity, but with a little different definiton:
/**
* #ORM\ManyToMany(targetEntity="Post", mappedBy="categories")
**/
protected $posts;
You can find all of these information in Doctrine docs

Abstract Doctrine association mapping without discriminator (legacy DB)

I'm working with a legacy database (that means no schema changes!) and I need to create a associations between the the Doctrine entities involved. I'll describe the data structure first and then explain what I've tried.
The database has a user table with various other tables also storing user related info. Eg:
siteUser has:
contentId (PK)
firstName
lastName
username
password
...
and siteUser entities have metadata in this system which is along the lines of:
metadataId (PK)
title
description
keywords
createDate
publishDate
contentId
contentTable (discriminator)
...
Almost everything in the database can have Metadata by storing it's PK in the metadata.contentId field and the table name in the metadata.contentTable field. Note that metadata.contentId is not a foreign key, these must have been alien to the DBA as I'm yet to see a single one.
Users on the system can save information they find relevant to them so that they can come back to the system later and don't have to go hunting for the same information again.
This is done with content types called conLink, conVideo, conLeaflet stored as database entities (which have metadata).
For example a conVideo looks like this:
contentId (PK)
embedCode
The way users can store mark this information as being relevant to them is by the system storing it in a link table called userSavedContent:
userSavedContentId (PK)
userId
metadataId
Note that userSavedContent.userId and userSavedContent.metadataId are also not foreign key constraints.
THE APPROACH!
I need to get user's saved content. In SQL this is no problem!
SELECT
metadata.title,
conVideo.embedCode
FROM
userSavedContent
INNER JOIN
metadata ON userSavedContent.metadataId = metadata.metadataId
INNER JOIN
conVideo ON conVideo.contentId = metadata.contentId
WHERE userSavedContent.userId = 193745
AND metadata.contentTable = 'conVideo'
However doing this in Doctrine is more complicated because the value of metadata.contentTable could potentially be any of the conLink, conVideo, conLeaflet entities.
So my application is built using Symfony2 (and Doctrine) and I have models defined for all of the above entities.
In this Metadata is an abstract class with a discriminator on metadata.contentTable:
/**
*
* #ORM\Table(name="metadata")
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="contentTable", type="string")
* #ORM\DiscriminatorMap(
* {
* "conLink" = "MyApp\Bundle\DataApiBundle\Entity\Metadata\ConLinkMetadata",
* "conVideo" = "MyApp\Bundle\DataApiBundle\Entity\Metadata\ConVideoMetadata",
* "siteUser" = "MyApp\Bundle\DataApiBundle\Entity\Metadata\SiteUserMetadata"
* }
* )
*/
abstract class Metadata
The ConVideoMetadata class extends Metadata and adds a content property that associates the ConVideo entity to it:
/**
* #var ContentType $content
*
* #ORM\OneToOne(
* targetEntity="MyApp\Bundle\DataApiBundle\Entity\ContentType\ConVideo",
* inversedBy="metadata",
* cascade={"persist", "remove"}
* )
* #ORM\JoinColumn(name="contentId", referencedColumnName="contentId")
*/
protected $content;
Now the userSavedContent entity has metadata property to associated it to an item of metadata.
/**
* #var Metadata $metadata
*
* #ORM\ManyToOne(
* targetEntity="MyApp\Bundle\DataApiBundle\Entity\Metadata",
* inversedBy="userSavedContent"
* )
* #ORM\JoinColumn(name="id", referencedColumnName="metadataId")
*/
protected $metadata;
And finally the siteUser is related to userSavedContent by the following property on it's entity:
/**
* #ORM\OneToMany(
* targetEntity="MyApp\Bundle\DataApiBundle\Entity\UserSavedContent",
* mappedBy="siteUser",
* cascade={"persist", "remove"},
* orphanRemoval=true
* )
* #ORM\JoinColumn(name="contentId", referencedColumnName="userId")
*/
private $userSavedContentItems;
THE PROBLEM!
In my siteUserRepository class I now need to query for a siteUser and all it's saved content items:
$builder = $this->createQueryBuilder('s')
->select('s', 'm', 'usc', 'uscm', 'uscc')
->innerJoin('s.metadata', 'm')
->leftJoin('s.userSavedContentItems', 'usc')
->leftJoin('usc.metadata', 'uscm')
->leftJoin('uscm.content', 'uscc');
return $builder;
This doesn't work!
"[Semantical Error] Error: Class MyApp\Bundle\DataApiBundle\Entity\Metadata has no association named content"
This makes sense of course since MyApp\Bundle\DataApiBundle\Entity\Metadata doesn't have the content property, it's child MyApp\Bundle\DataApiBundle\Entity\Metadata\ConVideoMetadata is the one with that association. I thought Doctrine would have been able to work this out but apparently not.
So my question is:
Is this approach very wrong? And if not what can I do to make that association/query work?
The fix for this issue was to get Doctrine to eagerly fetch the concrete metadata->content entities. I could declare these explicitly but used Doctrine's MetadataFactory to get the Metadata entity's discriminator for the list of all possible content types.
$metadataFactory = $this->getEntityManager()->getMetadataFactory();
$metadataMetadata = $metadataFactory->getMetadataFor('MyApp\Bundle\DataApiBundle\Entity\Metadata');
foreach ($metadataMetadata->discriminatorMap as $contentEntity) {
$builder->getQuery()
->setFetchMode(
$contentEntity,
'content',
ClassMetadata::FETCH_EAGER
);
}

NULL inserted while Persisting new Entity, Mapping table column to 2 tables. doctrine 2

I need to map the same column to 2 differences tables (lets say normal and extended).
/**
* #var ItemValue
*
* #OneToOne(targetEntity="ItemValue")
* #JoinColumn(name="id_value", referencedColumnName="id_value")
*/
private $value;
/**
* #var ItemValueExtended
*
* #OneToOne(targetEntity="ItemValueExtended")
* #JoinColumn(name="id_value", referencedColumnName="id_value")
*/
private $valueExtended;
/**
* #var string $isExtended
*
* #Column(name="is_extended", type="string", nullable=false)
*/
private $isExtended = 'YES';
I have no problem with joining data based on the isExtended attribute using DQL:
"SELECT id,idv FROM ItemData id
JOIN id.value idv WHERE id.isExtended='NO'";
and
"SELECT id,idv FROM ItemData id
JOIN id.valueExtended idv WHERE id.isExtended='YES'";
but when ever I want to persist a new object, NULL is inserted in id_value column ?!!
$oValue = ItemValue();
.
.
$oData = new ItemData();
$oData->setValue($oValue);
.
.
.
$em->persist($oData);
$em->flush();
Any Idea ?
From Doctrine2 documentation:
In the case of bi-directional associations you have to update the
fields on both sides.
One possible solution would be:
$oData = new ItemData();
$oData->setValue($oValue);
$oValue->setData($oData);
but it's tedious. Another better one is set the cascade option on both sides of the one-to-one association:
#OneToOne(targetEntity="ItemValue"), cascade={"persist", "remove"})
This way your code will work. You can choose the appropriate cascade options looking at here.
In the case where both the parent and child entity are new entities (neither have been persisted) a PrePersist lifecycle event on the parent can help:
/**
* ....
*
* #ORM\HasLifecycleCallbacks
*/
class ParentEntity {...
/**
* #ORM\PrePersist()
*/
public function prePersist() {
foreach($this->getChildEntities() as $childEntity) {
$childEntity->setParent($this);
}
}
-
class ChildEntity {
....
This will automatically create the child -> parent relationship upon saving the parent.
In many instances Doctrine will then be able to work out the rest at the SQL level.

Categories