Replies: 2 comments 4 replies
-
I get your pain. If I would had been forced into soft deletes, I would had created special relations that fetch only deleted or only not deleted rows, without scopes because scopes are applied or not on some cases. I encountered similar issue regarding parent_id and I had to condition always the parent_id column. I never use scopes by the way. |
Beta Was this translation helpful? Give feedback.
-
One could probably implement a "wrapper object" ( |
Beta Was this translation helpful? Give feedback.
-
PROBLEM STATEMENT:
SoftDeletes is just just a "scope" for a model and that works very well when developers are querying for that model directly.
However, if you start using SoftDeletes in your app, you're inevitably going to get to the point where every single model has at least one relationship with a soft deleted record.
There are several challenges with this:
Difficult to Debug
All of these lines of code behave differently:
Unfortunately, because SoftDeletes is just filtering/scoping on the deleted_at column, there is no way to differentiate between a relationship that's been scoped or SoftDeleted, and those two use cases are, semantically, VERY different.
It's easy for a developer to think "ah, that column is not null so...." they don't handle it. When it goes wrong (not if) you get a stacktrace in the logs saying
".ERROR: Attempt to read property "id" on null"
, which isn't informative or helpful and it causes a failure in production.No easy way to check for deleted relationships
Even if your developers do think to check for scoped/soft deleted data, chances are, your model has already been through with/load/loadMissing so you now have to go back to the DB to get the data like this:
Of course, you can always add "withTrashed()" to the relationship but that's a bit like smashing a walnut with a nuke and it will, eventually, lead to a bug that shows soft-deleted data to the client because now you have to iterate through each result (in the case of a HasMany, BelongsToMany, etc) to check for trashed();
Or you can handle it on a case-by-case basis when eager loading, per:
This works but, again, you have to account for every instance and you're team is going to miss some of these cases and you're going to get failures.
SOLUTION(s)
I'm actually struggling with how to solve this and the following is more of an "is there a better way" so I'd love to hear other people's thoughts on it.
The problem I see is that soft-deleted records are currently filtered out of the result set. For Laravel to be able to intelligently handle soft-deleted relationships, it would need to query the soft-deleted data (on some level, even if it's only to return a single column saying "deleted") during the query and that might also incur performance issues in some circumstances.
One thought I had was to provide a mechanism for this; something akin to:
Then, if the code accesses a soft-deleted relationship (
$user->tenant->name
) it would immediately throw an "AccessScopedModelException(Model $parent, Relationship $relationship, EloquentScope $scopeClass)" (or maybe it just throws an AccessSoftDeletedModelException)Notes:
This does two things:
It would also allow us to do easy checks like
$user->tenant->isTrashed()
(or->isOutOfScope(null|string|EloquentScope $scopeClass = null)
) to determine if the record is intentionally scoped out.I LOVE exceptions (and I know many don't so I'm sure some will hate the above, which is why I suggest making it optional) but I'm open to alternatives.
But my ultimate goals are to make SoftDeleted accesses explicit and easy to handle by developers when it inevitably goes wrong.
Beta Was this translation helpful? Give feedback.
All reactions