Based on question and this answer, i confuesd what the event preUpdate() in combination with symfony and doctrine really does.
To be exact, i mean this line in the answer above:
$event->setNewValue('password', $user->getPassword());
Why i need the extra step of manually setting the new value?
I thought that doctrine handles the event and update the entity by itself without a call of e.g. flush?
Supportive of the thrown exception
Field "password" is not a valid field of the entity "...\UserBundle\Entity\User" in PreUpdateEventArgs.
when i use this, i don't understand why i need to modify the entity manually.
Extended info about the exception: it's only thrown when users logged in. Not when users are registered or edited.
The setNewValue() method can be used to change the value of fields that have been marked as updated. It doesn't work if, for whatever reason, the field you want to update isn't already changed.
Usually, setting the password to null is enough to add it to the changeset. In the event that it isn't, you can request the unit of work recompute the changes.
For example:
<?php
if ($event->hasChangedField('password')) {
$event->setNewValue('password', $newPassword);
}
else {
$em = $event->getEntityManager();
$uow = $em->getUnitOfWork();
$uow->recomputeSingleEntityChangeSet(
$em->getClassMetadata(User::class),
$entity
);
}
Related
I'm new in Drupal 8 and I need to update the user when a node of a specific content type is created or updated. I found hook_entity_create but this hook acts when creating a new entity. Any solution?
There're 3 main hooks you can ustilise here:
hook_entity_insert: for when an entity is actually created
hook_entity_update: for when an entity is updated
hook_entity_delete: for when an entity is deleted
Note: Beware that this hook will be called on any entity so unless you intend to make whatever operation you're performing run on every entity, do something like this:
function yourmudolename_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {
if ($entity instanceof \Path\to\your\EntityInterface){
// Your code here
}
}
Or better still use the entity type hook insead.
hook_ENTITY_TYPE_insert
hook_ENTITY_TYPE_update
hook_ENTITY_TYPE_delete
See here: https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21entity.api.php/function/hook_ENTITY_TYPE_insert/8.5.x for details.
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
I am developing a new application using object oriented approach with some REST involved, I am not using any frameworks.
The question I have is where is the best place to validate a user’s input in a setter like below:
public function setSalary($salary)
{
if (Validator::money($salary))
$this->salary = $salary;
else
return 'Error that is an invalid number';
}
Or in the controller?
public function updateSalary()
{
$errors = array();
if (Validator::money($_POST['salary']))
$salary = $_POST['salary'];
else
$errors ['salary'] = 'Error that is an invalid number';
if(count($errors))
return $errors;
$employee = new Employee($_POST['e_Id']);
$employee->setSalary($salary);
$employee->save();
}
If I was to put in the setter how should my controller look, and return validation errors?
I have seen most people do validation in the controller, however I think should be the models responsibility for validation as it going to be using the data, and we can reuse that model without repeating ourselves.
However there can be times when validation rules may need to be different in some special cases like different validation for a different view or different validation for a supper admin.
Which one would you say is in accordance with best practices?
First of all, since you seem to aspire to implement MVC-like structure, lets start by some general mistakes, that are not related to validation directly.
Only part of your code, containing PHP superglobals, should be the bootstrap stage. Having superglobals sprinkled all over your code makes it really hard to test. And your code also becomes tightly couple to your HTML, via the <input> names.
Even if your for or if statement contains a single line, you should always use curly brackets. Well, in general your code should follow the PSR-1 and PSR-2 guidelines.
Controllers should not have any logic, or be dealing with saving of data. Read this post, maybe it clears some things up.
Ok .. now back to the original subject.
In general there are two schools of thought:
You do the validation in the domain entity
Your domain entity (in your case Employee) contains all the business roles, that pertain to it. And it can use those rules to assess, if it is in a valid state.
The code would go something like this:
$employee = new Entity\Employee;
$employee->setID($id);
$employee->setSalary($money);
if ($employee->isValid()) {
$mapper = new Mapper\Employee($dbConn);
$mapper->store($emplyee);
}
You never create invalid domain entity
This approach comes from DDD, where you domain entity is created by some other class and it can only be changes from one valid state to another valid state. Essentially, if you want to explore this approach, you will have to read this book (probably several times).
Also, there is one other validation form, which is note covered by the previous two: data integrity checks. This is type of validation, that is actually done my RDBMS. For example, the UNIQUE constraints.
When you encounter ans integrity violation, it usually would throw an exception, that you handle in service layer.
Validation must be called every time you write data to the database. So in this case from the controller. The actual validation happens in the model. The model is the object, that knows which rules it's fields obey and it can check whether the data is valid or not. Also, the model is the border between the rest of the world and the database. So, I would do something like this:
public function updateSalary()
{
$employee = new Employee($_POST['e_Id']);
$employee->setSalary($_POST['salary']));
if ($employee->validate()) {
$employee->save();
} else {
return $employee->getErrors();
}
}
Why I offer you this way:
because you keep the validation at one place. Later, if you want to validate another field, you will call the validate() method again. You won't write another validation for each field or class;
You can create a base class and put the validate() method there - all clients will call the validate() method, and wouldn't care about the specifics of the fields. The validate method will care only of what to validate - which fields and what the rules are. This information will be set in the specific (child) classes, like the Employee class.
If you want to validate only one field (like in your case), in the validate() method you can make a simple check of which fields are changed and do validation only of these fields.
Depends of you, if the validation rules are "global", in other words if they are the same every time you update that DB table/Object propriety, place them in the Model, otherwise validate user input in the Controller if in different situations you need different validation rules for the same Entity.
Firstly, I am not a geek below is just what I think.
It should be done in controller, just because right now you are only validating number, which just simple check and I think you just have to apply regex for this.
What actually I understand is that, model is where you keep your business logic, but if your field value is all together wrong than you will never process business logic and you don't want your model to come in play.
I would suggest to apply validation in the Model where possible. It has the advantage that the Model can be tested directly in a more complete way, and that the Model is guaranteed to only persist valid data.
Of course, the Controller needs to handle validation, and might be the first layer that calls on validation when it concerns complex validation on distributed items. But in the example you give there is no such complexity.
Note that anyway some validation will even be performed by the database engine (such as NOT NULL and primary key requirements).
I would also suggest to use exceptions in the Model, as this guarantees the interruption of the running function, and lets you process all (validation) errors in a similar way within the Controller. I would advise to configure your database access layer to also trigger exceptions. In case of PDO you would do that as follows:
$dbh = new PDO($dsn, $user, $password);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
In the Model you would throw an exception when validation fails:
public function setSalary($salary) {
if (!Validator::money($salary)) {
throw new Exception('Invalid value provided as salary.');
}
$this->salary = $salary;
}
In the Controller you would catch errors and log them: as you did in $errors, but I would keep them in the Model as well for later access by the View. This illustrates how the Model detects the validation error, but the Controller deals with it.
I would also suggest to not create an Employee instance directly, but to let the Model do that for you:
public function updateSalary($emp_id, $salary) {
try {
// Note that any of the following statements could trigger exceptions:
$employee = $this->$model->getEmployee($emp_id);
$employee->setSalary($salary);
$employee->save();
} catch(Exception $e) {
$this->$model->logError('salary', $e->getMessage());
}
}
Call the latter function with the posted arguments, as this gives a better indication what the method is using as input. The top-level PHP code would look like this:
$model = new Model();
$controller = new Controller($model);
$view = new View($controller, $model);
$controller->updateSalary($_POST['e_Id'], $_POST['salary']);
echo $view->output();
The View would access to the logged errors to report them to the client.
I realise that the debate as to where to detect validation errors, where handle them, when to trigger exceptions (and when not), ... etc, will never end. But this works for me.
Question
Can I use the Doctrine entity manager (or some other Symfony function) to check if an entity has been updated?
Background
I am building a CMS with the ability to save "versions" of each page. So I have a Doctrine annotated entity $view (which is basically the "page), and this entity has nested associated entities like $view->version (which contain the majority of the information that can be updated in different revisions). This entity is edited with a standard Symfony form in the CMS. When the form is submitted, it does a $em->persist($view) and the Entity Manager detects if any of the fields have been changed. If there are changes, the changes are persisted. If there are no changes, the entity manager ignores the persist and saves itself a database call to update. Great.
But before the entity is saved, my versioning system checks if it's been more than 30 minutes since the current version was last save, or if the user submitting the form is different than the user who saved the current version, and if so it clones the $viewVersion. So the main record for $view remains the same id, but it works from an updated revision. This works great.
HOWEVER... If it's been a while since the last save, and someone just looks at the record without changing anything, and hits save, I don't want the version system to clone a new version automatically. I want to check and confirm that the entity has actually changed. The Entity Manager does this before persisting an entity. But I can't rely on it because before I call $em->persist($view) I have to clone $view->version. But before I clone $view->version I need to check if any of the fields in the entity or it's nested entities have been updated.
Basic Solution
The solution is to calculate the change set:
$form = $this->createForm(new ViewType(), $view);
if ($request->isMethod( 'POST' )) {
$form->handleRequest($request);
if( $form->isValid() ) {
$changesFound = array();
$uow = $em->getUnitOfWork();
$uow->computeChangeSets();
// The Version (hard coded because it's dynamically associated)
$changeSet = $uow->getEntityChangeSet($view->getVersion());
if(!empty($changeSet)) {
$changesFound = array_merge($changesFound, $changeSet);
}
// Cycle through Each Association
$metadata = $em->getClassMetadata("GutensiteCmsBundle:View\ViewVersion");
$associations = $metadata->getAssociationMappings();
foreach($associations AS $k => $v) {
if(!empty($v['cascade'])
&& in_array('persist', $v['cascade'])
){
$fn = 'get'.ucwords($v['fieldName']);
$changeSet = $uow->getEntityChangeSet($view->getVersion()->{$fn}());
if(!empty($changeSet)) {
$changesFound = array_merge($changesFound, $changeSet);
}
}
}
}
}
The Complication
But I read that you shouldn't use this $uow->computerChangeSets() outside of a the lifecycle events listener. They say you should do a manual diff of the objects, e.g. $version !== $versionOriginal. But that doesn't work because some fields like timePublish always get updated, so they are always different. So is it really not possible to use this to getEntityChangeSets() in the context of a controller (outside of an event listener)?
How should I use an Event Listener? I don't know how to put all the pieces together.
UPDATE 1
I followed the advice and created an onFlush event listener, and presumably that should load automatically. But now the page has a big error which happens when my service definition for gutensite_cms.listener.is_versionable passes in another service of mine arguments: [ "#gutensite_cms.entity_helper" ]:
Fatal error: Uncaught exception 'Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException' with message 'Circular reference detected for service "doctrine.dbal.cms_connection", path: "doctrine.dbal.cms_connection".' in /var/www/core/cms/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php:456 Stack trace: #0 /var/www/core/cms/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php(604): Symfony\Component\DependencyInjection\Dumper\PhpDumper->addServiceInlinedDefinitionsSetup('doctrine.dbal.c...', Object(Symfony\Component\DependencyInjection\Definition)) #1 /var/www/core/cms/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php(630): Symfony\Component\DependencyInjection\Dumper\PhpDumper->addService('doctrine.dbal.c...', Object(Symfony\Component\DependencyInjection\Definition)) #2 /var/www/core/cms/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php(117): Symfony\Componen in /var/www/core/cms/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php on line 456
My Service Definition
# This is the helper class for all entities (included because we reference it in the listener and it breaks it)
gutensite_cms.entity_helper:
class: Gutensite\CmsBundle\Service\EntityHelper
arguments: [ "#doctrine.orm.cms_entity_manager" ]
gutensite_cms.listener.is_versionable:
class: Gutensite\CmsBundle\EventListener\IsVersionableListener
#only pass in the services we need
# ALERT!!! passing this service actually causes a giant symfony fatal error
arguments: [ "#gutensite_cms.entity_helper" ]
tags:
- {name: doctrine.event_listener, event: onFlush }
My Event Listener: Gutensite\CmsBundle\EventListener\isVersionableListener
class IsVersionableListener
{
private $entityHelper;
public function __construct(EntityHelper $entityHelper) {
$this->entityHelper = $entityHelper;
}
public function onFlush(OnFlushEventArgs $eventArgs)
{
// this never executes... and without it, the rest doesn't work either
print('ON FLUSH EXECUTING');
exit;
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
$updatedEntities = $uow->getScheduledEntityUpdates();
foreach($updatedEntities AS $entity) {
// This is generic listener for all entities that have an isVersionable method (e.g. ViewVersion)
// TODO: at the moment, we only want to do the following code for the viewVersion entity
if (method_exists($entity, 'isVersionable') && $entity->isVersionable()) {
// Get the Correct Repo for this entity (this will return a shortcut
// string for the repo, e.g. GutensiteCmsBundle:View\ViewVersion
$entityShortcut = $this->entityHelper->getEntityBundleShortcut($entity);
$repo = $em->getRepository($entityShortcut);
// If the repo for this entity has an onFlush method, use it.
// This allows us to keep the functionality in the entity repo
if(method_exists($repo, 'onFlush')) {
$repo->onFlush($em, $entity);
}
}
}
}
}
ViewVersion Repo with onFlush Event: Gutensite\CmsBundle\Entity\View\ViewVersionRepository
/**
* This is referenced by the onFlush event for this entity.
*
* #param $em
* #param $entity
*/
public function onFlush($em, $entity) {
/**
* Find if there have been any changes to this version (or it's associated entities). If so, clone the version
* which will reset associations and force a new version to be persisted to the database. Detach the original
* version from the view and the entity manager so it is not persisted.
*/
$changesFound = $this->getChanges($em, $entity);
$timeModMin = (time() - $this->newVersionSeconds);
// TODO: remove test
print("\n newVersionSeconds: ".$this->newVersionSeconds);
//exit;
/**
* Create Cloned Version if Necessary
* If it has been more than 30 minutes since last version entity was save, it's probably a new session.
* If it is a new user, it is a new session
* NOTE: If nothing has changed, nothing will persist in doctrine normally and we also won't find changes.
*/
if($changesFound
/**
* Make sure it's been more than default time.
* NOTE: the timeMod field (for View) will NOT get updated with the PreUpdate annotation
* (in /Entity/Base.php) if nothing has changed in the entity (it's not updated).
* So the timeMod on the $view entity may not get updated when you update other entities.
* So here we reference the version's timeMod.
*/
&& $entity->getTimeMod() < $timeModMin
// TODO: check if it is a new user editing
// && $entity->getUserMod() ....
) {
$this->iterateVersion($em, $entity);
}
}
public function getChanges($em, $entity) {
$changesFound = array();
$uow = $em->getUnitOfWork();
$changes = $uow->getEntityChangeSet($entity);
// Remove the timePublish as a valid field to compare changes. Since if they publish an existing version, we
// don't need to iterate a version.
if(!empty($changes) && !empty($changes['timePublish'])) unset($changes['timePublish']);
if(!empty($changes)) $changesFound = array_merge($changesFound, $changes);
// The Content is hard coded because it's dynamically associated (and won't be found by the generic method below)
$changes = $uow->getEntityChangeSet($entity->getContent());
if(!empty($changes)) $changesFound = array_merge($changesFound, $changes);
// Check Additional Dynamically Associated Entities
// right now it's just settings, but if we add more in the future, this will catch any that are
// set to cascade = persist
$metadata = $em->getClassMetadata("GutensiteCmsBundle:View\ViewVersion");
$associations = $metadata->getAssociationMappings();
foreach($associations AS $k => $v) {
if(!empty($v['cascade'])
&& in_array('persist', $v['cascade'])
){
$fn = 'get'.ucwords($v['fieldName']);
$changes = $uow->getEntityChangeSet($entity->{$fn}());
if(!empty($changeSet)) $changesFound = array_merge($changesFound, $changes);
}
}
if(!$changesFound) $changesFound = NULL;
return $changesFound;
}
/**
* NOTE: This function gets called onFlush, before the entity is persisted to the database.
*
* VERSIONING:
* In order to calculate a changeSet, we have to compare the original entity with the form submission.
* This is accomplished with a global onFlush event listener that automatically checks if the entity is versionable,
* and if it is, checks if an onFlush method exists on the entity repository. $this->onFlush compares the unitOfWork
* changeSet and then calls this function to iterate the version.
*
* In order for versioning to work, we must
*
*
*/
public function iterateVersion($em, $entity) {
$persistType = 'version';
// We use a custom __clone() function in viewVersion, viewSettings, and ViewVersionTrait (which is on each content type)
// It ALSO sets the viewVersion of the cloned version, so that when the entity is persisted it can properly set the settings
// Clone the version
// this clones the $view->version, and the associated entities, and resets the associated ids to null
// NOTE: The clone will remove the versionNotes, so if we decide we actually want to keep them
// We should fetch them before the clone and then add them back in manually.
$version = clone $entity();
// TODO: Get the changeset for the original notes and add the versionNotes back
//$version->setVersionNotes($versionModified->getVersionNotes());
/**
* Detach original entities from Entity Manager
*/
// VERSION:
// $view->version is not an associated entity with cascade=detach, it's just an object container that we
// manually add the current "version" to. But it is being managed by the Entity Manager, so
// it needs to be detached
// TODO: this can probably detach ($entity) was originally $view->getVersion()
$em->detach($entity);
// SETTINGS: The settings should cascade detach.
// CONTENT:
// $view->getVersion()->content is also not an associated entity, so we need to manually
// detach the content as well, since we don't want the changes to be saved
$em->detach($entity->getContent());
// Cloning removes the viewID from this cloned version, so we need to add the new cloned version
// to the $view as another version
$entity->getView()->addVersion($version);
// TODO: If this has been published as well, we need to mark the new version as the view version,
// e.g. $view->setVersionId($version->getId())
// This is just for reference, but should be maintained in case we need to utilize it
// But how do we know if this was published? For the time being, we do this in the ContentEditControllerBase->persist().
}
So my understanding is that you basically need to detect if doctrine is going to update an entity in the database so you can record that change or insert a version of the old entity.
The way you should do that is by adding a listener to the onFlush event. You can read more about registering doctrine events here.
For example you will need to add to your config file a new service definition like that:
my.flush.listener:
class: Gutensite\CmsBundle\EventListener\IsVersionableListener
calls:
- [setEntityHelper, ["#gutensite_cms.entity_helper"]]
tags:
- {name: doctrine.event_listener, event: onFlush}
Then you will create the class EventListener like any symfony service. In this class, a function with the same name as the event will be called, ( onFlush in this case )
Inside this function you can go through all updated entities:
namespace Gutensite\CmsBundle\EventListener;
class IsVersionableListener {
private $entityHelper;
public function onFlush(OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
$updatedEntities = $uow->getScheduledEntityUpdates();
foreach ($updatedEntities as $entity) {
if ($entity->isVersionable()) {
$changes = $uow->getEntityChangeSet($entity);
//Do what you want with the changes...
}
}
}
public function setEntityHelper($entityHelper)
{
$this->entityHelper = $entityHelper;
return $this;
}
}
$entity->isVersionable() is just a method I made up which you can add to your entities to easily decide whether this entity is tracked for changes or not.
NOTE: Since you are doing this in the onFlush. That means that all changes that will be saved to the DB have been computed. Doctrine will not persist any new entities. If you create new entities you will need to manually compute the changes and persist them.
First thing: there is a versionable extension for Doctrine (it was recently renamed to Loggable), that does exactly what you are describing, check that out, maybe it solves your use case.
With that said, this sounds like a job for an onFlush event listener. The UnitOfWork is already in a "changes computed" state, where you can just ask for all of the the changes on all of the entities (you can filter them with an instanceof, or something like that).
This still doesn't solve your issue about saving a new, and the old version too. I am not 100% sure this will work, because persisting something in an onFlush listener will involve workarounds (since doing a flush in an onFlush will result in an infinite loop), but there is $em->refresh($entity) that will roll back an entity to its "default" state (as it was constructed from the database).
So you can try something like, check to see if there are changes to the entity, if there are, clone it, persist the new one, refresh the old one, and save them. You will have to do extra legwork for your relations though, because cloning only creates a shallow copy in PHP.
I'd advise to go with the versionable extension, since it has everything figured out, but read up on the onFlush listener too, maybe you can come up with something.
In case someone is still interested in a different way than the accepted answer (it was not working for me and I found it messier than this way in my personal opinion).
I installed the JMS Serializer Bundle and on each entity and on each property that I consider a change I added a #Group({"changed_entity_group"}). This way, I can then make a serialization between the old entity, and the updated entity and after that it's just a matter of saying $oldJson == $updatedJson. If the properties that you are interested in or that you would like to consider changes the JSON won't be the same and if you even want to register WHAT specifically changed then you can turn it into an array and search for the differences.
I used this method since I was interested mainly in a few properties of a bunch of entities and not in the entity entirely. An example where this would be useful is if you have a #PrePersist #PreUpdate and you have a last_update date, that will always be updated therefore you will always get that the entity was updated using unit of work and stuff like that.
Hope this method is helpful to anyone.
I'm using Doctrine to save user data and I want to have a last modification field.
Here is the pseudo-code for how I would like to save the form once the user presses Save:
start transaction
do a lot of things, possibly querying the database, possibly not
if anything will be changed by this transaction
modify a last updated field
commit transaction
The problematic part is if anything will be changed by this transaction. Can Doctrine give me such information?
How can I tell if entities have changed in the current transaction?
edit
Just to clear things up, I'm trying to modify a field called lastUpdated in an entity called User if any entity (including but not limited to User) will be changed once the currect transaction is commited. In other words, if I start a transaction and modify the field called nbCars of an entity called Garage, I wish to update the lastUpdated field of the User entity even though that entity hasn't been modified.
This is a necessary reply that aims at correcting what #ColinMorelli posted (since flushing within an lifecycle event listener is disallowed - yes, there's one location in the docs that says otherwise, but we'll get rid of that, so please don't do it!).
You can simply listen to onFlush with a listener like following:
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Events;
class UpdateUserEventSubscriber implements EventSubscriber
{
protected $user;
public function __construct(User $user)
{
// assuming the user is managed here
$this->user = $user;
}
public function onFlush(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
// before you ask, `(bool) array()` with empty array is `false`
if (
$uow->getScheduledEntityInsertions()
|| $uow->getScheduledEntityUpdates()
|| $uow->getScheduledEntityDeletions()
|| $uow->getScheduledCollectionUpdates()
|| $uow->getScheduledCollectionDeletions()
) {
// update the user here
$this->user->setLastModifiedDate(new DateTime());
$uow->recomputeSingleEntityChangeSet(
$em->getClassMetadata(get_class($this->user)),
$this->user
);
}
}
public function getSubscribedEvents()
{
return array(Events::onFlush);
}
}
This will apply the change to the configured User object only if the UnitOfWork contains changes to be committed to the DB (an unit of work is actually what you could probably define as an application level state transaction).
You can register this subscriber with the ORM at any time by calling
$user = $entityManager->find('User', 123);
$eventManager = $entityManager->getEventManager();
$subscriber = new UpdateUserEventSubscriber($user);
$eventManager->addEventSubscriber($subscriber);
Sorry for giving you the wrong answer at first, this should guide you in the right direction (note that it's not perfect).
You'll need to implement two events. One which listens to the OnFlush event, and acts like this:
// This should listen to OnFlush events
public function updateLastModifiedTime(OnFlushEventArgs $event) {
$entity = $event->getEntity();
$entityManager = $event->getEntityManager();
$unitOfWork = $entityManager->getUnitOfWork();
if (count($unitOfWork->getScheduledEntityInsertions()) > 0 || count($unitOfWork->getScheduledEntityUpdates()) > 0) {
// update the user here
$this->user->setLastModifiedDate(new \DateTime());
}
}
We need to wait for the OnFlush event, because this is the only opportunity for us to get access to all of the work that is going to be done. Note, I didn't include it above, but there is also $unitOfWork->getScheduledEntityDeletions() as well, if you want to track that.
Next, you need another final event listener which listens to the PostFlush event, and looks like this:
// This should listen to PostFlush events
public function writeLastUserUpdate(PostFlushEventArgs $event) {
$entityManager = $event->getEntityManager();
$entityManager->persist($this->user);
$entityManager->flush($this->user);
}
Once the transaction has been started, it's too late, unfortunately, to get doctrine to save another entity. Because of that, we can make the update to the field of the User object in the OnFlush handler, but we can't actually save it there. (You can probably find a way to do this, but it's not supported by Doctrine and would have to use some protected APIs of the UnitOfWork).
Once the transaction completes, however, you can immediately execute another quick transaction to update the datetime on the user. Yes, this does have the unfortunate side-effect of not executing in a single transaction.
#PreUpdate event won't be invoked if there's no change on the entity.
My guess would have been, similarly to the other 2 answers, is to say whatever you want to do when there will or will not be changes, use event listeners.
But if you only want to know before the transaction starts, you can use Doctrine_Record::getModified() (link).
This is a fairly basic question about CakePHP, but since my knowledge of this framework is rather rusty, it is making me lose a lot of time.
I have a ManyToMany relation between Guest and Present. Whenever a new Guest is created and associated with a present, I would like to mark the Present as taken. If the present is already taken, some error should arise. The reason why I am not just declaring that a Guest hasMany Presents is because in the future things may change and more than one guest could associate to a present, so I prefer to avoid a Db migration.
My Guest::add() action looks like follows. It is called with a POST with the data of a new Guest and the id of an existing Present.
public function add() {
if ($this->request->is('post')) {
$id = $this->request->data['Present']['id'];
$this->Guest->create();
$present = $this->Guest->Present->findById($id);
if ($present['Present']['taken']) {
throw new ForbiddenException();
}
if ($this->Guest->save($this->request->data)) {
if ($this->Guest->Present->saveField('taken', true)) {
// Give the guest a uuid and proceed with a welcome message
$this->Guest->read();
$this->set('uuid', $this->Guest->data['Guest']['uuid']);
}
}
}
else {
throw new ForbiddenException();
}
}
What happens is that a new Guest is created (correct) and associated with the given present (correct) but when I save the taken field a new present is created instead of modifying the given one.
What is the correct way to proceed to update the current Present?
If it is of any help, I am using CakePHP 2.0
For obtaining the model data by the primary key it's better to use theIn addition read method:
$present = $this->Guest->Present->read(null, $id);
The read method sets the model's id attribute so that further calls to other methods affect the same data record, rather than creating a new one. This should solve the problem you are having.
Model callbacks tend to be better suited for these situations. You could add a beforeSave callback to the Guest class to checks if the present is already taken, and not allow the creation if it is. This way the model logic is left in the model layer and you don't need to do any extra work e.g. if the constraint has to be enforced also when existing Guests are saved, or created from different controllers or actions.
It sounds like the ID of the model you are trying to save is losing scope. You should be able to resolve your issue by updating your code:
...
if ($this->Guest->save($this->request->data)) {
$this->Guest->Present->id = $id;
if ($this->Guest->Present->saveField('taken', true)) {
...