How to remove a relationship cleanly in Docrine 2? - doctrine-orm

There are entities Endpoint, EndpointServerConfig, and Server:
/**
* Server
*
* #ORM\Table(
* name="server",
* indexes={
* #ORM\Index(name="fk_server_server_type_idx", columns={"server_type_id"}),
* #ORM\Index(name="fk_server_cluster_idx", columns={"cluster_id"})
* }
* )
* #ORM\Entity
*/
class Server
{
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=32, nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="NONE")
*
* #Groups({"export"})
*/
protected $name;
/**
* #var EndpointServerConfig[]
*/
protected $endpointServerConfigs;
}
/**
* EndpointServerConfig
*
* #ORM\Table(name="endpoint_server_config", indexes={
* #ORM\Index(name="fk_endpoint_server_config_server_idx", columns={"server_name"})}
* )
* #ORM\Entity
*/
class EndpointServerConfig
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #var Server
*
* #ORM\ManyToOne(targetEntity="Server")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="server_name", referencedColumnName="name")
* })
*
* #Groups({"export"})
*/
protected $server;
/**
* #var Endpoint
*
* #ORM\OneToOne(targetEntity="Endpoint", mappedBy="endpointServerConfig")
*/
protected $endpoint;
}
/**
* Endpoint
*
* #ORM\Table(
* name="endpoint",
* indexes={
* ...
* #ORM\Index(name="fk_endpoint_endpoint_server_config_idx", columns={"endpoint_server_config_id"}),
* ...
* }
* )
* #ORM\Entity
* ...
*/
class Endpoint
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
}
Now I want to update an entity (e.g. Foo), that contains an Endpoint. Among other changes I want to remove the reference to the Server from the Endpoint's EndpointServerConfig. That means for the database: The endpoint_server_config.server needs to be set to NULL.
I load the Foo to a Zend\Form, disable the server and submit the changes. On the server side I unset the EndpointServerConfig#server over Foo:
/** #var Foo $myFoo */
if(! $myFoo->getEndpoint()->getEndpointServerConfig()->getServer() || ! $myFoo->getEndpoint()->getEndpointServerConfig()->getServer()->getName()) {
$myFoo->getEndpoint()->getEndpointServerConfig()->setServer(null);
}
$this->entityManager->persist($myFoo);
$this->entityManager->flush($myFoo);
It leads to an error:
An exception occurred while executing 'UPDATE server SET name = ? WHERE name = ?' with params ["", "someservername"]:
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`mydb`.`endpoint_server_config`, CONSTRAINT `fk_endpoint_server_config_server` FOREIGN KEY (`server_name`) REFERENCES `server` (`name`) ON DELETE NO ACTION ON UPDATE NO ACTION)
That means, Doctrine tries to UPDATE the Server, instead of just to remove the reference to it from the EndpointServerConfig. But why?
Only when I manually set endpoint_server_config.server_name to NULL (directly in the database), I can save the changes via form and Doctrine.
How to get it working?
EDIT
Just noticed, that I get the same problem on every update of the EndpointServerConfig. So not only on setServer(null), bu also when I try to set a new Server. In this case the attempts leads to the error:
An exception occurred while executing 'UPDATE server SET name = ? WHERE name = ?' with params ["newservername", "someservername"]:
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`mydb`.`endpoint_server_config`, CONSTRAINT `fk_endpoint_server_config_server` FOREIGN KEY (`server_name`) REFERENCES `server` (`name`) ON DELETE NO ACTION ON UPDATE NO ACTION)

Add following:-
cascade={"persist", "remove"}
on your relation.
#ORM\ManyToOne(targetEntity="Server", cascade={"persist", "remove"})

Related

Doctrine delete items from collection - different column name

I have problem with Doctrine collection. In PromoCodeTerm entity I have #OneToMany $days collection, but when doctrine delete items from this collection, generates SQL query with different column name, like a promoCodeTerm instead of promo_code_term_id.
class PromoCodeTerm {
/**
* #var string
* #ORM\Column(name="id", type="guid")
* #ORM\Id()
*/
private $id;
/**
* #var PromoCodeTermDay[]
* #ORM\OneToMany(targetEntity="AppBundle\Entity\PromoCode\PromoCodeTermDay", mappedBy="promoCodeTerm", cascade={"persist"}, orphanRemoval=true)
*/
private $days;
}
class PromoCodeTermDay {
/**
* #var integer
* #ORM\Column(name="day_id", type="integer")
* #ORM\Id()
*/
private $day;
/**
* #var PromoCodeTerm
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\PromoCode\PromoCodeTerm", inversedBy="days")
* #ORM\JoinColumn(name="promo_code_term_id", referencedColumnName="id", onDelete="CASCADE")
* #ORM\Id()
*/
private $promoCodeTerm;
}
What am I doing wrong?
An exception occurred while executing 'DELETE FROM `promo_code__promo_code_term_day` WHERE `day` = ? AND `promoCodeTerm` = ?' with params [1, \"c2fce03a-0507-47ca-a060-362cce683b06\"]:\n\nSQLSTATE[42S22]: Column not found: 1054 Unknown column 'promoCodeTerm' in 'where clause'

Doctrine 2 ManyToOne multipy JoinColumns

I have next entities:
/**
* Customer
*
* #ORM\Table(name="customer")
* #ORM\Entity()
*/
class Customer
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var Account[]|ArrayCollection
*
* #ORM\OneToMany(targetEntity="Account", mappedBy="customer")
*/
private $accounts;
/**
* #var Account
*
* #ORM\OneToOne(targetEntity="Account")
*
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id",referencedColumnName="customer_id"),
* #ORM\JoinColumn(name="default_account_id", referencedColumnName="id")
* })
*/
private $defaultAccount;
}
/**
* Account
*
* #ORM\Table(name="account")
*/
class Account
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var Customer
*
* #ORM\ManyToOne(targetEntity="Customer", inversedBy="accounts")
* #ORM\JoinColumn(nullable=false)
*/
private $customer;
}
Idea that customer has multiply accounts, onr of the account should be default for customer. I would like to use foreign key with multiply fields to ensure that default customer account belongs to this customer, but I'm getting next error:
Column name `customer_id` referenced for relation from MyBundle\Entity\Customer towards MyBundle\Entity\Account does not exist.
Actually there is no "customer_id" field on ORM level, because it is "customer.id" but I dont know how to reference it.
It seems that line #ORM\JoinColumn(name="id",referencedColumnName="customer_id"), is redundant. Try to configure this field as following:
/**
* #var Account
*
* #ORM\OneToOne(targetEntity="Account")
*
* #ORM\JoinColumn(name="default_account_id", referencedColumnName="id")
*/
private $defaultAccount;
By the way, I think it would be better if you just added a boolean column is_default into your Account entity.

Symfony2 Form with Doctrine Relationship Persistence Issue

So I have 3 entities with their properties.
A USER which has MANY USERSKILLS with each ONE having a single corresponding SKILL
Here are the entities:
/**
* #ORM\Table(name="skills")
* #ORM\Entity()
*/
class Skill
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=30, unique=true)
*/
private $name;
/**
* #ORM\Column(name="active", type="boolean")
* #var bool
*/
private $active = false;
/**
* #ORM\OneToMany(targetEntity="UserSkill", mappedBy="skill")
*/
private $userSkills;
}
/**
* #ORM\Table(name="user_skill")
* #ORM\Entity()
*/
class UserSkill
{
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="User", inversedBy="skills")
* #var User
*/
private $user;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Skill", inversedBy="userSkills")
* #var Skill
*/
private $skill;
/**
* #ORM\Column(type="integer")
* #var int
*/
private $level = 0;
}
/**
* StartupDriven\UserBundle\Entity\User
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="StartupDriven\UserBundle\Entity\UserRepository")
*/
class User implements AdvancedUserInterface, \Serializable
{
/**
* #ORM\OneToMany(targetEntity="UserSkill", mappedBy="user")
* #var ArrayCollection
*/
private $skills;
}
They are created using 2 symfony form objects which works fantastically.
The problem is when I go to persist these objects in the controller.... I get an error
$form->handleRequest($request);
if ($form->isValid()) {
/**
* #var User $user
*/
$user = $form->getData();
/**
* #var UserSkill $userSkill
*/
foreach ($user->getSkills() as $userSkill) {
// No updating skills... Only new ones.
if (!$userSkill->getId()) {
// Check skill name match.
if ($matched_skill = $em->getRepository('StartupDrivenUserBundle:Skill')->findOneBy(array('name' => $userSkill->getName()))) {
$userSkill->setSkill($matched_skill);
}
else {
// No match. Create new generic skill.
$em->persist($userSkill->getSkill());
// THE ERROR HAPPENS ON THE FOLLOWING LINE!
$em->flush();
}
// Set the User
$userSkill->setUser($user);
// Persist the user skill.
$em->persist($userSkill);
$em->flush();
}
}
$em->persist($user);
$em->flush();
Error Message:
A new entity was found through the relationship 'StartupDriven\UserBundle\Entity\User#skills' that was not configured to cascade persist operations for entity: StartupDriven\UserBundle\Entity\UserSkill#0000000053e8628e00007f7f2fd56e1f. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'StartupDriven\UserBundle\Entity\UserSkill#__toString()' to get a clue.
I have tried every combination of persist & flush that I can think of. I have tried the cascade option above (which gives a different error that the "user id which is required by the primary key rules is not set")... I am completely lost. It seems like such a simple relationship... Where am I going wrong?

Unwanted cascade delete by Doctrine2

I am working on a product catalog, and have two entities, PcatSalesItem and PcatCategory with a many-to-many relationship between them. If I delete a category, and there are still sales items associated with it, I want an exception to be thrown, I do NOT want cascading delete. On the RDBMS level (PostgreSQL), in the join table, I have set the foreign keys to "ON DELETE RESTRICT". However, when I delete a category that has sales items, Doctrine does a cascading delete. Nowhere have I specified cascade=remove to Doctrine!
Here are the entities:
/**
* PcatSalesItem
*
* #ORM\Table(name="pcat_sales_item")
* #ORM\Entity
* #Gedmo\Loggable(logEntryClass="Qi\Bss\BaseBundle\Entity\Business\LogEntryBusiness")
*/
class PcatSalesItem
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="pcat_sales_item_id_seq", allocationSize=1, initialValue=1)
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=64, nullable=false)
* #Gedmo\Versioned
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="description", type="text", nullable=true)
* #Gedmo\Versioned
*/
private $description;
/**
* #var array $categories
*
* #ORM\ManyToMany(targetEntity="PcatCategory")
* #ORM\JoinTable(name="pcat_category_x_sales_item",
* joinColumns={#ORM\JoinColumn(name="sales_item_id", referencedColumnName="id", onDelete="RESTRICT")},
* inverseJoinColumns={#ORM\JoinColumn(name="category_id", referencedColumnName="id", onDelete="RESTRICT")}
* )
*/
private $categories;
...
}
/**
* PcatCategory
*
* #ORM\Table(name="pcat_category")
* #ORM\Entity
* #Gedmo\Loggable(logEntryClass="Qi\Bss\BaseBundle\Entity\Business\LogEntryBusiness")
*/
class PcatCategory
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="pcat_category_id_seq", allocationSize=1, initialValue=1)
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=64, nullable=false)
* #Gedmo\Versioned
*/
private $name;
/**
* #var array $salesItems
*
* #ORM\ManyToMany(targetEntity="PcatSalesItem")
* #ORM\JoinTable(name="pcat_category_x_sales_item",
* joinColumns={#ORM\JoinColumn(name="category_id", referencedColumnName="id", onDelete="RESTRICT")},
* inverseJoinColumns={#ORM\JoinColumn(name="sales_item_id", referencedColumnName="id", onDelete="RESTRICT")}
* )
*/
private $salesItems;
....
}
Here is the code I use to delete a category:
$em = $this->getDoctrine()->getManager();
$cat = $em->getRepository('QiBssBaseBundle:PcatCategory')->find(15);
$em->remove($cat);
$em->flush();
Any help will be greatly appreciated!

OneToMany Relationship Only Returns 1 Row

I am looking for some help as to why my OneToMany relationship in doctrine only returns one value. My data model has three tables. Users, Authorization, and Applications. The Authorization table is the glue that maps users to applications and also contains an accesslevel field to indicate their level of access for that application.
I have a user that has three entries in authorization for three different applications, but for some reason, doctrine is only loading 1 of those authorization records. I've included my full data model below. The attribute in question is "authorization" in the Webusers table.
Anyone know what I am doing wrong?
class Webusers {
/**
* #var integer
*
* #ORM\Column(name="userid", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $userid;
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="Applications", mappedBy="userid")
*/
private $applications;
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\OneToMany(targetEntity="Authorization", mappedBy="user")
*/
private $authorization;
/**
* Constructor
*/
public function __construct() {
$this->applications = new \Doctrine\Common\Collections\ArrayCollection();
$this->authorization = new \Doctrine\Common\Collections\ArrayCollection();
}
class Authorization {
/**
* #var integer
*
* #ORM\Column(name="accesslevel", type="integer", nullable=false)
*/
private $accesslevel;
/**
* #var integer
*
* #ORM\Column(name="applicationid", type="integer", nullable=false)
* $ORM\Id
*/
private $applicationid;
/**
* #var integer
*
* #ORM\Column(name="userid", type="integer", nullable=false)
* #ORM\Id
*/
private $userid;
/**
* #var \Webusers
*
* #ORM\ManyToOne(targetEntity="Webusers")
* #ORM\JoinColumn(name="userid", referencedColumnName="userid")
*/
private $user;
}
class Applications {
/**
* #var integer
*
* #ORM\Column(name="applicationid", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $applicationid;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=50, nullable=false)
*/
private $name;
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="Webusers", inversedBy="applicationid")
* #ORM\JoinTable(name="authorization",
* joinColumns={
* #ORM\JoinColumn(name="applicationid", referencedColumnName="applicationid")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="userid", referencedColumnName="userid")
* }
* )
*/
private $userid;
/**
* Constructor
*/
public function __construct()
{
$this->userid = new \Doctrine\Common\Collections\ArrayCollection();
}
}
I was able to fix this by making sure I specified both the webusers and application relationship as ManyToOne AND giving them the #ORM\Id property since they make up a composite primary key.
I think the main reason this was failing was because I had not associated authorization to applications.
The key points of my new model are as follows:
class Webusers {
/**
* #var \Doctrine\Common\Collections\Collection
* #ORM\OneToMany(targetEntity="Authorization", mappedBy="user")
*/
private $authorization;
}
class Authorization {
/**
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Applications")
* #ORM\JoinColumn(name="applicationid", referencedColumnName="applicationid")
*/
private $application;
/**
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Webusers")
* #ORM\JoinColumn(name="userid", referencedColumnName="userid")
*/
private $user;
}
class Applications {
/**
* #ORM\OneToMany(targetEntity="Authorization", mappedBy="application")
*/
private $authorization;
}