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.
Related
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;
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'));
I have this piece of code inside an Entity:
/**
* #ORM\ManyToOne(targetEntity="Centers")
* #ORM\JoinColumn(name="center_id", referencedColumnName="id")
* #ORM\Column(type="string", length=36, name="center_id")
*/
protected $centerId;
However, schema:update says that all is in sync. Even changing Centers to some other inexistent word, produces no error.
Please delete the last line of annotation doctrine like this and change $centerId by $center (there is object an object) :
/**
* #ORM\ManyToOne(targetEntity="Centers")
* #ORM\JoinColumn(name="center_id", referencedColumnName="id")
*/
protected $center;
Clear cache and run your command.
Tell me if this solved your problem.
For eg I have entities like User,Item, Image. User has many items. Item has many images.
Which delete option should I choose, cascade={'remove'} or onDelete=Cascade ?
Also I have life cycle callbacks on Image. I know the difference between above mentioned cascade options. I was wondering if I used onDelete=cascade option, on deleting a User object, will the life cycle callback like PostRemove() be called ?
Here are my entities:
//User.php
class User {
/**
* #ORM\OneToMany(targetEntity="Item", mappedBy="user", onDelete="CASCADE")
*/
private $items;
}
//Item.php
class Item {
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="User", inversedBy="items")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* })
*/
private $user;
/**
* #ORM\OneToMany(targetEntity="ItemImage", mappedBy="item",onDelete="CASCADE")
*/
protected $images;
}
//ItemImage.php
class ItemImage {
/* Setters and getter **/
/**
* #var Items
*
* #ORM\ManyToOne(targetEntity="Item", inversedBy="images")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="item_id", referencedColumnName="id")
* })
*/
private $item;
/**
* #ORM\PostRemove()
*/
public function removeUpload() {
unlink($this->getUploadDir() . '/' . $this->imageName);
}
}
My question is when a user is deleted, will all the items associated with user and images related to the items be deleted ? I also want the PostRemove() callback of Image entity be called when User is deleted ? Which option should I use, onDelete="cascade" or cascade={'remove'} for such cases?
onDelete='CASCADE' will add an SQL level ON DELETE CASCADE. So yes, the images will be deleted from the table. This is very efficient because the database handles the deletes.
To have the listener called cascade={'remove'} is necessary. This makes Doctrine fetch the object's graph, which is slower.
Both options may be specified at the same time. In this case Doctrine will act as described in the last paragraph, but DELETEs not coming from Doctrine will cascade correctly, too; no listeners will be called in this case obviously.
I would tend to specify cascade={'remove'} only in this case to avoid accidental DELETEs without the listener being called (if there is no cleanup task for unreferenced files).
Details can be found in the Doctrine documentation.
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.