If I var_dump($object), the field status is updated in the object, but not saved in database.
Can you help me ?
<?php
namespace Acme\MyBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
class SoftDeleteListener {
public function preSoftDelete(LifecycleEventArgs $args){
$em = $args->getEntityManager();
$object = $args->getEntity();
//if entity has field "status"
if($em->getClassMetadata(get_class($object))->hasField('status')){
//set the status with string "deleted"
$object->setStatus('deleted');
}
}
}
?>
You should persist your object and notify the UoW that you have made some changes. You can achieve that adding this in your condition:
$em->persist($object);
$classMetadata = $em->getClassMetadata(get_class($object));
$em->getUnitOfWork()->computeChangeSet($classMetadata, $object);
Related
I am new to EventListeners (even though I can read here and there that it is best to use subscribers) and I got maybe a silly question:
Can I access data from another table (Table 2) in my EventListener plugged in my Entity 1 (Table1).
Here my example:
representation of my table
I have an EventListener on my table 1 and want to access the rows E "crit2" and "crit3" to check their value.
If crit 2 is "combo", I would concatenate on F the value of A and D (Table 1) otherwise, the other, A and D.
I tried a eventListener and get a file NewOrganised.php in the folder EventListener:
<?php
namespace App\EventListener;
use App\Entity\ProductsInfoOrganised;
use Doctrine\Persistence\Event\LifecycleEventArgs;
class NewOrganised
{
// the listener methods receive an argument which gives you access to
// both the entity object of the event and the entity manager itself
public function postPersist(LifecycleEventArgs $args): void
{
$entity = $args->getObject();
// if this listener only applies to certain entity types,
// add some code to check the entity type as early as possible
if (!$entity instanceof ProductsInfoOrganised) {
return;
}
$entityManager = $args->getObjectManager();
// ... do something with the Product entity
}
}
In my entity ProductsInfoOrganised aka Table 1, I added to the end
/**
* #ORM\PrePersist
*/
public function setValuesLeftBlank(): void
{
$this->triabc = $this->packId . $this->adapteGaucher;
$this->triacb = $this->adapteGaucher . $this->packId;
// if( $crit2 == "localisation" ) {
// $this->triId = $this->shopId . $this->triabc;
// } else {
// $this->triId = $this->shopId . $this->triacb;
// }
}
I call it in my services.yaml:
App\EventListener\NewOrganised:
tags:
-
name: 'doctrine.event_listener'
priority: 500
connection: 'default'
I am not sure if I should do as I did in my Entity and call an entityManagerInterface there to get value of the rows wanted or in my EventListener:
public function postPersist(LifecycleEventArgs $args): void
{
$entity = $args->getObject();
$entityRef = $args->getObject();
// if this listener only applies to certain entity types,
// add some code to check the entity type as early as possible
if (!$entity instanceof ProductsInfoOrganised) {
return;
}
if (!$entityRef instanceof ProductsInfoOrganisation) {
return;
}
$entityManager = $args->getObjectManager();
// ... do something with the Product entity
}
Thanks
I am using Doctrine 2.4.6 in my Symfony 2.6.1 project. The problem is that changes made to entity in preUpdate callback are not saved in database. Code follows:
class MyListener {
public function preUpdate(PreUpdateEventArgs $args) {
$entity = $args->getEntity();
$args->setNewValue('name', 'test');
// echo $args->getNewValue('name'); --> prints 'test'
}
}
class DefaultController extends Controller {
/**
* #Route("/commit", name="commit")
*/
public function commitAction(Request $request) {
$content = $request->getContent();
$serializer = $this->get('jms_serializer');
/* #var $serializer \JMS\Serializer\Serializer */
$em = $this->getDoctrine()->getManager();
/* #var $em \Doctrine\Orm\EntityManagerInterface */
$persons = $serializer->deserialize($content, 'ArrayCollection<AppBundle\Entity\Person>', 'json');
/* #var $persons \AppBundle\Entity\Person[] */
foreach($persons as $person) {
$em->merge($person);
}
$em->flush();
return new JsonResponse($serializer->serialize($persons, 'json'));
// Person name is NOT 'test' here.
}
}
The preUpdate doesn't allow you to make changes to your entities. You can only use the computed change-set passed to the event to modify primitive field values. I bet if you check the database you'll see that the Person entities did get updated, you just won't see them in the $persons variable until the next time you manually retrieve them.
What you'll have to do after the flush is retrieve the entities from the database to see their updates values:
$em->flush();
$personIds = array_map(function($person) { return $person->getId(); }, $persons);
$updatedPersons = $em->getRepository('AppBundle:Person')->findById($personIds);
return new JsonResponse($serializer->serialize($updatedPersons, 'json'));
I have a many-to-many relationship between users (the owning side) and user groups, and am having issues using doctrine module's hydrator to create a new user group.
When I create a new user group and hydrate, persist, and flush it, the records change in the database, but the entity variable itself representing the user group doesn't end up with any users in it post-hydration.
Context: We have a REST controller route that we use to create a new user group via POST. It accepts parameters to initialize it with some users via hydration. This operation successfully updates the database, but its response is incorrect. It is supposed to extract the data from the now-persistent entity and echo it back to the client. However, it fails to extract any users, so the response incorrectly returns as an empty group. Not using the hydrator's extract method and instead using more basic doctrine commands fails too--it seems like the entity variable itself is just not kept up to date after being persisted.
So my question really is: why is the hydrator not extracting users? If we've messed up the owner/inverse assocation, why is it working at all (i.e. persisting the users to the database but not to the entity).
Here is the relevant code, probably only the first two blocks are needed.
public function create($data) {
...
$hydrator = $this->getHydrator();
$em = $this->getEntityManager();
$entity = $this->getEntity();
$entity = $hydrator->hydrate($data, $entity);
// Persist the newly created entity
$em->persist($entity);
// Flush the changes to the database
$em->flush();
return $this->createResponse(
201,
true,
'Created item',
$this->getHydrator()->extract($entity)
);
Here is are the setters and getters the hydrator is using:
... more fields...
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="groups")
*/
protected $users;
...
/**
* Getter for users
*
* #return mixed
*/
public function getUsers() {
return $this->users;
}
public function addUsers(Collection $users) {
foreach ($users as $user) {
$user->addGroups(new ArrayCollection(array($this)));
}
}
I think the above is the only relevant code, but I'll include some more in case I'm wrong. Here is the getHydrator method:
public function getHydrator() {
if(null === $this->hydrator) {
$hydrator = new DoctrineObject($this->getEntityManager(), $this->getEntityName());
// Use Entity metadata to add extraction stategies for associated fields
$metadata = $this->em->getClassMetadata($this->getEntityName());
foreach ($metadata->associationMappings as $field => $mapping) {
// Use our custom extraction strategies for single and collection valued associations
if($metadata->isSingleValuedAssociation($field)) {
$hydrator->addStrategy($field, new RestfulExtractionStrategy());
}
else if($metadata->isCollectionValuedAssociation($field)) {
$hydrator->addStrategy($field, new RestfulExtractionCollectionStrategy());
}
}
$this->hydrator = $hydrator;
}
return $this->hydrator;
}
Here is the RestfulExtractionCollectionStrategy (the other strategy isn't being used here, I have verified this).
namespace Puma\Controller;
use DoctrineModule\Stdlib\Hydrator\Strategy\AllowRemoveByValue;
use Doctrine\Common\Collections\Collection;
/**
* You can use this strategy with fields that are collections,
* e.g. one to many. You need to use the RestfulExtractionStrategy
* if you want to get extract entities from a singleton field, e.g. manyToOne.
**/
class RestfulExtractionCollectionStrategy extends AllowRemoveByValue
{
public function extract($value)
{
if ($value instanceof Collection) {
$return = array();
foreach ($value as $entity) {
if(method_exists($entity, 'getId')){
$return[] = $entity->getId();
}
else {
$return[] = $entity;
}
}
return $return;
}
return $value;
}
}
I am not quite familiar with hydration, etc., so your code looks kind of strange to me and I cannot guarantee this will work, but have you tried to refresh the entity after flushing (i.e. $em->refresh($entity)) and maybe return the entity instead of $this->getHydrator()->extract($entity)?
I think I've finally solved it--I added a line to the "setter" method, addUsers which manually updates the users property of the group after updating the related users. I would be a bit surprised if this was best practice, though. I had thought that updating the owning side (the users) would automatically update the inverse side (the user group). Perhaps I was wrong. If anyone else has a better idea I'll gladly give the answer credit to them.
public function addUsers(Collection $users) {
foreach ($users as $user) {
$user->addGroups(new ArrayCollection(array($this)));
// This is the new line (updating the usergroup property manually)
$this->users->add($user);
}
}
Just wondering if it is possible to only use some parts of the symfony form handling. For exampe, when creating CRUD actions via generate:doctrine:crud I get something in my Controller (for handling create User POST requests) that looks like this:
$entity = new User();
$form = $this->createForm(new UserType(), $entity,
array(
'action' => $this->generateUrl('user_create'),
'method' => 'POST',
));
$form->handleRequest($request);
//Here I have a filled Entity
But what I want is to have this in a more reusable solution. Currently I have my business logic in a service called UserModel. Here I also want to have the create method to create, validate and persist a new entity. Tough the UserModel should also be callable from some Command scripts via the console, so I probably won't always have Request nor a Form.
So now from the above code I know that Symfony is already somehow populating data to an Entity according to the UserType definition, but how could I do that manually without having a Form or a Request and instead just some array of data?
So that I don't have to take care of setting the properties myself.
Edit:
The validation is no issue for populating the entity, I'm using the validator later on the populated entity before persisting the data.
And also important for me would be that the passed related entity ids will be handled/loaded automatically.
you may still use the Form component, but instead of using handleRequest, you should use directly submit.
If you are curious, you should look up the code on github for both handleRequest and what it actually does ; you'll see that it just do some verification, takes the data from the Request, and then uses the submit method of the Form.
So, basically, you can use only the submit method with the data you wish to use. It even validates your entity. :)
UPDATE
And for the concern of creating / updating related entities, if your relation have a persist / update cascade, it should roll out from itself without you doing a single thing, except persist + flush on your main entity.
If you do not worry about handling validation like things, you can do something like I have done.
You can define a trait or include the fromArray function in your entity classes.
trait EntityHydrationMethod
{
public function fromArray($data = array())
{
foreach ($data as $property => $value) {
$method = "set{$property}";
$this->$method($value);
}
}
}
If you defined trait, you can use it in your entities like:
class User{
use EntityHydrationMethod;
}
Then from your user model you can define your create function something like:
public function create($data = array())
{
$entity = new User();
$entity->fromArray($data);
return $entity;
}
-Updated-
As you updated your question. you may achieve this by defining a trait or include the createFromArray function in your EntityRepository classes.
trait RepositoryCreateMethod {
public function createFromArray($data)
{
$class = $this->getClassName();
$object = new $class();
$meta = $this->getClassMetadata();
foreach ($data as $property => $value) {
$v = $value;
if(!empty($value) && $meta->hasAssociation($property)) {
$map = $meta->getAssociationMapping($property);
$v = $this->_em->getRepository($map['targetEntity'])->find($value);
if(empty($v)){
throw new \Exception('Associate data not found');
}
}
$method = "set{$property}";
$object->$method($v);
}
return $object;
}
}
If you defined trait, you can use it in your Repository like:
class UserRepository{
use RepositoryCreateMethod;
}
Then you can use this something like call from controller:
$user = $this->getDoctrine()
->getRepository('YourBundle:User')
->createFromArray($data);
Using Symfon2.0 and Doctrine, I am trying to do the following:
Insert a new row in the database
in the moment of inserting, automatically generate a code and also insert it.
That code is related to the ID of the table. Something like <something>/row_id
How can I easily do this?
I've been trying Doctrine Livecycle callbacks. But:
PrePersist still doesn't have the ID.
PostPersist have the ID, but somehow the insertion has already been done and, although I can set any property from the Entity, I don't know how to make it to be persisted "again" to the database.
Any clue overthere?
Any other way to do it properly?
In your create controller:
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
$entity->setCode($code);
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('entity_show'
,array('id' => $entity->getId())));
}
The second block ($entity->setCode...) is what you need to add and will need to be customized to suit your purposes
Alternatively, you can use Listeners:
<?php
namespace ormed\ormedBundle\Listener;
use Doctrine\ORM\Event\OnFlushEventArgs; use
Symfony\Component\DependencyInjection\Container;
class LastModifiedListener {
private $container;
public function __construct(Container $container){$this->container = $container;}
public function onFlush(OnFlushEventArgs $eventArgs)
{
$entityManager = $eventArgs->getEntityManager();
$unitOfWork = $entityManager->getUnitOfWork();
foreach ($unitOfWork->getScheduledEntityInsertions() AS $entity) {
$entity->setCode( $code );
$entityManager->persist($entity);
$classMetadata = $entityManager->getClassMetadata(get_class($entity));
$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity);
} } }