How to remove an entity when it is used an association class? - php

I've a class user with a one-to-many relationship against ArticleVote which is itself an Association Class (see below).
Here is how my entities looks like:
class User
{
protected $articlesVotes;
}
An user holds an ArticleVote collection.
While an ArticleVote is referenced by a composite primary key based on the UserId and the ArticleId:
class ArticleVote
{
protected $article;
protected $user;
}
Now, let's say I want to remove an ArticleVote from User, naturally I do $user->getArticlesVotes()->removeElement($articleVote); which results in the actual removing of the entity inside the collection but as the ArticleVote is both a relationship and an entity, the row in database is not removed at all.
I know, I can do $em->remove($articleVote); but I wish I could remove it from the collection of the user to bypass the EntityManager, what if I want to remove several $articleVote?
Currently, I create/remove the vote in my User model by passing the Article entity and it's my User entity which creates the ArticleVote object and append it itself, I wish I could have the same behavior for the removal feature.
Any ideas? (oh, and by the way, I already tried with cascade="remove")

I ran to this exact issue yesterday. When setting cascade="remove" this removes the association marked within your UnitOfWork. However to have the items removed from the database you need to mark your $user property in the ArticlesVote Entity to cascade on delete. Like so..
class ArticleVote
{
protected $article;
/**
* #ManyToOne(targetEntity=....
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $user;
}
This will add an "on delete" cascade to the foreign key in your database, and article votes associated with a deleted user will be deleted along with it.

Related

Working with Doctrine array of enum and store it in a separate table

I'm currently building Entity model and one of my Doctrine Entities have ManyToMany relation with an external dictionary (like ENUM). So the entity field will be an Array of Enum.
I'm looking for a way to have it as an array field on my entity, but to store it as a separate DB table.
Would like to get any advice/links/etc.
The question is a bit out of context but..
A many to many is already an array (Iterator) in your entity.
You can create your own entity acting as a Many To Many and set the column as enum.
Finally, I've decided to create an Entity to store this relation. To make sure it will be deleted on unlinking from the parent entity, I've used the orphanRemoval=true option on the OneToMany relation side.
class Entity {
/**
* #ORM\OneToMany(targetEntity="EntityType", mappedBy="entity", orphanRemoval=true)
*/
protected $types;
}
class EntityType {
/**
* #ORM\ManyToOne(targetEntity="Entity")
*/
protected $entity;
/**
* #ORM\Column(type="MyEnum")
*/
protected MyEnum $type;
}

Merge is creating new record for children

Merge is creating not working for children #OneToMany
I am using Php Doctrine and I am using #OnToMany mapping with cascade all. I have a parent class SalesOrder and child class SalesOrderDetails.
Case 1 : Save - When I save new record sales order along with sales order details. It is working as expected.
Case 2 : Update - Here is the issue, I am merging the Sales Order which is fine however its inserting new records for its children SalesOrderDetail instead of updating it. Ideally it should it apply mergebut for children as well but its not.
As of now, I am getting the Sales Order Details by id from DB then change the properties of it. Ideally that should not be the case, mean if we set the id to unmanned object, it should update instead of creating new records.
Note:
1. Merge is working with parent object if it has the id value.
2. I am not adding new item here, I am just updating the existing recorded through merge.
SalesOrder.php
/**
* #Entity #Table(name="sales_orders")
* */
class SalesOrder extends BaseEntity {
/**
* #OneToMany(targetEntity="SalesOrderDetail",cascade="all", mappedBy="salesOrder" )
*/
protected $itemSet;
function __construct() {
$this->itemSet = new ArrayCollection();
}
}
SalesOrderDetail.php
/**
* #Entity #Table(name="sales_order_details")
* */
class SalesOrderDetail extends BaseEntity {
/** #Id #Column(type="integer") #GeneratedValue * */
protected $id;
/**
* #ManyToOne(targetEntity="SalesOrder")
* #JoinColumn(name="order_no", referencedColumnName="order_no")
*/
protected $salesOrder;
}
Debug Mode screen
If I use cascade={"merge"}
I am getting different error if I am using Cascades merge
Type: Doctrine\ORM\ORMInvalidArgumentException Message: Multiple
non-persisted new entities were found through the given association
graph: * A new entity was found through the relationship
'Ziletech\Database\Entity\SalesOrder#itemSet' that was not configured
to cascade persist operations for entity:
Ziletech\Database\Entity\SalesOrderDetail#0000000052218380000000007058b4a6.
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
'Ziletech\Database\Entity\SalesOrderDetail#__toString()' to get a
clue. * A new entity was found through the relationship
'Ziletech\Database\Entity\SalesOrder#itemSet' that was not configured
to cascade persist operations for entity:
Ziletech\Database\Entity\SalesOrderDetail#0000000052218071000000007058b4a6.
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
'Ziletech\Database\Entity\SalesOrderDetail#__toString()' to get a
clue.
You have a mistake in your mapping, cascade needs an array
/**
* #OneToMany(targetEntity="SalesOrderDetail", cascade={"all"}, mappedBy="salesOrder" )
*/
protected $itemSet;

Persisting entity with Doctrine association

I'm having trouble trying to persist an entity with an association using Doctrine.
Here's the mapping on my owning side: (User.php)
/** #Role_id #Column(type="integer") nullable=false */
private $role_id;
/**
* #ManyToOne(targetEntity="Roles\Entities\Role")
* #JoinColumn(name="role_id", referencedColumnName="id")
*/
private $role;
There's no mapping on the inverse side, I tried with (OneToMany) and it didn't seem to make a difference.
Basically, I'm passing a default value of 2 (integer) to a method setRole_id but it shows up as blank when I actually go to persist the entity which causes a MySQL error as that column doesn't allow nulls.
Edit 1:
Literally just persisting this for role_id
$this->user->setRole_id( 2 );
Cheers,
Ewan
Your mapping seems incorrect. Try to rewrite it as follows:
/**
* #ManyToOne(targetEntity="Roles\Entities\Role")
* #JoinColumn(name="role_id", referencedColumnName="id", nullable=false)
*/
private $role;
In other words, you only need to describe the role_id as the join column of your relationship. You don't need to map it as a "normal" column. Then just write and use a regular setter declared like the one below:
public function setRole(Roles\Entities\Role $role) {
$this->role = $role;
}
Use the above instead of $this->user->setRole_id(2) and persist your user entity. Doctrine should automatically take care of storing the correct entity ID in the foreign key field for you.

Doctrine 2 Entities Relations Remove

I have an owning entity that has the following relation to an "attribute" entity:
/**
* #ORM\OneToMany(targetEntity="Attribute", mappedBy="entity", cascade={"persist", "remove", "merge"})
**/
protected $attributes;
On the side, the owned entity relation looks like this:
/**
* #ORM\ManyToOne(targetEntity="Entity", inversedBy="attributes")
* #ORM\JoinColumn(name="entity_id", referencedColumnName="id")
*/
protected $entity;
When I create an instance of an entity, add attributes to it and save it. It all works fine.
When I remove one attribute from the entity and persist, the attribute is not deleted in the database and re-appears upon refresh.
Anyone has an idea?
Solution
What you're looking for is orphan removal.
You can read on if you'd like details on why your current situation isn't working.
Cascade woes
The cascade operation won't do what you want unfortunately. The "cascade=[remove]" just means that if the entity object is removed then doctrine will loop through and remove all child attributes as well:
$em->remove($entity);
// doctrine will basically do the following automatically
foreach ($entity->getAttributes() as $attr)
{
$em->remove($attr);
}
How to do it manually
If you needed to remove an attribute from an entity you'd delete the attribute like so:
$entity->getAttributes()->removeElement($attr);
$em->remove($attribute);
Solution details
But, to do that automatically we use the orphan removal option. We simply tell doctrine that attributes can only belong to entities, and if an attribute no longer belongs to an entity, simply delete it:
/**
* #ORM\OneToMany(targetEntity="Attribute", mappedBy="entity", orphanRemoval=true, cascade={"persist", "remove", "merge"})
**/
protected $attributes;
Then, you can remove the attribute by simply doing this:
$entity->getAttributes()->removeElement($attr);
Be careful when using orphan removal.
If you remove an element and then call refresh on the main entity the element is not removed from the internal orphan removal array of doctrine.
And if flush is called later, will result in removing that entry from the db, ignoring the refresh.
This looks like a bug to me, and resulted in loss of images on a lot of products in my app. I had to implement a listener to call persist again on those entities, after they ware scheduled for delete.

Doctrine ODM MongoDB - Replicate a simple One to Many Reference with Constraint

I'm new to Doctrine, mongo and the ODM setup and while playing with this setup in ZF1 I'm trying to replicate a simple one to many reference with a constraint. Here is the situation and would like some advise on how to achieve this.
This is a simple user->role mapping, so in a sql situation I would have tables as follows:
Users
- id
- name
- role_id
Roles
- id
- name
Then a foreign key constraint would be set on the users role_id to map to the role id. And upon deleting a role a foreign key constraint would be triggered stopping the operation.
How could I achieve the same goal in Doctrines MongoDB ODM?
So far I have played with different types of annotations on the User entity including #ReferenceOne #ReferenceMany with different cascade options...
The choice left to me now is to implement #PreUpdate, #PreRemove lifecycle events on the 'role' entity and then check that no users are using the role, if they are then on update change the reference to match or on remove throw an exception.
Am I right here or lost ?
Thank you,
Si
For something like that I wouldn't have two separate 'tables' like you would in SQL. You would just have the role type as a property of the user. And then if you wanted to remove a role type you can just manipulate the role field of all users with that role.
But to answer your question, I would do it's like so.
<?php
class User {
/** #MongoDB\Id */
protected $id;
/** #MongoDB\String */
protected $name;
/** #MongoDB\ReferenceOne(targetDocument="Role", mappedBy="user") */
protected $role;
//Getters/Setters
}
class Role {
/** #MongoDB\Id */
protected $id;
/** #MongoDB\String */
protected $name;
/** #MongoDB\ReferenceMany(targetDocument="User", inversedBy="role") */
protected $users;
public function _construct() {
$this->users = new Doctrine\Common\Collections\ArrayCollection;
}
// Getters/Setters
public function hasUsers() {
return !$this->users->isEmpty();
}
}
Then I would create a service class for working with my document manager.
class RoleService {
public function deleteRole(Role $role) {
if (!$role->hasUsers()) {
// Setup the documentManager either in a base class or the init method. Or if your über fancy and have php 5.4, a trait.
$this->documentManager->remove($role);
// I wouldn't always do this in the service classes as you can't chain
// calls without a performance hit.
$this->documentManager->flush();
}
}
}

Categories