I have doctrine's softdelete behavior attached to all of my models. Is there a way I can hard delete a particular record?
In cakephp I remember detaching the behavior... deleting the record and then re attaching the behavior.
Is there something similar in symfony/doctrine ? If so then how do I detach a behavior?
Cheers
umm .. the SoftDelete behavior includes a much nicer way of doing this ... just call
$record->hardDelete();
Think I'd go for Zed's way, but for completeness:
The Event listener method for delete (and select) for the soft delete behaviour contains:
if ( ! $query->contains($field)) {
// do the magic stuff to covert the query to respect softdelete
}
This means that if you explicitly mention the field in the query, it won't apply the transformation to the query.
So, if you do:
$q = Doctrine_Query::create()
->delete('Table t')
->where('t.id = ? AND t.deleted != 2 ', 1);
it won't apply the soft delete stuff and will actually delete the record. Note that you can do anything with t.deleted, I've just done something that will always be true. The alias ('t.') is important too for it to work.
This trick works for selects too, which is where I've normally used it before.
As I say though, I think its nicer to do:
$old_dqlc = Doctrine_Manager::getInstance()->getAttribute(Doctrine::ATTR_USE_DQL_CALLBACKS);
Doctrine_Manager::getInstance()->setAttribute(Doctrine::ATTR_USE_DQL_CALLBACKS, false);
$record->delete();
Doctrine_Manager::getInstance()->setAttribute(Doctrine::ATTR_USE_DQL_CALLBACKS, $old_dqlc);
In particular, you can still use the delete() method rather than having to manually create the query. The one plus for the query method is that if you have other behaviours attached to the record, they will still be respected.
$object->getListener()->setOption('disabled',true);
This will disable all record listeners for this object.
Try calling this, it should disable the behavior handling.
$manager->setAttribute(Doctrine::ATTR_USE_DQL_CALLBACKS, false);
As a dirty way you can generate an SQL query that deletes the entry from the table.
link text i would think that this function and setting the use dql callbacks to false just like on the manager should do the trick :).
Wanted to agree with Joshua Coady that the best way would be to use
$record->hardDelete()
However, I also wanted to add here since it's one of the first results on google for detaching the behavior in doctrine that the easiest way to detach the behavior for "selects" is simply to include "deleted_at" (or whatever you have named your field as in the query. The listener looks to see if it is included and if so does not filter deleted records out.
Doctrine_Core::getTable('Record')->createQuery()->select('id, etc1, etc2')->addSelect('deleted_at')->execute();
will return deleted records.
Related
I have a strange problem with \Doctrine\ORM\UnitOfWork::getScheduledEntityDeletions used inside onFlush event
foreach ($unitOfWork->getScheduledEntityDeletions() as $entity) {
if ($entity instanceof PollVote) {
$arr = $entity->getAnswer()->getVotes()->toArray();
dump($arr);
dump($entity);
dump(in_array($entity, $arr, true));
dump(in_array($entity, $arr));
}
}
And here is the result:
So we see that the object is pointing to a different instance than the original, therefore in_array no longer yields expected results when used with stick comparison (AKA ===). Furthermore, the \DateTime object is pointing to a different instance.
The only possible explanation I found is the following (source):
Whenever you fetch an object from the database Doctrine will keep a copy of all the properties and associations inside the UnitOfWork. Because variables in the PHP language are subject to “copy-on-write” the memory usage of a PHP request that only reads objects from the database is the same as if Doctrine did not keep this variable copy. Only if you start changing variables PHP will create new variables internally that consume new memory.
However, I did not change anything (even the created field is kept as it is). The only operations that were preformed on entity are:
\Doctrine\ORM\EntityRepository::findBy (fetching from DB)
\Doctrine\Common\Persistence\ObjectManager::remove (scheduling for removal)
$em->flush(); (triggering synchronization with DB)
Which leads me to think (I might be wrong) that the Doctrine's change tracking method has nothing to do with the issue that I'm experiencing. Which leads me to following questions:
What causes this?
How to reliably check if an entity scheduled for deletion is inside a collection (\Doctrine\Common\Collections\Collection::contains uses in_array with strict comparison) or which items in a collection are scheduled for deletion?
The problem is that when you tell doctrine to remove entity, it is removed from identity map (here):
<?php
public function scheduleForDelete($entity)
{
$oid = spl_object_hash($entity);
// ....
$this->removeFromIdentityMap($entity);
// ...
if ( ! isset($this->entityDeletions[$oid])) {
$this->entityDeletions[$oid] = $entity;
$this->entityStates[$oid] = self::STATE_REMOVED;
}
}
And when you do $entity->getAnswer()->getVotes(), it does the following:
Load all votes from database
For every vote, checks if it is in identity map, use old one
If it is not in identity map, create new object
Try to call $entity->getAnswer()->getVotes() before you delete entity. If the problem disappears, then I am right. Of cause, I would not suggest this hack as a solution, just to make sure we understand what is going on under the hood.
UPD instead of $entity->getAnswer()->getVotes() you should probably do foreach for all votes, because of lazy loading. If you just call $entity->getAnswer()->getVotes(), Doctrine probably wouldn't do anytning, and will load them only when you start to iterate through them.
From the doc:
If you call the EntityManager and ask for an entity with a specific ID twice, it will return the same instance
So calling twice findOneBy(['id' => 12]) should result in two exact same instances.
So it all depends on how both instances are retrieved by Doctrine.
In my opinion, the one you get in $arr is from a One-to-Many association on $votes in the Answer entity, which results in a separate query (maybe a id IN (12)) by the ORM.
Something you could try is to declare this association as EAGER (fetch="EAGER"), it may force the ORM to make a specific query and keep it in cache so that the second time you want to get it, the same instance is returned ?
Could you have a look at the logs and post them here ? It may indicates something interesting or at least relevant to investigate further.
When I throw records into the "trashbin" I use the normal delete method with softDelete enabled.
When I force delete a record I want to delete the belonging images as well. So I want to use Laravel's events. On forceDeleting I want some code to be executed.
What event can I call for that? When calling forceDeleting I get:
Call to undefined method Illuminate\Database\Query\Builder::forceDeleting()
What event should I use for this?
EDIT
For now I'm using:
Document::deleting(function ($document) {
if(!$document->deleted_at) {
// normal delete
}else{
// force delete
}
});
See also: https://codeneverlied.com/force-deleting-event-in-laravel/
But I still like to know if there is a event for this.
You can create your own event and fire it yourself. Would be easiest to extend the existing Delete Event and maybe call it Force Deleting.
The next step would be to extend the softDeleting trait to fire this event for you automatically.
According to the Eloquent documentation, these are the events:
retrieved, creating, created, updating, updated, saving, saved, deleting, deleted, restoring, and restored.
So to answer your question of if it exists, that answer would be no. Not according to the documentation of Laravel 5.8
https://laravel.com/docs/5.8/eloquent
Hope I could help.
ImJT
Better approach is this:
// GOOD approach
static::deleted(function ($document) {
if (Document::withTrashed()->where('id', $model->id)->exists()) {
// document is soft deleted
} else {
// document is force deleted
}
});
I have a problem, when I try to run this function in my model it does nothing. The print statement prints out.
DELETE FROM child_participantsWHERE Child_Name='test'
andParent_username='tester2'
Which when I run from command line works correctly(the record exists and is deleted). But when I try it from my web application it gives me no error but does not actually delete anything. I know i am passing data correctly because I receive it in my controller and model. What gives?
function remove_child($username, $participant_name)
{
$where = "`Child_Name`='$participant_name' and`Parent_username`='$username'";
$this->db->where($where, null, false);
$this->db->delete($this->child_table);
echo $this->db->last_query();
}
From the documentation:
If you use multiple function calls they will be chained together with AND between them:
Try changing:
$where = "`Child_Name`='$participant_name' and`Parent_username`='$username'";
to
$this->db->where('Child_Name', $participant_name);
$this->db->where('Parent_username', $username);
// translates to WHERE Child_Name='XXX' and Parent_username='XXX'
Hope this helps!
Do you get the same results when you break it out into two where method calls? I would do this over how you are using the where method.
$this->db->where('Child_Name',$participant_name);
$this->db->where('Parent_username',$username);
$this->db->delete($this->child_table);
also, turn on the profiler to see all the queries that are being run to make sure there are not other parts of code we cannot see that might be interfering or a transaction not being committed
$this->output->enable_profiler(TRUE);
Another suggestion is the practice of soft deletes so that way your data is not truly gone and also minimizes how much you need to rely on reconstructing your log file. Also to make simple CRUD operations faster you can use a very simple extension of the base model. One that I have used by recommendation is https://github.com/jamierumbelow/codeigniter-base-model
Check that does your user has delete privilege in the database. if it has than change your
code like this:
function remove_child($username, $participant_name)
{
$this->db->trans_start();
$this->db->where('Child_Name',$participant_name);
$this->db->where('Parent_username',$username);
$this->db->delete($this->child_table);
$this->db->trans_complete();
return TRUE;
}
i hope that this will solve your problem.
How can I schedule an entity for update, manually, when no property is actually changed?
I tried $entityManager->getUnitOfWork()->scheduleForUpdate($entity) but it gave an error in the core, and I have no intetion of debuging Doctrine.
The entity is managed if it matters: $entity = $repository->findOne(1)
I need this so doctrine would call my EventSubscriber on flush().
I've also tried something like $entityManager->getEventManager()->dispatchEvent(\Doctrine\ORM\Events::preUpdate), but then my listener's preUpdate() receives EventArgs instead of PreUpdateEventArgs.
Any help is appreciated!
Method mentioned by Wpigott not working for me (at least in doctrine/orm v2.4.2), instead I'm using this:
$entityManager->getUnitOfWork()->setOriginalEntityProperty(spl_object_hash($entity), 'field_name', '__fake_value__');
Where field_name existent property name.
The solution is a bit hacky, but I was able to achieve this by doing something like the following.
$objectManager->getUnitOfWork()->setOriginalDocumentData($object, array('__fake_field'=>'1'));
This essentially causes Doctrine to think the document has changed from the original, and it computes it as a change which will cause the events to be executed on flush.
The example is for the MongoODM solution, but the same technique should work for ORM like below.
$objectManager->getUnitOfWork()->setOriginalEntityData($object, array('__fake_field'=>'1'));
Even though this question is quite a bit old, I just found a way much more elegant to solve this problem I want to share here using Doctrine ORM 2.6.2:
You can simply tell the ClassMetadataInfo object of your table to just state those fields as dirty that you pass to the propertyChanged function of the UnitOfWork. The important parts here are the setChangeTrackingPolicy and propertyChanged calls:
$unitOfWork = $entityManager->getUnitOfWork();
$classMeta = $entityManager->getClassMetadata('Your\Table\Class');
$ctp = $classMeta->changeTrackingPolicy; # not a function but a variable
# Tell the table class to not automatically calculate changed values but just
# mark those fields as dirty that get passed to propertyChanged function
$classMeta->setChangeTrackingPolicy(ORM\ClassMetadataInfo::CHANGETRACKING_NOTIFY);
# tell the unit of work that an value has changed no matter if the value
# is actually different from the value already persistent
$oldValue = $entity->getValue('fieldName'); # some custom implementation or something
$newValue = $oldValue;
$unitOfWork->propertyChanged($entity, 'fieldName', $oldValue, $newValue);
# field value will be updated regardless of whether its PHP value has actually changed
$entityManager->flush();
# set the change tracking policy back to the previous value to not mess things up
$classMeta->setChangeTrackingPolicy($ctp);
You may also want to have a look at the Doctrine\Common\NotifyPropertyChanged interface.
I hope this will be useful for someone.
What's the goal? If there is no property changed, why would you plan an update?
I'm not having any luck using merge(). I'm doing almost exactly what is documented:
/* #var $detachedDocument MyDocumentClass */
$detachedDocument = unserialize($serializedDocument);
$document = $dm->merge($detachedDocument);
$document->setLastUpdated(new \MongoDate());
$dm->persist($document);
but the change never sticks. I have to do this instead:
$dm->createQueryBuilder('MyDocumentClass')
->findAndUpdate()
->field('lastUpdated')->set(new \MongoDate())
->getQuery()
->execute();
merge() seems pretty straightforward, so I'm confused why it doesn't work like I think it should.
In your first code example, merge() followed by persist() is redundant, and you omitted a flush(), which is the only operation that would actually write to the database (unless you execute a query manually, as you did in the second example). If you walk through the code in UnitOfWork::doMerge(), you'll see that it's going to either persist the object (if it has no ID) or fetch the document by its ID. The end result is that merge() returns a managed document. Persist ensures that the document will be managed after it is called (it returns nothing itself). If you poke in UnitOfWork::doPersist(), you'll see that passing a managed object to the method is effectively a NOOP.
Try replacing persist() with flush(). Note that you can flush a single document if necessary, but $dm->flush() processes all managed objects by default.
If that still doesn't help, I'd confirm that the lastUpdated field is properly mapped in ODM. You can inspect the output of $dm->getClassMetadata('MyDocumentClass') to confirm. If it isn't a mapped field, UnitOfWork will detect no changes in the document and there will be nothing to flush.
As an aside: in the second code example, you're executing findAndUpdate() without any search criteria (only the set() is specified). Typically, you'd pair the modification with something like equals() (probably the ID in your case) to ensure that a single document is atomically modified and returned.