Delete link between two table with a query builder - php

I have made a queryBuilder inside an entity repository to delete a link between two table.
I have this two entities
Domain :
/**
* #var int
*
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="string", length=64)
* #Assert\NotBlank
* #Assert\Length(max="64")
* #AppAssert\DomainName
*/
private $name;
// Some other fields
/**
* #var SshKey[]|ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\SshKey", inversedBy="domains")
* #ORM\JoinTable(name="domain_sshkey",
* joinColumns={#ORM\JoinColumn(referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="key_id", referencedColumnName="id")}
* )
*/
private $sshKeys;
And SshKeys :
/**
* #var int
*
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime
*
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
private $createdAt;
// Other fields
/**
* #var Domain[]|ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Domain", mappedBy="sshKeys")
*/
private $domains;
I am trying to delete links between this two tables when SshKeys id is in sshKeys field inside domain table.
So I made this query builder in my DomainRepository
public function deleteSshkeyDomainLink($invalidSshkey)
{
$qb = $this->createQueryBuilder('d');
$qb->delete()
->where($qb->expr()->in('ssh.id', ':ssh_keys_id'))
->setParameter('ssh_keys_id', $invalidSshkey)
->join('d.sshKeys', 'ssh')
;
return $qb->getQuery()->execute();
}
But this QB return this error
[Doctrine\ORM\Query\QueryException]
[Semantical Error] line 0, col 39 near 'ssh.id IN(:s': Error: 'ssh' is not defined.
[Doctrine\ORM\Query\QueryException]
DELETE AppBundle\Entity\Domain d WHERE ssh.id IN(:ssh_keys_id)
I don't understand why this is returning ssh is not defined because I have made a join with this alias.
This query builder should work ? I really don't know how too fix this.
Thanks for your help.

why do you want to delete Domain when you just need to delete sshKey from Domain (link between them)?
In Domain entity you can define method removeSshKey like this for example
public function removeSshKey(SshKey $key)
{
$this->sshKeys->removeElement($key);
return $this;
}
Then in controller where you want to delete the link between entities you should call it something like this
$domain = $this->getDoctrine()->getRepository('Domain')->find($domainId);
foreach ($domain->getSshKeys() as $sshKey)
{
if ($sshKey->getId() == $invalidSshKeyId)
{
$domain->removeSshKey($sshKey);
}
}
$em = $this->getDoctrine()->getManager();
$em->flush();
this should delete the link

Related

Doctrine - OneToMany relation, all result row doesn't fetch in object

I try to get all my objects DemandCab with their children object (DecisionCab).
My 2 entities
/**
* DemandCab.
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="DemandCabRepository")
*/
class DemandCab
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var DecisionCab
*
* #ORM\OneToMany(targetEntity="\My\CabBundle\Entity\DecisionCab", mappedBy="demandCab")
*/
private $decisionsCab;
/**
* #var \DateTime
*
* #ORM\Column(name="startDate", type="datetime")
*/
private $startDate;
/**
* #var \DateTime
*
* #ORM\Column(name="endDate", type="datetime", nullable=true)
*/
private $endDate;
/**
* #var int
*
* #ORM\Column(name="followup", type="integer", nullable=true)
*/
private $followup;
/**
* #var InfoCab
*
* #ORM\ManyToOne(targetEntity="\My\CabBundle\Entity\InfoCab", inversedBy="demandsCab")
*/
private $infoCab;
}
/**
* DecisionCab.
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="DecisionCabRepository")
*/
class DecisionCab
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var DemandCab
*
* #ORM\ManyToOne(targetEntity="\My\CabBundle\Entity\DemandCab", inversedBy="decisionsCab")
*/
private $demandCab;
/**
* #var bool
*
* #ORM\Column(name="decision", type="boolean", nullable=true)
*/
private $decision;
/**
* #var string
*
* #ORM\Column(name="motif", type="string", length=500, nullable=true)
*/
private $motif;
/**
* #var string
*
* #ORM\Column(name="role", type="string", length=255)
*/
private $role;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="datetime", nullable=true)
*/
private $date;
/**
* #var DemandCab
*
* #ORM\ManyToOne(targetEntity="\My\CabBundle\Entity\DemandCab", inversedBy="decisionsCab")
*/
private $demandCab;
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="\My\CabBundle\Entity\User", inversedBy="decisionsCab")
*/
private $user;
}
In my DemandCabRepository
public function findAllCompleted(){
$qb = $this->createQueryBuilder("dem");
$qb->select('dem, dec');
$qb->leftJoin("dem.decisionsCab", "dec");
$qb->andWhere("dem.completed = 1");
$qb->orderBy("dem.startDate", "DESC");
return $qb->getQuery()->getResult();
}
My DemandCab data
My DecisionCab data
When i dump result, only 2 decisions appear ...
... whereas when i use getArrayResult, i have my 4 decisions ...
The query is good but i dont understand why hydration remove DecisionCab object with attribute decision at 0 or 1 (only null are display).
I would like to understand why and is there a solution to get DemandCab object with all DecisionCab children object.
Thanks
I am able to reproduce your issue, but I am not sure if this is your case.
Anyway, my assumption is that you query the demand Entity joined with decision relation at least once with the help of a query builder. Maybe this is done in your action, in an event listener or somewhere else in your code.
So you may have something like:
$qb = $this->getDoctrine()
->getRepository(DemandCab::class)->createQueryBuilder("dem");
$qb->select('dem, dec');
$qb->leftJoin("dem.decisionsCab", "dec");
$qb->andWhere("dec.decision IS NULL");
$qb->orderBy("dem.startDate", "DESC");
$results = $qb->getQuery()->getResult(); // <-- the decisionsCab collection is hydrated but filtered
$qb2 = $this->getDoctrine()
->getRepository(DemandCab::class)->createQueryBuilder("dem");
$qb2->select('dem, dec');
$qb2->leftJoin("dem.decisionsCab", "dec");
$qb2->andWhere("dem.completed = 1");
$qb2->orderBy("dem.startDate", "DESC");
$q = $qb2->getQuery();
//$q->setHint(Query::HINT_REFRESH, true);
$results = $q->getResult();
The issue is in Doctrine\ORM\Internal\Hydration\ObjectHydrator, it has the property "initializedCollections" where already initialized collections are kept and the collections are hashed by the parent entity type and the entity itself. Unfortunately in the above case, the heydrator does not understand that the collection is filtered in the 1st query and uses it in the 2nd query in order to avoid rehydration.(github link)
The solution is to cause the query builder to refresh. Try the code:
$qb->orderBy("dem.startDate", "DESC");
$q = $qb->getQuery();
$q->setHint(Query::HINT_REFRESH, true); // <-- Tell the hydrator to refresh
return $q->getResult();
First you have initialize your class with the relation ManyToOne with an ArrayCollection.
And you don't need any of this 'DemandCabRepository'. All the work is done by Doctrine

Doctrine2 One to One Relation and extra table

I have 3 tables:
users, users_profile and extra table users_addresses.
User class:
class User
{
/**
* #ORM\Id()
* #ORM\Column(name="user_id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #var int
*/
private $userId;
/**
* #ORM\OneToOne(targetEntity="Entity\UserProfile", cascade={"persist", "remove"}, mappedBy="user", fetch="LAZY")
* #var Entity\UserProfile
*/
private $profile;
/**
* #param Entity\UserProfile $profile
*/
public function setProfile(UserProfile $profile) {
$this->profile = $profile;
$profile->setUser($this);
return $this;
}
(...)
}
User profile class
class UserProfile
{
/**
* #ORM\Id()
* #ORM\Column(name="profile_id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #var int
*/
private $profileId;
/**
* #ORM\OneToOne(targetEntity="Entity\User", inversedBy="profile")
* #ORM\JoinColumn(name="user_id", referencedColumnName="user_id")
* #var Entity\User
*/
private $user;
/**
* #ORM\ManyToMany(targetEntity="Entity\Address", cascade={"persist"})
* #ORM\JoinTable(name="users_addresses",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="user_id")},
* inverseJoinColumns={#ORM\JoinColumn(name="address_id", referencedColumnName="address_id")}
* )
**/
private $addresses;
/**
* #param Address $address
*/
public function addAddress(Address $address) {
if (!$this->addresses->contains($address)) {
$this->addresses->add($address);
}
return $this;
}
(...)
}
Address class
class Address
{
/**
* #ORM\Id()
* #ORM\Column(name="address_id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #var int
*/
private $addressId;
/**
* #ORM\Column(type="string", length=128, nullable=false)
* #var string
*/
private $city;
(...)
}
Add user with profile data and address
$userAddress = new \Entity\Address();
$userAddress->setStreet($street);
$userAddress->setCity($city);
$userAddress->setCountry($country);
$userAddress->setState($state);
$userAddress->setZipCode($zipCode);
$userProfile = new \Entity\UserProfile();
$userProfile->addAddress($userAddress);
$user = new \Entity\User();
$user->setProfile($userProfile);
$em->persist($user);
$em->flush();
The problem i am having at the moment is that the entities persist ok, but the 'user_id' in the join table ('users_addresses') is not correct ('user_id' = 0, 'address_id' = correct). Table 'addresses' must not contain extra field like 'user_id'. What am I doing wrong?
I need a structure like this:
Your modeling shows that tables users and users_profile share the same primary key, namely user_id, and thus that column is also a foreign key toward users in users_profile.
Hence, you should replace profile_id in the Php annotations by user_id, remove the key auto generation directive, and update (or delete and recreate) the schema.
class UserProfile {
/**
* #ORM\Id
* #ORM\Column(name="user_id", type="integer")
* #var int
*/
private $profileId;
/* ... */
}
Then
$ doctrine orm:info --verbose
Found 3 mapped entities:
[OK] Address
[OK] User
[OK] UserProfile
$ doctrine orm:schema-tool:create
ATTENTION: This operation should not be executed in a production environmnent.
Creating database schema...
Database schema created successfully!
Or use the update command if you already have a schema.
You could also rename the php class attribute ($profileId), for "consistency" with the DB model, but I don't think it matters much, unless you have code which relies on a specific name.

How to add extra WHERE clauses when retrieving relationships in Doctrine2

I have two entities Post and Comment.
Structure:
Post:
id
title
body
Comment:
id
post_id
body
active
class Post
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #ORM\Column(name="body", type="text")
*/
private $body;
/**
* #ORM\OneToMany(
* targetEntity="Comment",
* mappedBy="post"
* )
*/
private $comments;
class Comment
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="body", type="text")
*/
private $body;
/**
* #ORM\ManyToOne(
* targetEntity="Post",
* inversedBy="comments"
* )
* #ORM\JoinColumn(
* name="post_id",
* referencedColumnName="id"
* )
*/
private $post;
As a result when I want to get all comments for a post I use $post->getComments() and it works.
How I can add extra Where clauses into this relationship if I want to get only posts with active = 1.
I know that I can do it by DQL or queryBuilder but I want to know how I can do it by mapping
I think the cleanest way to retrieve only active comments is to use Doctrine's Criteria object in the getComments method of your Post entity
use Doctrine\Common\Collections\Criteria;
and
public function getComments()
{
$criteria = Criteria::create();
$criteria->where(Criteria::expr()->eq('active', 1));
return $this->comments->matching($criteria);
}
Edit
If you want to prevent multiple queries each time you retrieve the active comments, you'll need to store them in a local variable. Instead of modifying getComments, you could add $active_comments and getActiveComments, which will populate $active_comments and only query the db if $active_comments is false.
class Post {
private $active_comments;
public function getActiveComments()
{
if(!$this->active_comments) {
$criteria = Criteria::create();
$criteria->where(Criteria::expr()->eq('active', 1));
$this->active_comments = $this->comments->matching($criteria);
}
return $this->active_comments;
}

Doctrine/Symfony - I have some errors. Something wrong with my relations

I always get an exception with my entities. I tried to play with them. I checked on google which relation is the most appropriate for my entities... But I couldn't find the best configuration.
I'm making a website where you can create some albums.
A user can have multiple albums.So I have an entity Album and I have inside a user property :
/**
* Album
*
* #ORM\Table(name="album")
* #ORM\Entity(repositoryClass="Moodress\Bundle\AlbumBundle\Entity\AlbumRepository")
*/
class Album
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
** #ORM\ManyToOne(targetEntity="Moodress\Bundle\UserBundle\Entity\User")
** #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var \DateTime
*
* #ORM\Column(name="creationDate", type="datetime")
*/
private $creationDate;
/**
* #var \DateTime
*
* #ORM\Column(name="modificationDate", type="datetime")
*/
private $modificationDate;
/**
* #ORM\OneToMany(targetEntity="Moodress\Bundle\AlbumBundle\Entity\Picture", cascade={"persist"}, mappedBy="album")
*/
private $pictures;
/**
* Constructor
*/
public function __construct()
{
$this->creationDate = new \Datetime();
$this->modificationDate = new \Datetime();
}
// Get and set
}
However, When a user subscribe on the website, I create a default album called Upload that I want to keep in the user class.
This is what I tried to do :
/**
* User
*
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Moodress\Bundle\AlbumBundle\Entity\Album", cascade={"persist"})
*/
protected $albumUpload;
// Get and set
}
I have this error :
Undefined index: album in /Users/Sandro/sites/moodress-website/Symfony/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php line 1608. This error appears directly when I serialize any entity that has a user object...
The error is caused by the association annotation of the $albumUpload property in the User class.
Doctrine assumes that you have a $album property in your Album entity when you use a #ORM\OneToOne(targetEntity="Moodress\Bundle\AlbumBundle\Entity\Album") association in your User entity.
Try
/**
** #ORM\ManyToOne(targetEntity="Moodress\Bundle\UserBundle\Entity\User", inversedBy="albums")
** #ORM\JoinColumn(nullable=false)
*/
private $user;
in your Album entity and
/**
* #ORM\OneToMany(targetEntity="Moodress\Bundle\AlbumBundle\Entity\Album", mappedBy="user", cascade={"persist"})
*/
protected $albums;
in your User entity. In addition please add $this->albums = new ArrayCollection() to User::__construct().
Now if you want to add and keep a default album for users you should implement this somewhere in your business logic. You could figure out a way to identify the default album in your collection of albums and prevent deletion especially for this item.
Hope this helps.
My error:
'Undefined index: album'
results from this annotation in the Album class.
/**
* #ORM\OneToMany(
* targetEntity="Moodress\Bundle\AlbumBundle\Entity\Picture",
* cascade={"persist"},
* mappedBy="album"
* )
*/
I have set mappedBy to album but there is no $album property in my Picture class.
I did add this to my Picture class and clear my cache.
/**
* #ORM\ManyToOne(
* targetEntity="Moodress\Bundle\AlbumBundle\Entity\Album",
* inversedBy="pictures"
* )
*/
protected $album;
It solved my problem

DQL query returns: StateFieldPathExpression or SingleValuedAssociationField expected

I have the following DQL query:
public function findByIdJoinedToCodeExample($pageId)
{
$query = $this->getEntityManager()
->createQuery('
SELECT c FROM acmeStyleGuideBundle:codeExample c
JOIN c.PageContent p
WHERE p.codeExample = :cex'
)
->setParameter('cex', $pageId);
try {
return $query->getResult();
} catch (\Doctrine\ORM\NoResultException $e) {
return null;
}
}
It is attempting to retreive data from an entity called codeExample which has a ManyToOne relationship with an entity called PageContent. The relationships seem to be set up correctly as the database is being correctly set up and the fixtures are being populated but when I try to run the above query I am faced with the following error:
An exception has been thrown during the rendering of a template ("[Semantical Error] line 0, col 130 near 'codeExample =': Error: Invalid PathExpression. StateFieldPathExpression or SingleValuedAssociationField expected.") in acmeStyleGuideBundle:Page:pages.html.twig at line 16.
It is being called by the following controller:
// find the current pages code examples (if there are any)
public function findCodeExamplesAction($pageId =10)
{
$em = $this->getDoctrine()->getManager();
$codeExample = $this->getDoctrine()
->getRepository('acmeStyleGuideBundle:codeExample')
->findByIdJoinedToCodeExample($pageId);
return $this->render(
'acmeStyleGuideBundle:Page:codeExample.html.twig',
array(
'Code' => $codeExample
)
);
}
Note: $pageID = 10 is because it was telling me that pageID wasn't being populated. That seems to be a separate issue than this one so I set a default for that for now.
I've been looking at this for hours but I'm still learning Doctrine and Symfony and I just cannot figure this one out by myself.
Here are my entities for codeExample:
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="textExample", type="text")
*/
private $textExample;
/**
* #var string
*
* #ORM\Column(name="codeExample", type="text")
*/
private $codeExample;
/**
* #var integer
*
* #ORM\Column(name="lang", type="integer")
*/
private $lang;
/**
* #ORM\ManyToMany(targetEntity="PageContent", mappedBy="codeExample")
*/
protected $PageContent;
/**
* Constructor
*/
public function __construct()
{
$this->PageContent = new \Doctrine\Common\Collections\ArrayCollection();
}
Here are my entities for PageContent:
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var ArrayCollection $pageSector_Id;
* #ORM\ManyToMany(targetEntity="pageSector", inversedBy="PageContent")
* #ORM\JoinTable(
* name="pageSector_PageContent",
* joinColumns={#ORM\JoinColumn(name="PageContent_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="pageSector_Id", referencedColumnName="id")}
* )
*/
protected $pageSector;
/**
* #var ArrayCollection $pageCategory_Id;
* #ORM\ManyToMany(targetEntity="pageCategory", inversedBy="PageContent")
* #ORM\JoinTable(
* name="pageCategory_PageContent",
* joinColumns={#ORM\JoinColumn(name="PageContent_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="pageCategory_Id", referencedColumnName="id")}
* )
*/
protected $pageCategory;
/**
* #ORM\ManyToOne(targetEntity="pageTypes", inversedBy="PageContent")
* #ORM\JoinColumn(name="pageTypesId", referencedColumnName="id")
*/
protected $pageTypes;
/**
* #var integer
*
* #ORM\Column(name="pageTypesId", type="integer")
*/
private $pageTypesId;
/**
* #ORM\OneToMany(targetEntity="PageContent", mappedBy="parentPage")
*/
private $childPages;
/** #ORM\ManyToOne(targetEntity="PageContent", inversedBy="childPages")
* #ORM\JoinColumn(name="parentPage_id", referencedColumnName="id")
**/
private $parentPage;
/**
* #var string
*
* #ORM\Column(name="pageName", type="string", length=255)
*/
private $pageName;
/**
* #var string
*
* #ORM\Column(name="pageUrl", type="string", length=255)
*/
private $pageUrl;
/**
* #var string
*
* #ORM\Column(name="richText", type="text")
*/
private $richText;
/**
* #var ArrayCollection $pageSector_Id;
* #ORM\ManyToMany(targetEntity="codeExample", inversedBy="PageContent")
* #ORM\JoinTable(
* name="codeExample_PageContent",
* joinColumns={#ORM\JoinColumn(name="PageContent_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="codeExample_Id", referencedColumnName="id")}
* )
*/
protected $codeExample;
Any advice you can give me would be amazing. I'm completely stuck here.
I managed to work it out myself:
$query = $this->getEntityManager()
->createQuery('
SELECT c FROM acmeStyleGuideBundle:codeExample c
JOIN c.PageContent p
WHERE p.codeExample = :cex'
)
->setParameter('cex', $pageId);
Should have been:
$query = $this->getEntityManager()
->createQuery('
SELECT c FROM acmeStyleGuideBundle:codeExample c
JOIN c.PageContent p
WHERE p.id = :cex'
)
->setParameter('cex', $pageId);
This seems odd to me as I thought that the point of objects is to NOT use the id but it worked so that's good enough for me. If there is a better way, please feel free to tell me.

Categories