Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Using our own API is more elegant, but it means that any changes to flaggings that are made via the Entity API will bypass count changes.
I am coming to the conclusion that we can't just say 'always use the flag API', because in D8 even more than D7, everything will want to work with entities as generic entities.
Comment | File | Size | Author |
---|---|---|---|
#49 | moveEventsOutOfService_2700727.49.patch | 17.88 KB | socketwench |
| |||
#49 | interdiff-2700727-44-49.txt | 2.09 KB | socketwench |
#44 | moveEventsOutOfService_2700727.44-interdiff.txt | 6.61 KB | Berdir |
#44 | moveEventsOutOfService_2700727.44.patch | 17.77 KB | Berdir |
| |||
#43 | moveEventsOutOfService_2700727.43.patch | 16.84 KB | socketwench |
|
Comments
Comment #2
socketwench CreditAttribution: socketwench at FFW commentedIt's starting to look that way. Fortunately, we can broadcast flagging events from anywhere, so it shouldn't be hard to move that logic from FlagService to the entity classes instead.
Comment #3
socketwench CreditAttribution: socketwench at FFW commentedWhy not Entity::postSave() for flagging, and Entity::preDelete() for unflagging? Why go to the hook?
Comment #4
socketwench CreditAttribution: socketwench at FFW commentedInstead of postSave(), ContentEntityBase::postCreate() would be better.
Comment #5
joachim CreditAttribution: joachim commentedI would be very careful about postCreate(). 'create' in Drupal entity lingo usually means 'create the object in memory but don't save to the database'. So either we don't want that, or that method is really badly named.
Comment #6
socketwench CreditAttribution: socketwench at FFW commentedThat would explain why it blows up impressively and doesn't behave as expected. >_<
There's a bigger problem with preDelete() in that the flagging entity no longer seems complete. There's a null flag and flagging.
Comment #7
joachim CreditAttribution: joachim commentedThat's odd. On D7 at least, hook_delete() got you a full entity.
D8 docs say:
Comment #8
martin107 CreditAttribution: martin107 commentedSo from https://www.drupal.org/node/2705789#comment-11084445
A blocker of a critical, becomes critical...
Comment #9
socketwench CreditAttribution: socketwench at FFW commentedComment #11
joachim CreditAttribution: joachim commentedShould be postSave() instead of postCreate().
Comment #12
socketwench CreditAttribution: socketwench at FFW commentedI don't think it makes a difference to the failures, but *shrug*.
Comment #14
socketwench CreditAttribution: socketwench at FFW commentedNow that's interesting. If we change our delete hooks to predelete hooks, now we're only getting odd counts instead of crashes.
Comment #16
joachim CreditAttribution: joachim commentedpostCreate() is called at the wrong time. Someone just doing Flagging::create() to prepare a form or something would cause the count to increase, and if the user abandons the form without submitting it, then we'd have a count of a flagging that doesn't exist.
Comment #17
socketwench CreditAttribution: socketwench at FFW commentedThis one only fails when resetting the flag, deleting a node, or deleting a user.
Comment #19
martin107 CreditAttribution: martin107 commentedSo does that mean the if statement in FlagService unFlagAll() can safely be removed, as flagging->delete() will always fire the event?
Comment #20
socketwench CreditAttribution: socketwench at FFW commentedBLOODY TYPO!
Comment #22
BerdirYou can move the first line out of the loop.
Not something for this issue, but isn't it a bit strange at this point that FlagginEvent doesn't just have the Flagging and you can get what you need from that? We could still over getFlag() method on it directly, but internally, it would just have a single property..
Comment #23
socketwench CreditAttribution: socketwench at FFW commentedI tried that once, but I thought I hit a block there. Maybe I'm misremembering.
Comment #24
socketwench CreditAttribution: socketwench at FFW commentedRefactored FlaggingEvent to take...you know, a flagging.
Comment #26
BerdirThose @vars are not necessary. You already type hint on FlaggingEvent, so you know what getFlagging() returns and in turn, that knows what getFlag() and getFlaggable() returns.
If that's not the case then we need to fix the documentation of those methods.
Comment #27
socketwench CreditAttribution: socketwench at FFW commentedBerdir mentioned on IRC that now we have a double-delete problem when we issue a flag reset. The patch swallows the error, but we really do need to fix this.
Is there a way we can leverage the FlaggingStorage class to solve this problem?
Comment #28
socketwench CreditAttribution: socketwench at FFW commentedRemoved unnecessary @vars.
Comment #29
socketwench CreditAttribution: socketwench at FFW commentedIncluded changes from https://www.drupal.org/node/2704959#comment-11092407
Comment #30
martin107 CreditAttribution: martin107 commentedJust a minor tweak while I see it
I am moving a \Drupal::service() call out of a loop.
it will only be significant if someone deletes a bucket load of flags.
Comment #31
socketwench CreditAttribution: socketwench at FFW commentedDammit. Missed that one. >_<
Comment #32
BerdirThis somewhat contradicts #2704959: FlagCountManager::decrementCounts() doesn't handle empty results gracefully now. Because with this, that scenario will definitely happen every time a flag is reset.
As mentioned in IRC, there are kind of two ways forward:
a) Ignore that problem for now, do *not* commit the other issue, just avoid errors. We should however at least avoid doing a bogus db update query on something that won't ever do anything, meaning, we need a third case for count 0 that does nothing.
b) Fix it here by removing the flag reset event. It will make it a bit slower as each removed flagging will decrement the counts on its own.
Either way, I think the flag reset API needs to be removed entirely. Just provide a batch that deletes all flaggings, just like the prepare uninstall stuff. It's impossible to make an API that scales.
Also, the flag API currently accepts an entity and will only reset flags for that entity. The event however doesn't contain that information and the flag counts will all be removed.
Comment #33
socketwench CreditAttribution: socketwench at FFW commentedIt also occurred to me to change the way preDelete() operates. It can receive multiple flagging deletes. Instead of reset, we create a new FlagBatchDelete event that can accept multiple deletes in place of reset. Then if only one flagging is passed to preDelete(), we issue a regular unflagged event.
Comment #34
BerdirYes, I was thinking around that as well.
But do you really want to do two different events? Then anyone who cares about that needs to implement both anyway. I'd just change unflag to always support multiple flaggings. flag can probably stay single, as there's no API to save multiple entities at once anyway.
The decrements method can get a bit complicated then, unless you just want to loop over all flags, but then we don't actually benefit from having multiple. I think you can do something like that:
1. Loop over all passed flaggings, build a list of flags, entity types and entity ids., also a count of flaggings per flag/entity_type/id.
2. Do a query for an IN for each of those, group by flag, entity type and id (The thing is, it's always only one entity type per flag, so we could just leave that out, not sure?).
3. Loop over the results (this way, you automatically skip any that don't exist), if you have the same count of flaggings being deleted ( or less?) delete, if there are more, update and decrement by the count of flags that are being deleted.
Then you just have to update the reset method to call $storage->delete($flaggings) and remove the reset event. Still needs to be converted to a batch but then we can do that without an API change.
Comment #35
socketwench CreditAttribution: socketwench at FFW commentedThat seems sensible.
Comment #36
socketwench CreditAttribution: socketwench at FFW commentedComment #38
martin107 CreditAttribution: martin107 commentedIt looks like there is a branch merging bug with
#2707127: Flag::toArray() is no longer needed.
so Flag config_export is corrupted - and Flag::toArray() reappears.
Comment #39
socketwench CreditAttribution: socketwench at FFW commentedCan't quite figure out why I can't get the entity type manager to inject into FlaggingService without it blowing up.
Comment #40
socketwench CreditAttribution: socketwench at FFW commentedFixed typo, deleted reset event completely.
Comment #41
socketwench CreditAttribution: socketwench at FFW commentedWhitespace fix.
Comment #42
Berdirthis looks like a bad merge conflict, this shouldn't be reverted?
this is part of the the same conflict.
I see that you're not yet implementing my idea above. I guess it can be a follow-up, but it's fairly important to speed up resets of a lot of flaggings.
exceptions shouldn't use t()
this method can go.
this can also change to a $storage->delete($flaggings)
I'm not really sure I see the point in the flaggingService, especially given that getFlaggings() is actually on flagService and this is now just an entity query + delete. Which will probably go away completely when switching to a batch process for reset.
Just like the config_export stuff, this all seems like an accidental and unnecessary revert of the previous commit? there's no reason that this has to be changed back.
Comment #43
socketwench CreditAttribution: socketwench at FFW commentedStill missing the speed enhancements from #34.
Comment #44
BerdirImplemented the suggested decrement method. Went a bit overboard, but the good news is that deleting N flaggings of one flag will result in just two queries now. One select and one delete.
Also some minor cleanup.
Comment #45
socketwench CreditAttribution: socketwench at FFW commentedThat's...a lot of calls to id(). Can we get the ids up front (instead of the entities), and then pass the entities inline to resetLoadedCounts() by calling getFlag() and getFlaggable()?
That's my own nit to pick.
Comment #46
joachim CreditAttribution: joachim commentedThat doesn't sound right.
Or this.
So what's happening to reset? It's not functionality I'm hugely keen on maintaining, but I'm aware a lot of people use it with Rules and stuff.
Comment #47
BerdirI don't care about $flag_id vs. $flag->id(), personally, I find that there is little difference in readability between the two.
@joachim:
Use reset with rules how exactly? Do something when a flag is reset or trigger a flag reset? The second is not a problem, the API still exists, it's simply a "delete all flaggings" now (like it already was before). That is a scalability issue in case you have a lot of flaggings, but as I wrote above, the only way to make it scale with content entities that might have fields is to make it use batch. It's also not more of a scalability issue with this patch than without.
Reacting to a flag being deleted would be harder, you could currently check if the flaggings list includes all list of a flag. We could also keep the event and not use it for now. But the way it is currently implemented is broken in some cases, for example when only a single entity is reset. the event right now doesn't have that data. So instead of keeping it, we could do a follow-up to consider re-adding it?
Comment #48
socketwench CreditAttribution: socketwench at FFW commentedIt's a personal preference. *shrugs*
We could keep reset as a courtesy event. It can be fired from the reset() API method, but we do not use internally to decrement counts. UnflaggingEvents would still be fired. I'd rather remove the event later when we don't need it; Rules support is still developing.
Comment #49
socketwench CreditAttribution: socketwench at FFW commentedIncludes changes from 45 and 46. Does not re-add reset event.
Comment #50
joachim CreditAttribution: joachim commented> Use reset with rules how exactly? Do something when a flag is reset or trigger a flag reset?
My impression from what I've seen in the issue queue is that people like to use Rules to cause a flag to be reset. Some crazy things I've seen include using flags to 'place' nodes into a cart, and then resetting that when the order is complete (yeah, sounds crazy to me too). I think there were sane-sounding use cases too, but that's the one that stuck in my mind!
Comment #51
BerdirOk, triggering a reset should still be possible, especially if it's per node, where there are likely no scalability problems with huge amount of flags.
The reason I'm hesitant to just keep/readd the reset event is that it is broken for per-entity resets. As I wrote, in flag HEAD, if you trigger reset for a single node (I guess there is no way to do that except through the API), the reset event is triggered and FlagCountsManager deletes *all* counts for that flag.
Maybe that argument should simply be removed and you should use unflagAll() for that use case?
Comment #52
socketwench CreditAttribution: socketwench at FFW commentedUnflagAllEvent? Split off into a separate issue?
Comment #53
socketwench CreditAttribution: socketwench at FFW commentedI have to agree. This issue creates an UnflaggingEvent, which I think is a good improvement, but not as useful for Rules stuff. Here's one possible solution: #2713819: Remove reset(), augment unFlagAll().
Is there anything more to be done on this issue? It's holding up two other criticals.
Comment #54
socketwench CreditAttribution: socketwench at FFW commentedBerdir says this is ready on IRC. ^_^
Comment #56
socketwench CreditAttribution: socketwench at FFW commentedAnd committed! Thanks everyone! Please follow up in new or linked issues.