symfony2: How to remove related entity with doctrine / restrict annotation? - symfony

I have some bit of troubles with delete constraint in an entity.
I have an entity merchandise and an entity vehicle with a relation many to one in merchandise, so a merchandise only could be in one vehicle, and a vehicle could have many merchandise. So I have:
class Merchandise{
/**
* #ORM\ManyToOne(targetEntity="Vehicle",inversedBy="merchandise")
* #ORM\JoinColumn(name="vehicle", referencedColumnName="id")
*/
private $vehicle;
}
class Vehicle{
/**
* #ORM\OneToMany(targetEntity="Merchandise",mappedBy="vehicle")
*/
private $merchandise;
}
What I want to get is that when I try to delete a Merchandise which have a vehicle, the Merchandise couldn't be deleted.
But I don't know how can I put an ORM Level restrict constraint. I tried restrict={"remove"} but it doesn't exist in #ORM\OneToMany.
I also try to put a preRemove function which return false, but it doesn't work :(
Any idea?
Thanks!!!

ManyToOne / inversedBy is the OWNING side of the bidirectional relation from doctrine's point of view - which can lead to confusion.
To resolve your issue add cascade operation to your merchandise entity. example:
/**
* #ORM\ManyToOne(targetEntity="Vehicle",mappedBy="merchandise", cascade={"all"})
*/
cascade can be set to a combination of :
persist
remove
merge
detach
all
Improve further by adding cascade ( ORM-level ) to your Vehicle entity aswell. example:
/**
* #ORM\OneToMany(targetEntity="Merchandise", mappedBy="vehicle", cascade={"persist","remove"})
*/
... or use onDelete ( database-level ) with one of
SET NULL
CASCADE
... like this
/**
* #ORM\OneToMany(targetEntity="Merchandise", inversedBy="vehicle", onDelete="CASCADE")
*/
Now if you remove a Vehicle - the related Merchandise entities will be removed. Added Merchandises will automatically be saved.
... finally update your schema and drop -> re-create your database if constraints have not been updated and errors occur. Make sure both sides use the cascade option.
Read more in the documentation chapter Transitive persistence / Cascade Operations.

Related

On delete of Parent entities - null the association on the other entity

I am trying to solve my issue on Doctrine ORM. I have 2 parent entities: CompanyDoctrineEntity and ServiceDoctrineEntity and 1 entity that are associated with these 2 (but the association is not required) OrderLinkRedirectLogDoctrineEntity. The association in OrderLinkRedirectLogDoctrineEntity is defined by:
class OrderLinkRedirectLogDoctrineEntity {
/**
* #Id
* #Column(type="integer")
* #ORM\GeneratedValue()
*
* #var int $id
*/
private $id;
/**
* Many logs have one company. This is the owning side.
*
* #ManyToOne(targetEntity="CompanyDoctrineEntity", cascade="detach")
* #JoinColumn(name="company_id", referencedColumnName="id")
*
* #var CompanyDoctrineEntity $company
*/
private $company;
/**
* Many logs have one service. This is the owning side.
*
* #ManyToOne(targetEntity="ServiceDoctrineEntity", cascade="detach")
* #JoinColumn(name="service_id", referencedColumnName="id")
*
* #var ServiceDoctrineEntity $service
*/
private $service;
}
My expected behaviour is, whenever either CompanyDoctrineEntity or ServiceDoctrineEntity is removed from the database, the association in the OrderLinkRedirectLogDoctrineEntity will be NULLed, which I believe what the cascade="detach" does, but for some reason, it's not working, as I am getting the following errors:
Fatal error: Uncaught PDOException: SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`test_app2`.`logs_order_link_redirects`, CONSTRAINT `FK_6C1CA74CED5CA9E6` FOREIGN KEY (`service_id`) REFERENCES `app_services` (`id`)) in /Users/arvil/Projects/app2.test/public_html/wp-content/themes/app-theme/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:117
Stack trace:
#0 /Users/arvil/Projects/app2.test/public_html/wp-content/themes/app-theme/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php(117): PDOStatement->execute(NULL)
#1 /Users/arvil/Projects/app2.test/public_html/wp-content/themes/app-theme/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php(1054): Doctrine\DBAL\Driver\PDOStatement->execute()
#2 /Users/arvil/Projects/app2.test/public_html/wp-content/themes/app-theme/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php(656): Doctrine\DBAL\Connection->exe in /Users/arvil/Projects/app2.test/public_html/wp-content/themes/app-theme/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php on line 49
I'm far from an expert on Doctrine, so take this with a grain of salt and test thoroughly.
Your relations are not nullable (defaults to false), which is why your foreign key constraint is complaining: logs_order_link_redirects.service_id (and company_id) isn't allowed to be null. That likely wasn't a problem before because you're not inserting the OrderLinkRedirectLogDoctrineEntity entities without the relationships. If you were to say
$redirectLog = new OrderLinkRedirectLogDoctrineEntity();
$entityManager->persist($redirectLog);
$entityManager->flush();
you'd probably trigger the same error immediately.
Also, I don't believe you want cascade={"detach"} here. Detach would just remove the entity from this entity manager instance (in other words: for the running process), so anything you'd do to the entity after detaching it wouldn't be reflected in the database when $entityManager->flush() is called. On the next request, the entity would be back in the entity manager.
I believe that adding nullable=true to your ManyToOne's JoinColumn annotations, e.g.
#JoinColumn(name="company_id", referencedColumnName="id", nullable=true)
will get you the result you're looking for. You'll need to update your database schema afterwards for changes to be applied to the tables. Also, make sure you don't have (or add) orphanRemoval=true on the inverse side so Doctrine doesn't automatically remove your entities if they lose their parent.
I prefer adding the JoinColumn annotation to relationships as well, even though it's not required if you're fine with Doctrine's default field name choices. Adding nullable=false makes it more explicit that this relationship cannot be null. That's implied if you don't have nullable=true, but when I start looking at relationships and need to know whether they can be null or not, I'm usually confused by something and I don't have mental energy to spare to actively remember the default values for important attributes.

Symfony 4 how to delete entity from OneToMany relationship

I'm having a bit of a problem deleting an entity assigned to another with a OneToMany relationship.
I have an entity called Business and it has a property "units" which is a collection of Unit entities on a OneToMany relationship (business can have many units).
When i try to delete a single unit from the database i get a violation of the foreign keys, it won't let me remove the unit from the business entity.
Here is a condensed version of both entities:
BUSINESS
/**
* #ORM\Entity(repositoryClass="App\Repository\BusinessRepository")
*/
class Business
{
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="App\Entity\Unit", mappedBy="business")
*/
private $units;
}
UNIT
/**
* #ORM\Entity(repositoryClass="App\Repository\UnitRepository")
*/
class Unit
{
/**
* #var Business
* #ORM\ManyToOne(targetEntity="App\Entity\Business", inversedBy="units")
* #ORM\JoinColumn(name="business_id", referencedColumnName="id")
*/
private $business;
}
So in the UnitRepository i have a delete method:
/**
* #param Unit $unit
*/
public function delete(Unit $unit){
$this->em->remove($unit);
$this->em->flush();
}
And i get this error:
An exception occurred while executing 'DELETE FROM unit WHERE id = ?' with params [1]:
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`businessdirectory`.`unit_day`, CONSTRAINT `FK_F03D80CEF8BD700D` FOREIGN KEY (`unit_id`) REFERENCES `unit` (`id`))
I don't know if i have set up the relationship incorrectly or not here, but i should be able to delete a single unit from a business, and i should be able to delete the entire business with it's units.
See if a Unit entity is the owning side of another relationship. At that point you would need to delete all the entities that depend on Unit first. You can freely delete the owned side of a One-To-Many relationship but you would need to clear all owned elements before deleting the owning side.

Entity associated with non-entity

I have an interface SupplierInterface with 2 implementations: B2BSupplier (a Doctrine entity), RetailSupplier (a static object).
<?php
namespace MyBundle\Model;
interface SupplierInterface {
const B2B = 'B2B';
const RETAIL = 'Retail';
/**
* #return string
*/
public function getSupplierType();
/**
* #return string
*/
public function __toString();
}
Another entity, Supply has a many-to-one relationship with a Supplier. Normally this isn't problematic. But because RetailSupplier is not a Doctrine entity, I'm a bit flummoxed about how to proceed.
Supply looks like this:
<?php
namespace MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Gedmo\Blameable\Traits\BlameableEntity;
use Gedmo\Timestampable\Traits\TimestampableEntity;
/**
* Supply
*
* #ORM\Table(name="cir_supply")
* #ORM\Entity()
*/
class Supply
{
use BlameableEntity;
use TimestampableEntity;
/**
* #var int
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="B2BSupplier")
* #ORM\JoinColumn(name="supplier_id", referencedColumnName="id", nullable=true)
*/
protected $supplier; // <-- PROBLEM, since supplier could be B2BSupplier entity, or it could be vanilla object RetailSupplier
/**
* #ORM\ManyToOne(targetEntity="Chemical", inversedBy="supplies")
* #ORM\JoinColumn(name="chemical_id", referencedColumnName="id", nullable=false)
*/
protected $chemical;
/**
* #ORM\Column(name="external_id", type="string")
*/
protected $externalId;
//getters and setters ...
}
How do I specify a Doctrine relationship when that relationship might not always be valid?
From my experience I'm 99% sure you can't do what you want in your current setup. That being said, there are a few workarounds I can think of. Also before I go into the workarounds. You should think if you really want OneToOne relation on 'supplier' or will ManyToOne work better. OneToOne has some Lazy loading issues and also Workaround 3 work better with ManyToOne.
Workaround 1:
Remove the relation and make the supplier filed contain the id, without having a relation defined.
Extend SupplierRepository 'find' method to handle the cases where id is
2.1 'null' there is no relation in witch case it returns RetailSupplier
2.2 call parent::find for all other cases
2.3 Optional: if null relations are required change 2.1 to use '0' instead of null (adds con 3)
Pros:
fast to achieve from your current setup
keep database foreign key (if step 2.3 is ignored)
Cons:
hidden behavior of the 'find' method
you loose the your doctrine relation
not scalable for other types of Suppliers
source of the information is split between the app and the database
if step 2.3 is required, you loose database foraign key ('0' will not be a foraign key)
Workaround 2:
Modify getSupplier to return RetailSupplier if $this->supplier is null
Modify setSupplier to set null if $supplier is instance of RetailSupplyer
Optinal: Change the first 2 steps to handle '0' as RetailSupplyer and 'null' as no relation
Pros:
fast to achieve from your current setup
keep database foreign key (if step 3 is ignored)
keep doctrine relation
Cons:
hidden behavior of the setter and getter
not scalable for other types of Suppliers
if step 3 is required, you loose database foraign key ('0' will not be a foraign key)
source of the information is split between the app and the database
Workaround 3 (doctrine inheritance mapping):
Create an abstract (called Supplier) this will be inherited by RetailSupplyer and B2BSupplier
Add inheritance metadata to Supplier abstract something like this
Create an entity for RetailSupplyer and a database table with one single line to start (the first RetailSupplier)
Change your database to match your inheritance mapping settings (for more info http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html)
Change your relation to ManyToOne on $supplier and make it to point to Supplier
Pros:
source of the information is only the database
no hidden behavior in your code
scalable for other types of suppliers and other more retail suppliers
Cons:
harder to achieve from your current setup (database changes, new doctrine setup, possibly some refactor)
pros/cons: Depending on the selected inheritance type you can have full relation path in your database (with foraign key), or you can have no relations. This is up to you ;) after you read the documentation for inheritance mapping.
PS: If I had to choose i will go with Workaround 3. It is hardest to achieve, but solid do it.
Hope this helps and happy coding
Alexandru Cosoi

Symfony2: Error persisting ManyToMany/OneToMany Relationships

I don't know why, maybe i am missing some basic logic but I always run again into the same issue. I can't persists ManyToMany collections, and it also faces me with OneToMany collections, though I can work around that.
I read through the doctrine documentation, and I think I do understand the thing with mappedBy and inversedBy (where the last one is always the owner and therefor responsible for persisting the data, please correct me if I am wrong).
So here's a basic example that I have right now, which I can't figure out.
I have an Entity called Site:
#Site.php
...
/**
* #ORM\ManyToMany(targetEntity="Category", mappedBy="sites")
*/
protected $categories;
and another one called Category:
#Category.php
...
/**
* #ORM\ManyToMany(targetEntity="Site", inversedBy="categories")
* #ORM\JoinTable(name="sites_categories")
*/
protected $sites;
Using the Symfony2 entity genenerator it added me some getters and setters to my Entites which look like this.
Site:
#Site.php
...
/**
* Add categories
*
* #param My\MyBundle\Entity\Category $categories
*/
public function addCategory(\My\MyBundle\Entity\Category $categories)
{
$this->categories[] = $categories;
}
/**
* Get categories
*
* #return Doctrine\Common\Collections\Collection
*/
public function getCategories()
{
return $this->categories;
}
The same counts for
Category:
#Category.php
...
/**
* Add sites
*
* #param My\MyBundle\Entity\Site $sites
*/
public function addSite(\My\MyBundle\Entity\Site $sites)
{
$this->sites[] = $sites;
}
/**
* Get sites
*
* #return Doctrine\Common\Collections\Collection
*/
public function getSites()
{
return $this->sites;
}
Fair enough.
Now in my controller, I am trying to persist a Site object:
public function newsiteAction() {
$site = new Site();
$form = $this->createFormBuilder($site); // generated with the FormBuilder, so the form includes Category Entity
// ... some more logic, like if(POST), bindRequest() etc.
if ($form->isValid()) {
$em = $this->getDoctrine()
->getEntityManager();
$em->persist($site);
$em->flush();
}
}
The result is always the same. It persists the Site Object, but not the Category entity. And I also know why (I think): Because the Category entity is the owning side.
But, do I always have to do something like this for persisting it? (which is actually my workaround for some OneToMany collections)
$categories = $form->get('categories')->getData();
foreach($categories as $category) {
// persist etc.
}
But I am running into many issues here, like I would have to do the same loop as above for deleting, editing etc.
Any hints? I will really give a cyber hug to the person who can clear my mind about that. Thanks!
.
.
.
UPDATE
I ended up changing around the relationship (owning and inverse side) between the ManyToMany mapping.
If somebody else runs into that problem, you need to be clear about the concept of bidrectional relationships, which took me a while to understand too (and I hope I got it now, see this link).
Basically what anserwed my question is: The object you want to persist must always be the owning site (The owning site is always the entity that has "inversed by" in the annotiation).
Also there is a concept of cascade annotation (see this link, thanks to moonwave99)
So thanks, and I hope that helps somebody for future reference! :)
Regarding OneToMany relationship, you want to know about cascade annotation - from Doctrine docs [8.6]:
The following cascade options exist:
persist : Cascades persist operations to the associated entities.
remove : Cascades remove operations to the associated entities.
merge : Cascades merge operations to the associated entities.
detach : Cascades detach operations to the associated entities.
all : Cascades persist, remove, merge and detach operations to associated entities.
following docs example:
<?php
class User
{
//...
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* #OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
*/
private $commentsAuthored;
//...
}
When you add comments to the author, they get persisted as you save them - when you delete the author, comments say farewell too.
I had same issues when setting up a REST service lately, and cascade annotation got me rid of all the workarounds you mentioned before [which I used at the very beginning] - hope this was helpful.

Many-to-many self relation with extra fields?

I am trying to develop a friends system, and I need a Many-To-Many relation on my User entities ; for now, this is what I've done :
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="friends")
*/
protected $friendsWith;
/**
* #ORM\ManyToMany(targetEntity="User", inversedBy="friendsWith")
* #JoinTable(name="friends",
* joinColumns={#JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
*/
protected $friends;
But I would like to have some extra fields for these relations, for example the creation date or the state (accepted, pending, ...) ; I've created another entity "Friend", and I would like this entity to be used as a link between friends. But I don't really know how to manage this...
Do you have some ideas ?
Thanks !
I'm afraid you need an extra class to make such an association.
Here is the tip from doctrine documentation:
Why are many-to-many associations less common? Because frequently you
want to associate additional attributes with an association, in which
case you introduce an association class. Consequently, the direct
many-to-many association disappears and is replaced by
one-to-many/many-to-one associations between the 3 participating
classes.
http://www.doctrine-project.org/docs/orm/2.1/en/reference/association-mapping.html#many-to-many-unidirectional
I guess it should be Friend -> Special Association Class (with fileds: user_id, friend_id, date created) ->Friend.
And you associate Friend to special class in two filed $myFriends and $imFriendOf :)

Resources