Can anyone tell me how to implement Doctrine NotifyPropertyChanged in Symfony?
I have implemented this code in an Entity that will notify about is changes. But how to add listener for these changes in another Entity?
I mean I want to handle this situation: Entity Book has oneToMany to Authors. When something is being changed in a partcular Author, I want to react on it in a Book Entity. So I have implemented NotifyPropertyChanged in Author. In a setter I invoke notifications, but how to add this listener to a Book entity?
It looks like your use case is a little bit different: the ǸotifyPropertyChanged` is usefull to tell Doctrine your entity changed. By default, Doctrine2 will iterate over all properties and compare them (so that's "automatic"). IMO, drop it. Only use case I see for it right now are some very specific cases were using the default policy would cost too much.
Another way to do would be to use some lifecycle events. Changing other entities from a preUpdate or postUpdate is notoriously difficult. Doctrine already started calculting the "changeset", what to persist to DB, and there's no easy way to add another entity to it.
Since you are ok writing some code inside the setters, the easier path is something like this:
class Author
{
public function setName($name)
{
// ...
// for each setter, call onChanged
$this->onChange();
}
private function onChange()
{
foreach ($this->books as $book) {
// maybe call some method on your books, like onAuthorChanged() ?
}
}
}
Related
I have read the docs about lifecycle events, and several questions here on SO about changing or persisting new entities during lifecycle events. Calling EnitityManager::flush() seems to be a problem.
Ok, but looking carefully at the docs, there is a code example where the field is changed in postPersist, but no flush is called.
I checked that, and the suggested change is not written to the DB. Only the object being persisted does receive the change.
<?php
/** #Entity #HasLifecycleCallbacks */
class User
{
// ...
/**
* #Column(type="string", length=255)
*/
public $value;
/** #PostPersist */
public function doStuffOnPostPersist()
{
$this->value = 'changed from postPersist callback!';
}
}
Maybe one should add this to the docs. I was mislead at first.
However, when adding the LifecyleEventArgs argument and flushing the contained EntityManager, they are written to DB:
/** #PostPersist */
public function doStuffOnPostPersist(LifecycleEventArgs $args)
{
$this->value = 'changed from postPersist callback!';
$args->getEntityManager()->flush(); // works in my tests. Is this safe to use ?
}
I don't know how to interpret the docs about whether it is OK or not to call flush inside the postPersist.
As you can see, I am searching for a reliable way to perform some kind of postprocessing to my entities after inserting or updating them. I have to use postPersist, since I need the auto-generated primary key value.
Side question: If yes, it is ok to flush, could I then also persist other objects in PostUpdate? Like so:
/** #PostPersist */
public function doStuffOnPostPersist(LifecycleEventArgs $args)
{
$this->value = 'changed from postPersist callback!';
$obj = new OtherObject("value " . $this->value);
$args->getEntityManager()->persist($obj);
$args->getEntityManager()->flush(); // works in my tests. Is this safe to use ?
}
Side-side question: I have tried the last variant, and it seems to work. But is it efficient, or am I possibly creating deep recursion stacks? According to the docs, the postPersist code is called during flush, so if I call flush during postPersist, I have to be careful not to persist an object that executes the same handler, which would lead to infinite recursion. Is this correct?
I checked that, and the suggested change is not written to the DB. Only the object being persisted does receive the change.
Maybe one should add this to the docs. I was mislead at first.
The code in the docs doesn't try to persist this modification of the value property
in database that's why no flush() is called. It's just show an example and this value could also be an unmapped to the database property of class User.
I don't know how to interpret the docs about whether it is OK or not to call flush inside the postPersist.
It is ok to call flush() on a PostPersist lifecycle callback in order to change a mapped property
of your entity. In your PostPersist callback your entity has already been inserted in your database. By changing the property
value and calling flush() your entity will be flag as to be updated, so the PostPersist event won't
be dispatched again (instead Pre/PostUpdate events will be dispatched).
Side question: If yes, it is ok to flush, could I then also persist other objects in PostUpdate?
It is also ok to persist an new object of another entity class in a PostPersist event callback with no problem,
but if you try to persist an object of the same (User) class in this PostPersist callback you will have an
infinite recursion, as you can easily understand.
Side-side question: I have tried the last variant, and it seems to work. But is it efficient, or am I possibly creating deep recursion stacks?
As I have explained before this code doesn't create too deep recursion stacks or infinite loops if not persisting objects of the same class (User) in which the callback belongs. The flush() will be called exactly two times. Although things could get more complicated when having also to deal with associations, in your example there is not such problem.
I am trying to implement a time tracking mechanism in my custom project management app.
This app contains multiple entities (tickets, projects, wiki pages, sprints, ...)
I want my timetracking to be "generic" in the sense that I want users to be able to log time against a ticket, project, wiki page, ...well any entity actually.
Now, I am trying to figure out what database schema (relation) to use for my TimeLog entity.
I could theoretically create a relation to each entity I have in my app, but that will require me to keep updating schema when I introduce new entities later on.
Has anybody every implemented anything like this?
All suggestions are welcomed.
Many thanks in advance.
I faced a similar situation in my app while trying to add comments, likes and other types of elements whose behaviour would not really depend on the entity they are attached to.
The solution I eventually chose was to have two fields in my referring entities (e.g. Comment) to hold both the id of the entity being referred to and its type. Since I was using this multiple times, I put the properties into the following trait:
namespace AppBundle\Entity\Traits;
use Doctrine\ORM\Mapping as ORM;
trait EntityReferenceTrait
{
/**
* #ORM\Column(name="reference_id", type="integer")
*/
private $referenceId;
/**
* #ORM\Column(name="reference_type", type="integer")
*/
private $referenceType;
/* ... setters & getters ... */
}
Then I could use it in the entities holding those kind of references:
/**
* #ORM\Table(name="comments", indexes={#ORM\Index(name="references", columns={"reference_id", "reference_type"})})
* #ORM\Entity(repositoryClass="AppBundle\Repository\Comment\CommentRepository")
*/
class Comment
{
/* ... other traits ... */
use \AppBundle\Entity\Traits\EntityReferenceTrait;
/* ... other fields & methods ... */
}
Note: I added an index for the references but it is not necessary for the whole thing to work properly. If you use such an index, beware of the order of your WHERE clauses if you want to benefit from it
In order to improve performance a bit and add additional configurations depending on the type of the entity being referred to, I handled settings directly in the config of my app. Thus, I have something like:
commentables:
news:
classname: AppBundle\Entity\News\News
type_id: 1
browse_route: news_comments
multiple_locales: false
...
This allows me to know precisely what kind of entities my Comment entity can refer. It also allows me to automatically hook specific listeners to the entities being referred to so that the removal of a referred entity triggers the removal of the related comments for example. I do this by processing the configuration in AppBundle/DependencyInjection/AppExtension.php (more about this here) and saving the needed listeners list into a parameter. Then, by adding a listener to the loadClassMetadata event, I can effectively handle the removal of related entities for example.
Here is the listener that hooks the listeners for specific lifecycle events of referred entities by using addEntityListener on the ClassMetadata instance:
namespace AppBundle\Listener;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class MappingListener
{
private $entityListenersMapping = [];
/**
* #param array $mappingConfig Associative array with keys being listeners classnames and values being arrays associating an event to a method name
*/
public function __construct(array $mappingConfig)
{
$this->entityListenersMapping = $mappingConfig;
}
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
if(!array_key_exists($classMetadata->name, $this->entityListenersMapping))
{
return;
}
// Hook the entity listeners in the class metadata
foreach($this->entityListenersMapping[$classMetadata->name] as $listenerClassName => $eventsCallbacks)
{
foreach($eventsCallbacks as $event => $methodName)
{
$classMetadata->addEntityListener($event, $listenerClassName, $methodName);
}
}
}
}
Either way, for this part, it mainly depends on the specific needs of your entity but I guess it is quite a common need that these "soft" foreign keys emulate a ON DELETE CASCADE behaviour via preRemove and postRemove events.
Considering the handling of those references and the entities owning them, I also created a EntityRefererManagerTrait to easily create services that manage those entities so that the other components interacting with them would not have to worry about the underlying configuration.
The interface of most public methods of those managers thus usually require:
the classname of the entity being referred to
the numeric id of the entity being referred to
With those two info and the configuration retrieved in my manager service, I can easily interact with the database even if, in my case, it stores an integer defined in the configuration as the reference type in place of the classname of the entity being referred to.
Based on this, I can enable comments, likes, votes, subscriptions and so on for any of my app entities (as long as its primary key is a single integer) with just a few more lines in my configuration files. No need to update database schema and with proper lifecycle events listeners being hooked, no worries about orphan entries in the database.
On a side note, it should be mentioned that you won't be able to retrieve referring entities from the inverse side as it won't be a real association. You won't benefit from foreign keys behaviours either. Thus, even if you emulate the ON DELETE CASCADE behaviour by listening to remove events, you won't be able to ensure that there are no orphans in your database if some DELETE operations are performed directly via DQL for example.
Question: how to trigger Doctrine lifecycle events in my code, having the entity data available?
Details
I have an active listener on the Doctrine postPersist and postUpdate events.
I cannot modify / override this listener.
In some places in my code, for performance reasons, I use DBAL to save data instead of ORM methods.
I'd like to stick to the same event system.
Thank you in advance for your help.
Extending #Cerad answer, here's a very basic sample code to achieve the result (trigger a Doctrine LifeCycle event). This sample assumes we're in a Symfony controller:
use Doctrine\ORM\Event\LifecycleEventArgs;
// ...
$user = new AppBundle\Entity\User();
// ... do something with the user
$entityManager = $this->getDoctrine()->getManager();
$eventManager = $entityManager->getEventManager();
$eventArgs = new LifecycleEventArgs($user, $entityManager);
$eventManager->dispatchEvent(\Doctrine\ORM\Events::postPersist, $eventArgs);
Assuming you have access to the entity manager then:
$eventManager = $entityManager->getEventManager();
After that you can build and dispatch events per the documentation.
I checked to see if there is a predefined service but did not see one. But you probably define one as a factory and inject it as needed.
Hope this is what you are asking for.
I'm developing a medium scale application using Symfony2 and Doctrine2. I'm trying to structure my code according to the SOLID principles as much as possible. Now here is the question:
For creating new Entities, I use Symfony Forms with proxy objects i.e: I don't bind the form directly to my Entity, but to some other class that will passed to some service which will take the needed action based on the received data, i.e: the proxy class serves as a DTO to that service which I will call the Handler. Now considering the Handler doesn't have a dependency on the EntityManager, where should I do calls to EntityManager::persist() and EntityManager::flush()? I am usually comfortable with putting flush in the controller but I'm not so sure about persist since the controller shouldn't assume anything about what the Handler does, and maybe Handler::handle (the method that the form data is passed to) does more than just persist a new Entity to the database. One Idea is to create some interfaces to encapsulate flush and persist and pass them around, which will act as wrappers around EntityManager::flush() and EntityManager::persist(), but I'm not so sure about it since EntityManager::flush() might create unwanted consequences. So Maybe I should just create an interface around persist.
So My question is where and how to make the call to persist and flush, in order to get the most Solid code? Or am I just overcomplicating things in my quest of best practices?
If you have a service that will handle tasks upon your entities, to me, the right way is to inject EntityManager into your service definition and do persist and flush operation inside it.
Another way to proceed, if you want to keep separate that logic, is to create an EventSubscriber and raise a custom event from your "entity service" when you're ready to do persist and flush operations
My 2 cents:
about flush, as it calls the DB, doing it like you already do when needed in your controllers sounds good to me.
about presist, it should be called in your Handler when your entity is in a "ready to be flushed" state. A Persister interface with only the persist method as a dependency of your Handlers, and a DoctrinePersister implementation injected in them looks OK.
Another option here - you can implement save() method in your entity repository class and make persistence there. Inject your entity repository as dependency into your Handler class.
If you don't want to couple your service and business logic to the EntityManager (good job), SOLID provides a perfect solution to separate it from your database logic.
//This class is responsible for business logic.
//It knows nothing about databases
abstract class CancelOrder
{
//If you need something from the database in your business logic,
//create a function that returns the object you want.
//This gets implemented in the inherited class
abstract protected function getOrderStatusCancelled();
public function cancel($order)
{
$order->setOrderStatus($this->getOrderStatusCancelled());
$order->setSubmittedTime(new DateTime());
//and other business logic not involving database operations
}
}
//This class is responsible for database logic. You can create a new class for any related CRUD operations.
class CancelOrderManager extends CancelOrder
{
public function __construct($entityManager, $orderStatusRepository)...
public function getOrderStatusCancelled()
{
return $this->orderStatusRepository->findByCode('cancelled');
}
public function cancel($order)
{
parent::cancel($order);
$this->entityManager->flush();
}
}
In a Symfony 2.5 preUpdate event listener, I'm confused about whether/when to pass in LifeCycleEventArgs or PreUpdateEventArgs (and which one).
The Doctrine Documentation says that starting in 2.4 you use LifeCycleEventArgs:
Since 2.4 the triggered event is given to the lifecycle-callback. With
the additional argument you have access to the EntityManager and
UnitOfWork APIs inside these callback methods.
But the example directly below that and all subsequent examples show PreUpdateEventArgs:
class User
{
public function preUpdate(PreUpdateEventArgs $event)
{
if ($event->hasChangedField('username')) {
// Do something when the username is changed.
}
}
}
So does that mean LifeCycleEventArgs (if it's available in your version) will do all that PreUpdateEventArgs does and more? They both seem to work (i.e. no errors).
And using the new LifeCycleEventArgs, is it still necessary to use $entity->setNewValue('fieldName', $value)? Is preUpdate still as restrictive or can we just do $entity->setFieldName($value)?
On Doctrine 2.2, class PreUpdateEventArgs extends LifecycleEventArgs and it only declares the entity changeset for the entity being pre-updated.
So yes, PreUpdateEventArgs does more, though theoretically (i.e. I didn't test that!) you could achieve the same using the EM's UOW:
In the LifecycleEventArgs, you can access the entity that has triggered the event using getEntity().
Same class, you can access the EM using getEntityManager()
Hence: $this->getEntityManager()->getUnitOfWork()->getEntityChangeSet($this->getEntity()) should give you the same changeset as the one provided by PreUpdateEventArgs
Conclusion: PreUpdateEventArgs facilitates a job that could already be achieved using slightly more code ;)
To dive a little deeper: UnitOfWork::commit() triggers the update by calling UnitOfWork::executeUpdates, which dispatches an instance of PreUpdateEventArgs filled with the already-computed changeset for this entity, directly taken from the UOW's changesets. (Again: I'm using 2.2, might be different on your release.)
About the $event->setNewValue('field', 'value') vs. $entity->setField('value'), I believe you need to just update the changeset (which is setNewValue's job). Calling the entity's setter directly might de-synchronize the entity from its state in DB or, more probably, just be useless as it shouldn't be taken into consideration. Except if you manually call the UOW's computeChangeSets().
Hope this helps!