Is there any way to get an entity ID before the persist/flush?
I mean:
$entity = new PointData();
$form = $this->createForm(new PointDataType(), $entity);
If I try $entity->getId() at this point, it returns nothing.
I can get it working by:
$em->persist($entity);
$em->flush();
(supposing $em = $this->getDoctrine()->getEntityManager();)
How can I achieve this?
If you want to know the ID of an entity before it's been persisted to the database, then you obviously can't use generated identifiers. You'll need to find some way to generate unique identifiers yourself (perhaps some kind of hash function can produce unique-enough values).
This is rarely a good idea, though, so you should be careful.
I would think very carefully about why I need to know the identifier before flush. Doctrine is quite good at letting you build up a big object graph, and persist/flush it all at once. It seems likely that you've got something ugly in your architecture that you're trying to work around. It might be a good idea to review that before going down the application-generated-id route.
You can use the #PostPersist annotation. A method annotated with that will be executed just before the flush terminate and the entity Id is available already.
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/events.html
postPersist - The postPersist event occurs for an entity after the entity has been made persistent. It will be invoked after the database insert operations. Generated primary key values are available in the postPersist event.
<?php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class PointData
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
/**
* #ORM\PostPersist
*/
public function onPostPersist()
{
// Put some simple logic here that required the auto-generated Id.
$this->doSomething($this->id);
}
...
}
you can use an auto generate ID to get a key like universally unique identifiers (UUID) or you can take the events of symfony:
postFlush - The postFlush event occurs at the end of a flush operation.
Doctrine best practices says,
You should avoid auto-generated identifiers. because:
Your DB operations will block each other
You are denying bulk inserts
You cannot make multi-request transactions
Your object is invalid until saved
Your object does not work without the DB
So you can use UUIDS instead
public function __construct() {
$this->id = Uuid::uuid4();
}
Also, Doctrine supports the UUID generation strategy since version 2.3.
Not sure why you need the ID before flushing, but, if you really need to persist the entity without saving to the database you can try using Transactions.
Try something like this:
$em->beginTransaction();
$em->persist($entity);
$em->flush();
$id = $entity->getId();
//do some stuff and save when ready
$em->commit();
$em = $this->getDoctrine()->getManager();
$entity = new PointData();
$em->persist($entity);
$entity->getId() <-- return <int>
$em->flush();
after persist you can get id
Related
I'm aware that there are extensions for Doctrine that manage tree / nested set behaviors, but that seems very much overkill for what I want.
I simply have a model called Faq with the fields question, answer and number, createdAt and updatedAt. The number column is used to be able to edit the order the questions appear in on the page.
And I'm using EasyAdminBundle to provide a simple admin panel for my client to edit the FAQ with.
Now here's the thing, let's say there are 5 questions and the client wants to make the 5th question the 3rd question. What I would like is that he can just simply edit the 5th question's number field with the value 3 and that all the other number fields of the other entities automatically adjust to this change. So 3 and 4 now become 4 and 5 respectively.
I'm assuming I need some kind of event listener for this, but I can't quite figure out what kind.
All I've figured out so far is that once I have the right event listener, I should do this when it executes:
function updateNumbers(EntityManagerInterface $em)
{
$faqRepo = $em->getRepository(Faq::class);
$faqs = $faqRepo->findAll();
// ^ that is already correctly sorted, based on number and updatedAt
foreach($faqs as $i => $faq) {
$faq->setNumber($i+1);
}
$em->flush();
}
Now I just need to know how to make sure that that function is triggered at the right moment. Any help?
With the code you wrote you should probably go for a postFlush even, but be aware that calling $em->flush() from the event listener will trigger the event yet again so you need to have a recursion guard. You may also want to write additional logic that will run the re-index logic only if a position of certain post actually did changed.
For a slightly simpler approach you could hook into a preUpdate event. When a number of an entry changes, you fire a DB query that will update all entries with number greater than or equal to the new value and increase their number by 1.
Okay I solved it by doing the following:
// src/EventListener/FaqSorter.php
namespace App\EventListener;
use App\Entity\Faq;
use Doctrine\ORM\Mapping as ORM;
use App\Repository\FaqRepository;
use Doctrine\ORM\Event\LifecycleEventArgs;
class FaqSorter
{
/**
* #ORM\PrePersist
* #ORM\PostUpdate
* #ORM\PostRemove
*/
function updateNumbers(Faq $faq, LifecycleEventArgs $event)
{
static $hasRun = false;
// This is not the prettiest solution to prevent recurrence,
// but AFAIK I never need to run this more than once per request.
if ($hasRun) return;
$em = $event->getEntityManager();
/** #var FaqRepository $faqRepo */
$faqRepo = $em->getRepository(get_class($faq));
$faqs = $faqRepo->findAll();
// ^ that is already correctly sorted, based on number and updatedAt
if ($faq->getNumber() === null) {
// If the number was null then this was triggered
// by a PrePersist event and then I'll just
// put it at the end of the list.
return $faq->setNumber(count($faqs) + 1);
}
foreach($faqs as $i => $faq) {
$faq->setNumber($i+1);
}
$hasRun = true;
$em->flush();
}
}
And then in config/services.yaml:
services:
# ...
App\EventListener\FaqSorter:
tags:
- { name: doctrine.orm.entity_listener }
And in the src/Entity/Faq.php:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\FaqRepository")
* #ORM\EntityListeners({"App\EventListener\FaqSorter"})
*/
class Faq {
// ...
}
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 want validate an entity doctrine differently when the entity is created, updated or deleted.
There is an entity constraint validator in my entity class.
// src/AppBundle/Entity/AcmeEntity.php
use AppBundle\Validator\Constraints as AcmeAssert;
/**
* #AcmeAssert\CustomConstraint
*/
class AcmeEntity
{
// ...
protected $name;
// ...
}
In my CustomConstraint I want determine if the Entity will be updated, created or delete for execute a specific validator.
Using unit of work is a solution ?
What is the best way to make this?
I think this problematic is common in lot of application ?
Thank's all ;)
You could either use validation groups based on the submitted data or handle itwhen you create the form by passing the validation group.
For example, in your controller when you create the form;
$form = $this->createForm(new AcmeType(), $acme, ['validation_groups' => ['create']]);
Then you entity would be something like;
/**
* Get name
*
* #Assert\Length(min=2, max=11, groups={"create", "update"})
* #AcmeAssert\ContainsAlphanumeric(groups={"create"}) // only applied when create group is passed
* #return string
*/
public function getName()
{
return $this->name;
}
This is what validation groups are made for.
Since Symfony Forms read validations from entity annotations and use internally the Validator component you'd have a look at these articles in the documentation:
http://symfony.com/doc/current/form/validation_groups.html
http://symfony.com/doc/current/validation/groups.html
http://symfony.com/doc/current/validation/sequence_provider.html
I'm having a hard time making sense of the Doctrine manual's explanation of cascade operations and need someone to help me understand the options in terms of a simple ManyToOne relationship.
In my application, I have a table/entity named Article that has a foreign key field referencing the 'id' field in a table/entity named Topic.
When I create a new Article, I select the Topic from a dropdown menu. This inserts an integer into the 'topic_id' foreign key field in the Article table.
I have the $topic association set up in the Article entity like this:
/**
* #ManyToOne(targetEntity="Topic")
* #JoinColumn(name="topic_id", referencedColumnName="id", nullable=false)
*/
private $topic;
The Topic entity doesn't have any reciprocating annotation regarding the Article entity. Topics don't care what Articles reference them and nothing needs to happen to a Topic when an Article that references the Topic is deleted.
Because I'm not specifying the cascade operation in the Article entity, Doctrine throws an error when I try to create a new Article: "A new entity was found through a relationship that was not configured to cascade persist operations. Explicitly persist the new entity or configure cascading persist operations on the relationship."
So I know I need to choose a cascade operation to include in the Article entity, but how do I know which operation to choose in this situation?
From reading the Doctrine manual, "detach" sounds like the right option. But researching others' similar questions here and here makes me think I want to use "persist" instead.
Can anyone help me understand what "persist," "remove," "merge," and "detach" mean in terms of a simple ManyToOne relationship like the one I've described?
In the Doctrine2 documentation "9.6. Transitive persistence / Cascade Operations" there are few examples of how you should configure your entities so that when you persist $article, the $topic would be also persisted. In your case I'd suggest this annotation for Topic entity:
/**
* #OneToMany(targetEntity="Article", mappedBy="topic", cascade={"persist", "remove"})
*/
private $articles;
The drawback of this solution is that you have to include $articles collection to Topic entity, but you can leave it private without getter/setter.
And as #kurt-krueckeberg mentioned, you must pass the real Topic entity when creating new Article, i.e.:
$topic = $em->getRepository('Entity\Topic')->find($id);
$article = new Article($topic);
$em->persist($article);
$em->flush();
// perhaps, in this case you don't even need to configure cascade operations
Good luck!
If you have a #OneToMany unidirectional association, like that described in section 6.10 of the Doctrine Reference, then most likely you forgot to persist the Topic before calling flush. Don't set the topic_id primary key in Article. Instead set the Topic instance.
For example, given Article and Topic entities like these:
<?php
namespace Entities;
/**
#Entity
#Table(name="articles")
*/
class Article {
/**
* #Id
* #Column(type="integer", name="article_id")
* #GeneratedValue
*/
protected $id;
/**
* #Column(type="text")
*/
protected $text;
/**
* #ManyToOne(targetEntity="Topic", inversedBy="articles")
* #JoinColumn(name="topic_id", referencedColumnName="topic_id")
*/
protected $topic;
public function __construct($text=null)
{
if (!is_null($text)) {
$this->text = $text;
}
}
public function setArticle($text)
{
$this->text = $text;
}
public function setTopic(Topic $t)
{
$this->topic = $t;
}
}
<?php
namespace Entities;
/**
#Entity
#Table(name="topics")
*/
class Topic {
/**
* #Id
* #Column(type="integer", name="topic_id")
* #GeneratedValue
*/
protected $id;
public function __construct() {}
public function getId() {return $this->id;}
}
After you generate the schema:
# doctrine orm:schema-tool:create
your code to persist these entities would look like something this
//configuration omitted..
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
$topic = new Entities\Topic();
$article1 = new Entities\Article("article 1");
$article2 = new Entities\Article("article 2");
$article1->setTopic($topic);
$article2->setTopic($topic);
$em->persist($article1);
$em->persist($article2);
$em->persist($topic);
try {
$em->flush();
} catch(Exception $e) {
$msg= $e->getMessage();
echo $msg . "<br />\n";
}
return;
I hope this helps.
I have an Entity called Game with a related Repository called GameRepository:
/**
* #ORM\Entity(repositoryClass="...\GameRepository")
* #ORM\HasLifecycleCallbacks()
*/
class Game {
/**
* #ORM\prePersist
*/
public function setSlugValue() {
$this->slug = $repo->createUniqueSlugForGame();
}
}
In the prePersist method, I need to ensure that the Game's slug field is unique, which requires a database query. To do the query, I need access to the EntityManager. I can get the EntityManager from inside GameRepository. So: how do I get the GameRespository from a Game?
You actually can get the repository in your entity and only during a lifecycle callback. You are very close to it, all you have to do is to receive the LifecycleEventArgs parameter.
Also see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html
use Doctrine\ORM\Event\LifecycleEventArgs;
/**
* #ORM\Entity(repositoryClass="...\GameRepository")
* #ORM\HasLifecycleCallbacks()
*/
class Game {
/**
* #ORM\prePersist
*/
public function setSlugValue( LifecycleEventArgs $event ) {
$entityManager = $event->getEntityManager();
$repository = $entityManager->getRepository( get_class($this) );
$this->slug = $repository->createUniqueSlugForGame();
}
}
PS. I know this is an old question, but I answered it to help any future googlers.
You don't. Entities in Doctrine 2 are supposed to not know of the entity manager or the repository.
A typical solution to the case you present would be to add a method to the repository (or a service class) which is used to create (or called to store) new instances, and also produces a unique slug value.
you can inject the doctrine entity manager in your entity
(using JMSDiExtraBundle)
and have the repository like this:
/**
* #InjectParams({
* "em" = #Inject("doctrine.orm.entity_manager")
* })
*/
public function setInitialStatus(\Doctrine\ORM\EntityManager $em) {
$obj = $em->getRepository('AcmeSampleBundle:User')->functionInRepository();
//...
}
see this : http://jmsyst.com/bundles/JMSDiExtraBundle/1.1/annotations
In order to keep the logic encapsulated without having to change the way you save the entity, instead of the simple prePersist lifecycle event you will need to look at using the more powerful Doctrine events which can get access to more than just the entity itself.
You should probably look at the DoctrineSluggableBundle or StofDoctrineExtensionsBundle bundles which might do just what you need.