Adding a timestamp property to a relation in Symfony2 - doctrine-orm

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.

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'));

Doctrine - Prevent double flush on using identity through foreign entity

I've got 2 entities - TestUser and TestAddress. Address has OneToOne relation with User and it's primary key is also foreign key to User.
/**
* #ORM\Entity
*/
class TestUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
}
/**
* #ORM\Entity
*/
class TestAddress
{
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="TestUser")
*/
private $user;
/**
* #param $user
*/
public function setUser($user)
{
$this->user = $user;
}
}
When I try to save both entities through entityManager I get an Exception.
$em = $this->getEntityManager();
$user = new TestUser();
$address = new TestAddress();
$address->setUser($user);
$em->persist($user);
$em->persist($address);
$em->flush();
Exception:
Doctrine\ORM\ORMInvalidArgumentException: The given entity of type 'Entity\TestAddress' (Entity\TestAddress#000000004fcda1bf000000002f07b49b) has no identity/no id values set. It cannot be added to the identity map.
Only way to save both entities is "flush" with User and then persist and flush with address.
$em->persist($user);
$em->flush();
$em->persist($address);
$em->flush();
Question: Is there way to use autoincrement on TestUser id and save TestUser and TestAddress entities with one flush?
class TestAddress
{
/**
* #ORM\OneToOne(targetEntity="TestUser", cascade={"persist"})
*/
private $user;
I know it is not even an answer to your question, but IMO you should rethink the purpose of this schema choice when the ID of the Address entity must correspond to the ID of the User entity. I do not think this is necessary and you would be better off by going with separate foreign key and primary key fields on Address entity.
However one other thing you could try is making the relationship bi-directional, that is defining the testAddress variable on the User object and then adding the corresponding mapping information to it:
/**
* #OneToOne(targetEntity="TestAddress", mappedBy="user", cascade={"persist"})
*/
private $testAddress;
then you will probably also need to create the setTestAddress() method on the user entity, and set the user object for TestAddress explicitly in there:
public function setTestAddress($address)
{
$address->setUser($this);
$this->testAddress = $address;
}
then you could call $user->setTestAddress($address) and try flushing only the user entity:
$user = new TestUser();
$address = new TestAddress();
$user->setTestAddress($address);
$em->persist($user);
$em->flush();
I am not quite familiar with the order in which doctrine persists entities, but by having the entity be persisted by the TestUser entity and not the other way around(as was suggested) User should be persisted first and than its ID added to the Address before it is even persisted.
I didn't test the code so there might be some errors in it.

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.