On my symfony project I have a problem with 2 entities linked by a unidirectionnal OneToOne relationship. My entities are : Club and Address, a Club can have an address. See entity declaration bellow :
Club Entity
class Club{
/**
* #ORM\OneToOne(targetEntity="FFPM\MainBundle\Entity\Address", cascade={"persist", "remove"}, orphanRemoval=true)
* #ORM\JoinColumn(name="address_id", referencedColumnName="id", nullable=true)
*/
protected $address;
...
}
Address Entity
class Address{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
}
For some reason when I remove a Club the Address stays in database. I tried with orphanRemoval and cascade{"remove"} and I can't get it to work even if I'm pretty sure it's some simple mistake.
Try use this construction:
/**
* #ORM\OneToOne(targetEntity="FFPM\MainBundle\Entity\Address", mappedBy="entidad", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="address_id", referencedColumnName="id", onDelete="CASCADE", nullable=true)
**/
private $personaFisica;
The relation OneToOne unidirectionnal in doctrine manage only the one side since you don't complete the relation. so there is two way you can persist or remove address entity, is that you use the domain Event listener or you manage your entity manually. And unfortunately both cases are not good practice.
Related
I started using symfony not long ago and at the moment I'm struggling with this problem:
I decided to have "who" information at entity level so I have defined these additional 4 prameters for every entity:
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
* #ORM\JoinColumn(name="created_by", referencedColumnName="id")
*/
private $createdBy;
/**
* #var \DateTime
*
* #ORM\Column(name="created_at", type="datetime")
*/
private $createdAt;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
* #ORM\JoinColumn(name="updated_by", referencedColumnName="id")
*/
private $updatedBy;
/**
* #var \DateTime
*
* #ORM\Column(name="updated_at", type="datetime", nullable=true)
*/
private $updatedAt;
My problem is now where and how I should populate createdBy and updatedBy. ATM I do that in my controller before persisting to the database. Thou I encountered a problem when a entity is a property of another entity and lets say I have an entity called Post that has a property images of type Document the entities Post and Document both have "who" information on them and images property inside Post is defined as follows:
/**
* #var array
*
* #ORM\ManyToMany(targetEntity="Nisand\DocumentsBundle\Entity\Document", cascade={"persist"})
* #ORM\JoinTable(name="blog_documents",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="document_id", referencedColumnName="id")}
* )
*/
private $images;
For Post suppose I set createdBy in the controller before persisting but on Document how should that work cause that will be persisted by the cascade rule?
How do you guys handle in your applications the "who" columns?
Try this bundle: StofDoctrineExtensionsBundle and use Blameable extension.
You will need set current user with BlameableListener. And it will cover your use case.
Documentation for Blameable is here: https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/blameable.md
When I try to make a OneToMany unidirectional association between this two entities i get this error when i try to update the database schema:
$ app/console doctrine:schema:update --dump-sql
[Doctrine\ORM\Mapping\MappingException]
OneToMany mapping on field 'address' requires the 'mappedBy'
attribute.
/**
* User
*
* #ORM\Table()
* #ORM\Entity
*/
class User
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Address")
* #ORM\JoinTable(name="users_address",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="address_id", referencedColumnName="id", unique=true)}
* )
*/
private $address;
//...
}
/**
* Address
*
* #ORM\Table()
* #ORM\Entity
*/
class Address
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// ...
}
Why is "mappedBy" required if the association is unidirectional?
Thanks in advance.
UPDATE: just as mentioned in the comment by #tchap an unidirectional OneToMany can be mapped with a #ManyToMany and a unique constraint on one of the join columns to enforce the onetomany cardinality. Just as the documentation says, but it was a bit confusing for me because there is already a #OneToMay annotation. So I just have to change the above code to this (by only changing the #OneToMany to #ManyToMany):
/**
* #ORM\ManyToMany(targetEntity="Address")
* #ORM\JoinTable(name="users_address",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="address_id", referencedColumnName="id", unique=true)}
* )
*/
private $address;
A OneToMany has to be bi-directional and is always the inverse side of a relationship and if you define the inverse side you need to point at the owning side of the relationship with a mappedBy attribute. The owning side is ManyToOne. In your case this would look like this:
In User your association has a mappedBy="user" attribute and points to the owning side Address:
/** ONE-TO-MANY BIDIRECTIONAL, INVERSE SIDE
* #var Collection
* #ORM\OneToMany(targetEntity="Address", mappedBy="user")
*/
protected $addresses;
In Address your association has a inversedBy="addresses" attribute and points to the inverse side User:
/** MANY-TO-ONE BIDIRECTIONAL, OWNING SIDE
* #var User
* #ORM\ManyToOne(targetEntity="User", inversedBy="addresses")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
The advantage of this solution is that you can find which user owns the Address by doing: $address->getUser(); and that you skip adding an additional join table to your database.
If you want the relationship to be uni-directional you can do as you did in your update; define a ManyToMany relationship with a join table and add a unique constraint on the address_id column.
/**
* #ORM\ManyToMany(targetEntity="Address")
* #ORM\JoinTable(name="user_address",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="address_id", referencedColumnName="id", unique=true)}
* )
*/
The disadvantage of this solution is that you cannot find out which User owns the address from the Address resource (the Address is not aware of the User). Such example can also be found here in the Doctrine documentation chapter 5.6. One-To-Many, Unidirectional with Join Table.
I can't figure it out..
Why I haven't access to Country table?
countryName should show Great Britain but it doesn't.
This is my dump($User):
My my piece of code of User entity:
/**
*
* #ORM\ManyToOne(targetEntity="Dashboard\MainBundle\Entity\Country", cascade={"persist"})
* #ORM\JoinColumn(name="country_id", referencedColumnName="id", nullable=true)
*
*/
private $countryId;
And my piece of code of Country Entity:
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
Depending on how you get the user maybe it is a lazy load that you are using which will get the country only if you call the getter explicitly, to always get a country with the user try :
/**
*
* #ORM\ManyToOne(targetEntity="Dashboard\MainBundle\Entity\Country", cascade={"persist"}, fetch="EAGER")
* #ORM\JoinColumn(name="country_id", referencedColumnName="id", nullable=true)
*
*/
private $countryId;
But still we need to know how you are getting the user the lazy load may override the fetch eager.
Your Country object is now only a Proxy object - dump function don't call a Doctrine to get a related object. Try before dump get your object for example:
dump($User->getCountry()):
dump($User);
OR try left join you Country in QueryBuilder
OR find a information about lazy load in Doctrine2 here
I have a form that shows entity:
class Event
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*
* #ORM\OneToMany(targetEntity="EventAttendee", mappedBy="event", cascade={"all"})
*/
private $attendees;
}
and a collection within it:
class EventAttendee
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*
* #ORM\ManyToOne(targetEntity="Event", inversedBy="attendees")
* #ORM\JoinColumn(name="event_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $event;
/**
*
* #ORM\OneToOne(targetEntity="Employee")
* #ORM\JoinColumn(name="employee_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $employee;
}
If I delete an employee from the collection and add it again, I'm getting integrity constraint violation. This is because Doctrine's UnitOfWork first executes Inserts and then Deletes. Therefore, when it inserts a new record db still has the old one with the same employee.
Doctrine2 developers did not provide any working solution for Symfony2 users (here is the thread: http://www.doctrine-project.org/jira/browse/DDC-601).
And thus, I'm asking the question here: is it anyhow possible to avoid this issue?
EDIT:
My current workaround is:
find all not-persisted colletion items ready to insert
remove them from the collection and save to a variable
remove all the items that were really deleted in the form
call flush()
add all the items for insert back to the collection
call flush()
This works for me, however doesn't look good. Maybe someone has a better solution.
I have several bundles in my app and I would like to have relations between tables.
One is my User(StoreOwner) which is in UserBundle, and the second is Store in StoreBundle.
The relation between them is OneToMany (User -> is owner of -> Store).
Store
/**
* Description of Store
*
* #ORM\Table(name="Store")
* #ORM\Entity(repositoryClass="Traffic\StoreBundle\Repository\StoreRepository")
* #author bart
*/
class Store extends StoreModel {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string $name
*
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank(
* message="Please provide your shop name"
* )
*/
protected $name;
/**
* #ORM\ManyToOne(targetEntity="Application\Sonata\UserBundle\Entity\StoreOwner", inversedBy="stores")
*
*/
protected $owner;
}
StoreOwner
/**
* #ORM\Entity
*
*/
class StoreOwner extends User implements StoreOwnerInterface {
/**
* #var type ArrayCollection()
*
* #ORM\OneToMany(targetEntity="Traffic\StoreBundle\Entity\Store", mappedBy="owner", cascade={"persist"})
*/
protected $stores;
}
My question is:
Is there any solution to avoid dependency between StoreBundle and UserBundle and keep relations between Entities in Doctrine?
This is a valid concern in my opinion. Two-way dependencies between bundles are a smell.
One way of solving the dependency issue is moving your entities out of the bundles into a more general namespace. This way both bundles will depend on the same "library" but won't depend on each other directly.
I recently wrote a blog post on how to do it: How to store Doctrine entities outside of a Symfony bundle?