Cascading deletion doesn't work with cascadeCallbacks if you first trash related elements.
If you have cascadeCallbacks enabled.
The 'purge'=>true only works if the related (hasMany) elements are not trashed.
Steps to reproduce:
- Have 2 tables, both with Trash.
- First table hasMany secondTable with
Dependent trueandsetCascadeCallbacks true - delete() entity from firstTable
- delete(['purge' => true]) the same entity
Expected result:
Have both entity from firstTable and related entities from secondTable hard deleted (purged)
Actual result:
Related entities from secondTable are left as soft deleted (or you get constraint violation if set).
It's because the DependentDeleteHelper.php doesn't see those related elements because by default the Trash Behavior hides deleted items.
foreach ($association->find()->where($conditions)->all()->toList() as $related) {
https://github.com/cakephp/cakephp/blob/323997781a608191192066b883201a12c3c5b2f2/src/ORM/Association/DependentDeleteHelper.php#L56
The only solution I see for this problem is to loop through all dependent associations and disable the trash behavior (or set some flag so that TrashBehavior::beforeFind() doesn't add the condition, and then revert back the setting after the deletion is done.
You are welcome to submit a patch for that (or any other solution).
Thanks for checking it. I did also check multiple places.
I could also loop over associations and use setFinder that would set options to include trash and revert it back after delete.
But I think manually handling deletion of trashed items in Model.beforeDelete would be a cleaner solution (unless it result in some crazy event loop, but I don't think so). But best would be to somehow override cascadeDelete in DependentDeleteHelper or HasMany/HasOne
Current dirty work-around I have found is to have (separate second) associations that is only used for delete with setFinder('WithTrashed')
I could also loop over associations and use setFinder that would set options to include trash and revert it back after delete
Yeah that would be inline with the approach I suggested above.
But best would be to somehow override cascadeDelete in DependentDeleteHelper or HasMany/HasOne
You could extend HasMany/HasOne::cacacadeDelete() and override Table::hasMany/hasOne() to use your custom association classes, but that's an app level solution.
@ADmad I don't think it can be fixed on the plugin level without changes in CakePHP, but correct me if I'm wrong.
- loop over associations and use setFinder
- loop through all dependent associations and disable the trash behavior (or set some flag so that TrashBehavior::beforeFind() doesn't add the condition
Both cases are not possible, because if delete fail for any reason, you can't undone those changes, and those changes affect all next ORM queries on that table.
So If your app have some handling for failed deletions (even printing some page with error message) - then you are f... as you could accidentally also fetch trashed entities.
You can't use Model.beforeDelete to modify Associations because Model.afterDelete might not fire and there is no other way to catch delete failures.
So the only place you can modify assosiations by overriding the delete() function with a try/catch/return value check, but that can only be done on app level, not possible from Behavior.