doctrine 2 entity is not filled with childs element - doctrine-orm

Within my database I have two related tables.
AppBooking
AppBookingService
AppBookingService has a foreign key to AppBooking
I use this method to make an insertion in both the first and second table.
public function bookingExecute($data){
try{
$this->em->beginTransaction();
$appBooking = $this->_fillAppBooking($data);
$this->em->persist($appBooking);
if(array_key_exists("appService",$data) && is_array($data['appService']) && count($data['appService']) > 0){
foreach($data['appService'] as $bookingService){
$appBookingService = $this->_fillAppBookingService($bookingService,$appBooking);
$this->em->persist($appBookingService);
$this->em->flush();
}
}
$this->em->flush();
$this->em->commit();
return $appBooking;
}catch (\Exception $ex){
$this->em->rollback();
throw new BookingException("",BookingError::BOOKING_QUERY_ERROR);
}
}
The data is written correctly
After that, in the same http request, I invoke the method below in order to have AppBooking Service data within my entity
$appBooking = $this->bookingService->findOne($id);
The problem that the AppBooking entity I get does not contain AppBookingService
The method
$appBooking->getServices()->count()
returns 0
If I make the same call in another http request I get the desired result.
It is as if doctrine did not update the entity in that same request
This is a part of AppBooking
/**
* #var Collection
* #ORM\OneToMany(targetEntity="AppBookingService", mappedBy="idBooking")
*/
private $services;
public function __construct() {
$this->services = new ArrayCollection();
}
This is part of AppBookingService
/**
* #var \Entity\Entity\AppBooking
*
* #ORM\ManyToOne(targetEntity="Entity\Entity\AppBooking", inversedBy="services")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="ID_BOOKING", referencedColumnName="ID_BOOKING", nullable=true)
* })
*/
private $idBooking;

This is because when running
$appBooking = $this->bookingService->findOne($id);
The entity is fetched from his internal proxy cache. No database query is executed to get the entity `s data. The same is true for its already loaded associations. The Proxy cache is valid during the execution of a http request.
To solve this, you either have to update the $services Collection manually or refresh the entity.
$this->em->refresh($appBooking);
Refreshes the persistent state of an entity from the database, overriding any local changes that have not yet been persisted.
You can also clear the entire entity manager cache before calling the findOne() method.
$this->em->clear();
Clears the EntityManager. All entities that are currently managed by this EntityManager become detached.
References
How do I force doctrine to reload the data from the database?

Related

Symfony 4: Updating an existing entity through a deserialized entity ends up with a new related item or with an ORMInvalidArgumentException

That's my first question here on Stackoverflow.com and before I'll write to much. First the controller function:
/**
* #Rest\Patch("/identifiers/v2/{id}")
*
* #ParamConverter("identifier")
* #ParamConverter("identifierPatch", converter="fos_rest.request_body")
*/
public function patchAction(Identifier $identifier, Identifier $identifierPatch)
{
$identifier->setLandingPage($identifierPatch->getLandingPage());
$identifier->setIdentifier($identifierPatch->getIdentifier());
$identifier->setIsUsed($identifierPatch->getIsUsed());
$this->entityManager->flush();
/**
* Just for debugging...
*/
$view = $this->view([
'identifier' => $identifier,
'identifierPatch' => $identifierPatch
]);
return $this->handleView($view);
}
When i try to UPDATE an existing entity this way I get an ORMInvalidArgumentException with a message "A new entity was found through the relationship (...)"
When I set cascade={"persist"} on the related entity:
/**
* #ORM\ManyToOne(targetEntity="App\Entity\LandingPage", inversedBy="identifiers")
* #ORM\JoinColumn(nullable=false)
* #Assert\NotNull()
* #Serializer\Type("App\Entity\LandingPage")
*/
private $landing_page;
... the related entity will be inserted as new entity and that's not what I am looking for.
I could use $this->entityManager->merge($identifier) but that's not what I am looking for aswell, because I'll need to do some manual validations in future and I would like to return the entity as response (the related entity will be null when not updated) and $this->entityManager->merge() will be deprecated in Doctrine 3.
Question: Is there any way to update the given entity with the deserialized entity?
Greetings,
Danny Endert
EDIT (1):
Well, I guess i found a solution regarding this "issue".
services.yaml
jms_serializer.object_constructor:
alias: jms_serializer.doctrine_object_constructor
public: false
Now I'm not getting any exception and the related entity will not be inserted as new entity.

Symfony - Validate entity differently in INSERT, UPDATE or DELETE

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

Delete operations in doctrine 2

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.

Doctrine2 - Get entity ID before flush

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

How to get a Respository from an Entity?

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.