Doctrine 2.0 vs 2.1 cascade remove OneToMany - doctrine-orm

Hello I have problem when trying to cascade remove entities in OneToMany relations.
After a few hours of debugging I tried to downgrade the doctrine from the latest 2.1.2 to 2.0.2 and It suddenly starts working.
Imagin two entities Company and Address in relation 1:N.
/**
* #Entity
*/
class Company extends Entity
{
/**
* #var integer
* #id #Column(type="integer")
* #generatedValue
*/
private $id;
/**
* #var Collection
* #OneToMany(targetEntity="Address",mappedBy="company", cascade={"persist","remove"})
*/
private $addresses;
}
/**
* #Entity
*/
class Address extends Entity
{
/**
* #var integer
* #id #Column(type="integer")
* #generatedValue
*/
private $id;
/**
* #var Company
* #ManyToOne(targetEntity="Company", inversedBy="addresses")
* #JoinColumn(name="company_id", referencedColumnName="id",nullable=false)
*/
private $company;
}
when I try to remove the entity Company, I would like the assigned addresses will be removed as well.
$em->remove($company);
$em->flush();
In doctrine 2.1.2 the deletion of addresses is not performed so the integrity constraint fails. In version 2.0.2 there it works perfectly. Wierd thing on it is, if I use EntityAudit extension https://github.com/simplethings/EntityAudit the LogRevisionListener is corretly versioning the addresses entities (set them revtype = DEL) in doctrine 2.1.2 (of course in 2.0.2 as well) but the UnitOfWork is not removing it.
Is there any difference how to handle cascade removing in 2.0.2 and in 2.1.2?
Thank you very much

Try using this on the addresses attribute of your Company Class
#OneToMany(targetEntity="Address",mappedBy="company",
cascade={"persist"}, orphanRemoval=true)

I had the same problem... Relations were added or updated, but not deleted, even if I had cascade: [persist, remove].
I found out that I didn't need the "remove" attribute in "cascade", but I had to add the orphanRemoval: true.
I was going crazy, you made my day!

I have met the same problem and i have solved him with that code :
$em->remove($object);
$em->flush();
$em->remove($user);
$em->flush();
Maybe you can use a findAll on your company for the addresses and remove this with a foreach like that :
// Return all the addresses of the company
$addresses = $em->getRepository(...)->findAllAddressesByCompany($company);
$em->remove($company);
foreach ($address in $addresses)
{
$em->remove($address);
}
That's not a very good method but for now, that's all I've found.

Related

Persisting Doctrine Entity with Collection results in exception

Am working on a simple setup with two tables with a one to many (and inversed) relationship. The two corresponding entities are:
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=ArticlesRepository::class)
* #ORM\Table(name="articles")
*/
class Article
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(name="unique_id", type="integer")
*/
protected int $idx;
/**
* The inverse side
* #ORM\OneToMany(targetEntity="Comment", mappedBy="article")
* #ORM\Column(name="uid", type="integer", nullable=true)
*/
protected $id;
public function __construct()
{
$this->id=new ArrayCollection();
}
}
and
use Doctrine\ORM\Mapping as ORM;
/**
* ORM\Entity(repositoryClass="CommentRepository::class")
* ORM\Table(name="comments")
*/
class Comment
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(name="uid", type="integer")
*/
protected $idx;
/**
* The owning side
* #ORM\ManyToOne(targetEntity="Article", inversedBy="uid")
* #ORM\JoinColumn(name="article", referencedColumnName="uid")
*/
protected $article;
protected $content;
}
Attempting to persist a new blank Article
$article=new Article();
$em->persist($article);
$em->flush();
results in the following exception
An exception occurred while executing 'INSERT INTO articles (uid) VALUES (?)' with params [{}]: PHP Warning: Object of class Doctrine\Common\Collections\ArrayCollection could not be converted to float in ...\vendor\doctrine\dbal\lib\Doctrine\DBAL\Driver\Mysqli\MysqliStatement.php line 164
I went searching for answers and came across suggestions close to but don't touch on this subject. One had to do with removing the type indication of the column. So I did and it gets stuck with the following exception
An exception occurred while executing 'INSERT INTO articles (uid) VALUES (?)' with params [{}]: Cannot add or update a child row: a foreign key constraint fails...
When type is removed, the field defaults to type string which makes no difference, since Doctrine still attempts to store an empty array in an integer field (see the values in the exception above).
Question: How can I get the setup to work properly and persist all objects correctly?
It turns out that the #ORM\OneToMany annotation in the Article entity should be used on a non-existent database field. In other words, property $id should not be an actual field in the database table of the entity.

How to tell Doctrine to save ID

I have some code that has Doctrine annotations for how to save it into the DB.
I have an ID string that has already been generated by the system. I want to tell Doctrine to use the id as the primary key, and also save it into the database when persisting the object.
Currently it seems that Doctrine is just not writing that field.
Doctrine\DBAL\Exception\NotNullConstraintViolationException: An
exception occurred while executing 'INSERT INTO voting_motion (type,
name, start_datetime, close_datetime, created_at, updated_at) VALUES
(?, ?, ?, ?, ?, ?)' with params ["personal_opinion", "Question about
food", "2020-07-02 12:00:00", "2020-07-07 13:00:00", "2020-05-27
11:16:49", "2020-05-27 11:16:49"]:
i.e. the insert into statement is just not including the ID field.
I thought the GeneratedValue annotation would be the appropriate thing to do here. But it seems not.
How do I tell Doctrine that "this is the primary id, and I do want you to write it when saving the entity"?
This is what my code for the entity with the relevant annotations looks like.
/**
* #Entity
* #Table(name="voting_motion")
* #HasLifecycleCallbacks
*/
class VotingMotion
{
/**
* #Id()
* #Column(name="id", type="string", unique=true)
* #GeneratedValue(strategy="NONE")
*/
private string $id;
/** #Column(type="string") **/
private string $type;
/** #Column(type="string") **/
private string $name;
// ...
// ...
}
These annotations work fine for me, you don't need to use #GeneratedValue(strategy="NONE") annotation. Also, run migrations:diff after changes in the GeneratedValue annotation.
/**
* #ORM\Id()
* #ORM\Column(type="guid", unique=true)
*/
private $id;
I use strategy="UUID" and works fine
/**
* #ORM\Id
* #ORM\Column(type="guid")
* #ORM\GeneratedValue(strategy="UUID")
*/
protected $id;

Must I sanitize my inputs when using setter methods to populate a Doctrine Entity?

I am under impression that ORM uses some kind of sanitation technique, but I am not sure. I looked at http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/security.html and was not clear on the issue.
Question
Will it be safe to use
$product = new Product();
$product->setModel($_POST['model']);
where POST is NOT sanitized previously, or must I always sanitize/validate my values first before sending them to Doctrine?
For reference
/**
* #Entity
*/
class Product
{
/**
* #var integer #Column(name="id", type="integer", nullable=false)
* #Id #GeneratedValue
*/
private $id;
/**
* #var string #Column(type="string")
*/
private $model;
}
You should always validate/sanitize user input. Even though Doctrine is using a prepared queries (which prevents SQL injections) you are not safe against other attacks.
Check this page, to see how to deal with inputs in Doctrine:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/security.html#user-input-and-doctrine-orm
Your are right, Doctrine ORM is doing all the sanitization automatically. Therefore, as long as you are using ORM, you are perfectly safe.
So in your example no additional sanitization is required.
I would only say that instead of using raw $_POST array you are supposed to use the Request object that is automatically injected in your controller:
$product = new Product();
$product->setModel($request->get('model'));

doctrine2 Detach doesn't work when there is association to put order in session

I'm trying to detach an object from entity_manager in doctrine2 in order to put him in session but it doesn't work. not when there is Association.
Ex:
<?php
namespace Travelyo\CoreBundle\Entity\Order;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="bundles")
* /
class Bundle
{
/**
* #var integer $id
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Order", inversedBy="orders",cascade={"detach","merge","remove"})
* #ORM\JoinColumn(name="order_id", referencedColumnName="id")
*/
private $order;
 }
There is no other relation in order.
But when I'm trying to do that
$em = $this->getDoctrine()->getEntityManager();
$bundle = $em->getRepository('TravelyoCoreBundle:Order\Bundle')->find(27);
$em->detach($bundle);
$em->detach($bundle->order);
serialize($bundle);
It's not working, I have all the object dependecies, proxies,...
If I'm doing that before detach
$bundle->setOrder(new Order());
Then the serialize is perfect, I just have the bundle.
What am I missing here ?
Even if your object is detached, it still has references to the doctrine components used to create it.
As a simple advice, I can tell you to simply serialize the object identifier instead. That's much cleaner, and while it will require you to perform a query on the other side (the unserialize part), you will avoid a lot of problems.
Just don't serialize your entities, it's just messy.

Adding a timestamp property to a relation in Symfony2

I am trying to model a simple comment system with 2 entities - User and Post. The User class has the regular properties, and the Post class looks like this:
class Post
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* User who posted activity
*
* #var User
* #ORM\ManyToOne(targetEntity="Acme\AppBundle\Entity\User")
*/
protected $user;
/**
* #var string $text
*
* #ORM\Column(name="text", type="string", length=255)
*/
private $text;
/**
* #var datetime $timestamp
*
* #ORM\Column(name="timestamp", type="datetime")
*/
private $timestamp;
}
I need to make a simple association - Users can favourite posts. The logical thing to do would be to add ManyToMany association in User class to Post like so
#ORM\ManyToMany(targetEntity="Post")
Which would then create a mapping table with user_id and post_id as primary keys. Now my problem is that I need to add a timestamp property to track when the user liked a post. One possibility is to create a new Entity "FavouritePost", map it to both User and Post class using ManyToOne association, and add a timestamp property to it.
1) Is this the right/only way to do it in Symfony2?
2) If I want to select the most recent 15 posts, and check if the currently logged in user has liked the posts, how can I write a query for that?
Any help appreciated, as I am drawing a blank on how to do it via Symfony
1) Yes you may create a jointure table, and so its FavouritePost entity.
2) You should use a Repository to create all special queries :
#ORM\Entity(repositoryClass="Acme\AppBundle\Repository\FavouritePostRepository")
Then it's easy Doctrine queries, like you will make much more.