Association errors with Doctrine after upgrade - doctrine-orm

I'm working on upgrading a product from Symfony 2.7 to 4.2 (currently at 3.4) and am getting stuck with some existing associations.
The field AppBundle\Entity\User#currentBillingAgreement is on the owning side of a bi-directional relationship, but the specified mappedBy association on the target-entity AppBundle\Entity\BillingAgreement# does not contain the required 'inversedBy' attribute.
If association AppBundle\Entity\User#currentBillingAgreement is one-to-one, then the inversed side AppBundle\Entity\BillingAgreement#user has to be one-to-one as well.
The User entity has these associations:
/**
* #var BillingAgreement
* #ORM\OneToOne(
* targetEntity="AppBundle\Entity\BillingAgreement",
* inversedBy="user",
* cascade={"persist"}
* )
* #ORM\JoinColumn(
* name="currentBillingAgreementID",
* referencedColumnName="billingAgreementID"
* )
*/
protected $currentBillingAgreement;
/**
* #var ArrayCollection
* #ORM\OneToMany(
* targetEntity="AppBundle\Entity\BillingAgreement",
* mappedBy="user",
* cascade={"persist"}
* )
* #Serializer\Exclude()
*/
protected $billingAgreements;
and BillingAgreement has this:
/**
* #var User
* #ORM\ManyToOne(
* targetEntity="AppBundle\Entity\User",
* inversedBy="billingAgreements"
* )
* #ORM\JoinColumn(
* name="userID",
* referencedColumnName="userID",
* nullable=false
* )
*/
protected $user;
When I add a OneToOne mapping to BillingAgreement::$user (#ORM\OneToOne(targetEntity="AppBundle\Entity\User", inversedBy="currentBillingAgreement")), I get a new error:
The field AppBundle\Entity\BillingAgreement#user is on the owning side of a bi-directional relationship, but the specified mappedBy association on the target-entity AppBundle\Entity\User# does not contain the required 'inversedBy' attribute.
and the original 2 errors remain.

You can make the OneToOne association unidirectional by removing inversedBy="user", from the annotation.
or
Use a different field for each association on BillingAgreement entity:
/**
* #var User
* #ORM\ManyToOne(
* targetEntity="AppBundle\Entity\User",
* inversedBy="billingAgreements"
* )
* #ORM\JoinColumn(
* name="userID",
* referencedColumnName="userID",
* nullable=false
* )
*/
protected $user;
/**
* #var User
* #ORM\OneToOne(targetEntity="AppBundle\Entity\User", inversedBy="currentBillingAgreement")
*/
protected $singleUser;
and in User entity:
/**
* #var BillingAgreement
* #ORM\OneToOne(
* targetEntity="AppBundle\Entity\BillingAgreement",
* inversedBy="singleUser",
* cascade={"persist"}
* )
* #ORM\JoinColumn(
* name="currentBillingAgreementID",
* referencedColumnName="billingAgreementID"
* )
*/
protected $currentBillingAgreement;
/**
* #var ArrayCollection
* #ORM\OneToMany(
* targetEntity="AppBundle\Entity\BillingAgreement",
* mappedBy="user",
* cascade={"persist"}
* )
* #Serializer\Exclude()
*/
protected $billingAgreements;
References
Doctrine 2.6 Association Mapping

Related

How to remove a relationship cleanly in Docrine 2?

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"})

Invalid mapping for a one-to-one relation

I'm trying to create a One-to-one bidirectional mapping between two Entitiesbut what I get is this error:
# app/console doctrine:schema:validate --env=dev_local
[Mapping] FAIL - The entity-class 'Belka\AuthBundle\Entity\Globaltoken' mapping is invalid:
* The association Belka\AuthBundle\Entity\Globaltoken#user refers to the inverse side field Belka\AuthBundle\Entity\User#globaltoken which does not exist.
[Mapping] FAIL - The entity-class 'Belka\AuthBundle\Entity\User' mapping is invalid:
* The mappings Belka\AuthBundle\Entity\User#globalToken and Belka\AuthBundle\Entity\Globaltoken#user are inconsistent with each other.
These are my entities:
User entity
/**
* #ORM\Entity
* #ORM\Table(name="app_auth.""User""", schema="app_auth")
* #ORM\Entity(repositoryClass="UserRepository")
* #UniqueEntity("username", groups={"strict"})
* #UniqueEntity("email", groups={"strict"})
* #Assert\GroupSequence({"User", "strict"})
*/
class User implements EncoderAwareInterface
{
/**
* #ORM\Id
* #ORM\Column(type="string")
* #Assert\NotBlank(message = "user.username.not_blank")
* #ORM\GeneratedValue(strategy="NONE")
* #Serializer\Groups({"default"})
*/
private $username;
/**
* #ORM\Id
* #ORM\Column(type="string")
* #Assert\NotBlank(message = "user.email.not_blank")
* #Assert\Email(message = "user.email.not_valid")
* #Serializer\Groups({"default"})
*/
private $email;
/**
* #ORM\Column(type="string", nullable=true)
* #Serializer\Groups("personal")
*/
private $password;
/**
* #ORM\Column(type="string")
* #Serializer\Groups({"default"})
*/
private $name;
/**
* #ORM\Column(type="string")
* #Serializer\Groups({"default"})
*/
private $surname;
/**
* #ORM\Column(type="string", length=5)
* #Serializer\Groups({"default"})
*/
private $lang;
/**
* #ORM\Column(type="boolean")
* #Serializer\Groups({"strict_adm"})
*/
private $deleted = false;
/**
* #ORM\OneToOne(targetEntity="Globaltoken", mappedBy="user", cascade={"persist"})
* #Serializer\Groups({"strict"})
*/
private $globalToken;
<removed methods>
}
Globaltoken entity
/**
* #ORM\Entity
* #ORM\Table(name="app_auth.""Globaltoken""", schema="app_auth")
* #ORM\Entity(repositoryClass="GlobaltokenRepository")
* #UniqueEntity("token", groups={"strict"})
* #UniqueEntity("last_use", groups={"strict"})
* #Assert\GroupSequence({"Globaltoken", "default", "strict"})
* #ORM\HasLifecycleCallbacks()
*/
class Globaltoken
{
//10 minutes, expressed in seconds
const VALIDITYINTERVAL = 600;
/**
* #ORM\Id
* #ORM\Column(type="string")
* #ORM\GeneratedValue(strategy="NONE")
* #Serializer\Groups({"strict"})
*/
private $token;
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="globaltoken")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="owned_by_username", referencedColumnName="username"),
* #ORM\JoinColumn(name="owned_by_email", referencedColumnName="email")
* })
* #Serializer\Groups({"strict"})
*/
private $user;
/**
* #ORM\Column(type="datetime")
* #Assert\NotBlank()
* #Serializer\Groups({"strict"})
*/
private $last_use;
/**
* #Serializer\Groups({"default"})
*/
private $expiring;
<removed methods>
}
what do you think it could be?
Got it. The uppercase-typo is the responsible of everything:
User::globalToken is NOT what inversedBy="globaltoken" expects. User::globaltoken is.
I fell into one of the most classic pitfalls in IT development I'd say that could drive yourself into distraction. I'm quite disappointed by the PHPStorm's support about Symfony annotations though.

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.

Error Creating Many to Many Relationship Using Doctrine 2

I'm trying to generate the schema for my database using Doctrine 2's ZF2 module but with the following definition:
/**
* #ORM\ManyToMany(targetEntity="Tag")
* #ORM\JoinTable(name="Manytomany_Issuetag",
* #ORM\joinColumns={#ORM\JoinColumn(name="IssueId", referencedColumnName="id")},
* #ORM\inverseJoinColumns={#ORM\JoinColumn(name="TagId", referencedColumnName="id")}
* )
*/
protected $tags;
When I run vendor/bin/doctrine-module orm:schema-tool:update --dump-sql I receive the following error:
Annotation #ORM\joinColumns is not allowed to be declared on property Application\Entity\Issue::$tags. You may only use this annotation on these code elements: PROPERTY
Edit: As requested here is the working annotation
/**
* #ORM\ManyToMany(targetEntity="Tag")
* #ORM\JoinTable(name="Manytomany_Issuetag",
* joinColumns={#ORM\JoinColumn(name="IssueId", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="TagId", referencedColumnName="id")}
* )
*/
protected $tags;
I think you need to drop a couple of the #ORM\ declarations, it should look like this (obviously without my comments)
/**
* #ORM\ManyToMany(targetEntity="Tag")
* #ORM\JoinTable(name="Manytomany_Issuetag",
* joinColumns={#ORM\JoinColumn(name="IssueId", referencedColumnName="id")},
* ^ drop the #ORM\
* inverseJoinColumns={#ORM\JoinColumn(name="TagId", referencedColumnName="id")}
* ^ drop the #ORM\
* )
*/
protected $tags;

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!