Follow-up to #2466385: flag count API functions are broken.

Problem/Motivation

There are several straight functions related to the flag count API. These are remnants of the 7.x module and are broken given the rest of the drastically changed module.

Proposed resolution

Refactor and fix the flag count API into a new FlagCountService. Instead of being tied directly to the FlagService, FlagCountService would rely on the event system and listen to the flag and unflag events.

Remaining tasks

  • Remove flag count methods from FlagService (yay!)
  • Figure out caching. Patch uses old D7 pattern, not tagged cache API.
  • Write tests for flag counts.

User interface changes

None.

API changes

A new FlagCountService would be available at id 'flag.counts' that would provide access to the flag count API.

Comments

joachim’s picture

Status: Active » Postponed
martin107’s picture

Assigned: Unassigned » martin107
StatusFileSize
new9.45 KB

First draft ... it may need more than a little finessing - but I hope the general shape is correct.
I don't think it yet warrants serious review but I prefer to publish early.

So to start off just a comment
The constants FlagEvent::ENTITY_FLAGGED/ENTITY_UNFLAGGED/FLAG_DELETE

This is fine and dandy... but given the event name the $action parameter of the FlagginEvent::_construct is redundant/removed.

  /**
   * Builds a new FlaggingEvent.
   *
   * @param \Drupal\flag\FlagInterface $flag
   *   The flag.
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity to be acted upon.
   * @param string $action
   *   The action to perform. One of 'flag' or 'unflag'.
   */
  public function __construct(FlagInterface $flag, EntityInterface $entity, $action) {

I have added @event tags to the names of the events as per the coding standard.
https://www.drupal.org/coding-standards/docs#event

In the fullness of time they need the appropriate @see tags to link to the new classes.

The FlaggingCounterSubscriber is little more than a wrapper for 2 methods in the flag.count service ... which use calls to a Drupal::service
Ugh so not great maybe the flag.count service is a little redundant?

Anyway more tomorrow

joachim’s picture

According to the event docs at https://api.drupal.org/api/drupal/core!modules!system!core.api.php/group..., the event subscriber itself needs to be registered a service.

Therefore given this, and your finding the subscriber ends up being just a wrapper, it seems to me that the event subscriber side and the service side of the flag counts could be one and the same.

martin107’s picture

Status: Postponed » Needs review
StatusFileSize
new8.71 KB
new8.85 KB

I kept this postponed because I knew the events were not firing and FlagConfirmFormTest was failing

Sorry for burying the lead - it is work in progress ... registering the service was the missing key ... so thank you for the prompt reply a little nudge in the right direction - and thanks for solving hidden bugs :)

For access to Entity and Flags from the Event I have opted to add getters... others may want to convert the property from private to public
Meh ... me I have have no opinion and will be flexible.

Naming thing is harder though . I am trying to fit in with the implicit conventions of the modules.

I have renamed FlaggingCountSubscriber() to FlagCountSubscriber()

We have a db table flag_counts.
FlagEvents for the act of Flagging
A service flag.count

In that context is seemed correct.

I still think it is early days and I am having a mini-review myself. but I am moving to needs review to trigger testbot.

martin107’s picture

Assigned: martin107 » Unassigned
StatusFileSize
new8.25 KB
new3.65 KB

1) Splitting off changes to FlagEvents as an out of scope side issue.
#2471086: Standardise annotations surrounding FlagEvents

2) getters -- now annotated using a standard description prefered in a drupal context.

3) A handful of minor coding standard tweeks.

I am happy to leave this issue as a "just move code into the correct place" thing... and leave my structural comments to the other issues pent-up behind this one.

In that light I think it looks complete.

joachim’s picture

This seems to be making a few API changes to the events that this new service subscribes to...

martin107’s picture

StatusFileSize
new7.53 KB
new1.47 KB

I contend some API change is justified.

I have reworked the code to remove all changes to FlagEventBase.php - by placing getFlag() in FlaggingEvent.php

This is because not all future classes extending FlagEventBase will need to grant public read access to the $flag, but FlagCounterSubscriber requires it.

joachim’s picture

Status: Needs review » Needs work

Oh I am absolutely not objecting to API change! It's just that I'd probably want to do the API change in a separate issue. I like to keep issues (and thus commits) small and focussed :)

Here you're removing the action from FlaggingEvent, and I'm not sure why...

  1. +++ b/src/Event/FlaggingEvent.php
    @@ -23,27 +23,36 @@ class FlaggingEvent extends FlagEventBase {
    +   * Returns the Entity associated with the Event.
    

    The pattern so far is to call it a 'flaggable entity' in comments, to distinguish from the flag and the flagging entity.

  2. +++ b/src/EventSubscriber/FlagCountSubscriber.php
    @@ -0,0 +1,87 @@
    +  /**
    

    Missing a blank line at the top of the class.

  3. +++ b/src/EventSubscriber/FlagCountSubscriber.php
    @@ -0,0 +1,87 @@
    +   * Reverts incrementation of count of flagged entities.
    

    That wording seems a bit convoluted!

martin107’s picture

Status: Needs work » Postponed
StatusFileSize
new6.24 KB

Some API changes hived off into

#2473349: Expose FlaggingEvent::flag/entity for public read access.
#2473423: Remove redundant FlaggingEnity::action

making this issue postponed on them

2) Fixed.

3) Corrected.

When the other issue are commit this patch will have to be revisited to fix issue related to #2473423: Remove redundant FlaggingEnity::action

joachim’s picture

+++ b/flag.services.yml
@@ -8,3 +8,7 @@ services:
+  flag.count:
+    class: Drupal\flag\EventSubscriber\FlagCountSubscriber
+    tags:

What I meant further up is that the class that subscribes to the event could be the FlaggingCountService itself.

joachim’s picture

Thanks for doing the extra legwork with the side issues and patches. Much appreciated! Having had to do a fair bit of git repository archaeology on various modules in the past, I know how useful this can potentially be.

Another thing -- this issue's summary is about moving the procedural functions over to a new service -- we seem to have veered off a bit with the even subscribing stuff.

martin107’s picture

As part of the discussion do I understand you correctly

Do you want the flexibility to access these functions from outside of the event system.

Say to allow for the possibility when triggering a fully blown FlaggingEvent has too much overhead - or to run this procedurally?

\Drupal::service('flag.count')->incrementFlagCounts(FlagInterface, $flag, EntityInterface $entity) 
martin107’s picture

Status: Postponed » Active
martin107’s picture

Status: Active » Needs review
StatusFileSize
new5.58 KB

This is a reroll triggered by #2473423: Remove redundant FlaggingEnity::action

Locally I see FlagSimpleTest fails... but I think that is broken in head at the moment.

Drupal core is changed alot today as DevDays ends.

socketwench’s picture

Status: Needs review » Needs work

It seems odd to me to name the class FlagCountEventSubscriber AND expose that as a service. It would make more sense to me to name it FlagCountService since to a contrib developer working with flag, they would care more that it's a service than how it does it's job internally (as an event subscriber).

There should also be an interface for the FlagCountsService in the case third party modules want to replace it. The same should go for FlagService, but that's another issue.

martin107’s picture

Status: Needs work » Needs review
StatusFileSize
new16.44 KB
new11.28 KB

From the issue summary

Refactor and fix the flag count API into a new FlagCountService. Instead of being tied directly to the FlagService, FlagCountService would rely on the event system and listen to the flag and unflag events.

But not all FlagCountService function need to listen to flag and unflag events.

Regarding the name change. It would be reasonable for me to be out voted 2 to 1 on this but before I move the method associated with the FlagEvent into a FlagCountService - let me do the uncontroversial thing and move the other five count methods from flag.module into a newly created FlagCountService/FlagCountServiceInterface

I note these methods are deprecated and from flag core they are only called in FlagCountsTest.

It was a bit more than a copy past exercise - I tried to find some reasonable name changes....

I am just including text, that maybe coming to a change record near you soon :)

flag_get_counts($entity_type, $entity_id) becomes \Drupal:service('Flag.count')->getFlagCounts($entity_type, $entity_id)

flag_get_entity_flag_counts($flag, $entity_type) becomes \Drupal:service('Flag.count')->getEntityFlagCounts($flag, $entity_type)

flag_get_flag_counts($flag_name, $reset = FALSE) becomes \Drupal:service('Flag.count')->getFlagTotalCounts($flag_name, $reset = FALSE)

flag_get_user_flag_counts($flag, $user) becomes \Drupal:service('flag.count')->getUserFlagCounts($flag, $user)

All these function use db_select which in the next iteration should be directly injected into the service.
and phpcs reports lots of documentation error in FlagCountServiceInterface but I want a small step copy and paste stage as much as possible.

[ The FlagCountSubscriber methods also use db_merge where a testable dependency injection solution should be found. ]

It would make more sense to me to name it FlagCountService since to a contrib developer working with flag, they would care more that it's a service than how it does it's job internally (as an event subscriber).

I was following the almost universally followed core convention that things that do "implements EventSubscriberInterface" have a subscriber suffix. So we pull developers in 2 directions.

With feedback from both of you I will move

incrementFlagCounts
decrementFlagCounts(FlaggingEvent $event)
getSubscribedEvents()

into the FlagCountService

But this is my last attempt to draw a distinction between event methods and stand alone service calls.

joachim’s picture

On the one hand, having this in two classes is clean. The Service is used by external code to obtain flag counts. The subscriber reacts to flagging events to keep the counts updated.

On the other hand, obtaining flag counts and updating them are two sides of the same coin. Having them split across two classes in two different parts of the /src tree seems faffy. Also, calling it the Subscriber doesn't convey the important part of what it does. It subscribes, but only because that's the way to be notified a flagging has happened, but that's not what we care about -- its job is to keep the counts up to date. It's a count manager.

Given that Berdir commented on another issue that most core service classes aren't called FooService, and that we could consider renaming FlagService, I can see a case for a unified FlagCountManager.

martin107’s picture

Assigned: Unassigned » martin107

Sure I will make the changes.

socketwench’s picture

Given that Berdir commented on another issue that most core service classes aren't called FooService, and that we could consider renaming FlagService, I can see a case for a unified FlagCountManager.

Agreed.

martin107’s picture

Assigned: martin107 » Unassigned
Issue tags: +Needs change record
StatusFileSize
new16.35 KB
new12.71 KB

1) Merged FlagCountService and the EventSubscriber into FlagCountManager and its corresponding FlagCountMangerInterface.

2) Now directly inserting the database service into the FlagCountManager, removing calls the db_select, db_update and db_remove.
The next step on the meta is add more testing ... so I want leave the new class in a state ready for that.

3) As for the "phpcs reports lots of documentation error in the interface" I have just cut and pasted the original text -- -so there is no change in documentation quality
I will add this as a separate issue to the meta....on the basis it is easier to review tightly focused issues.

Manually tested a simple flag -- could unflag and flag ok
FlagCountTest passes locally - let us see what testbot makes of the rest of it.

socketwench’s picture

Liking what I'm seeing so far. Minor quibbles on method names and if increment and decrement can be protected rather than public, but I'm willing to let that slide so we can get this patch in. I'll try to do a more thorough review later.

martin107’s picture

Sure naming things is hard.... I will be happy to make any changes that you like....

If I remember correctly those two methods need to be public as something deep within the bowls of Mordor err I mean the event system takes up the string name from the array returned by FlagCountManager::getSubscribedEvents().

socketwench’s picture

I will be happy to make any changes that you like....

Mostly having so many somthingFlagsomething() methods seems silly to me. We know its for Flag. It's on the FlagCountManager.

If I remember correctly those two methods need to be public as something deep within the bowls of Mordor err I mean the event system

Looks like. Pity. Still, good to keep it off of the interface.

socketwench’s picture

StatusFileSize
new16.29 KB

I would go for method names more like this.

joachim’s picture

Status: Needs review » Needs work

Looking really good!

Those flag count API methods really need a documentation rewrite though -- reading them I can't figure out at all what they're going to give back to me. As a follow-up though, as they've been like this for ages.

  1. +++ b/src/FlagCountManagerInterface.php
    @@ -0,0 +1,76 @@
    +   * @deprecated In Drupal 8
    

    The @deprecated comments need to be taken out ;)

  2. +++ b/src/FlagCountManagerInterface.php
    @@ -0,0 +1,76 @@
    +  public function getTotals($flag_name, $reset = FALSE);
    

    Missing a @return. Which was also the case for the procedural version too, so never mind -- one for documentation clean-up issue to come later.

And a bit of regexp processing to the diff for the tests gets us this set of pairs of changes for the CR:

flag_get_entity_flag_counts($this->flag, 'node');
$flagCountService->getEntityCounts($this->flag, 'node');

flag_get_user_flag_counts($this->flag, $this->adminUser);
$flagCountService->getUserCounts($this->flag, $this->adminUser);

flag_get_counts('node', $this->node->id());
$flagCountService->getCounts('node', $this->node->id());

flag_get_flag_counts($this->id);
$flagCountService->getTotals($this->id);

Initial draft here: https://www.drupal.org/node/2476349

martin107’s picture

new names look better, draft look good.

side issue created.

Unless I am missing something this issue looks complete...

joachim’s picture

Drat. We broke this with #2477781: remove FlagService::unflagByFlagging().

This is a straight reroll.

joachim’s picture

Assigned: Unassigned » shabana.navas
Status: Needs work » Needs review
StatusFileSize
new16.06 KB
new1.67 KB

Fixes:

- removed the @deprecated lines
- removed a @todo that's about to be done ;)
- removed a surplus blank line

And now we've all touched this patch... if someone wants to just give it a quick sanity check that I've not broken anything, I'll break the guidelines and commit it.

joachim’s picture

Assigned: shabana.navas » Unassigned

Oops. Didn't mean to assign it! I'm don't think Shabana's been following D8 work lately.

Status: Needs review » Needs work

The last submitted patch, 28: 2467413-27-28.flag_.flag-count-service.diff, failed testing.

joachim’s picture

Status: Needs work » Needs review

I keep forgetting about uploading the interdiff first...

The last submitted patch, 2: FlagCountService-2467413-2.patch, failed testing.

The last submitted patch, 9: FlagCountService-2467413-9.patch, failed testing.

The last submitted patch, 27: 2467413-21-27.flag_.flag-count-service.diff, failed testing.

martin107’s picture

Assigned: Unassigned » martin107

I have made a slow visual scan of the changes....

Mentally I followed the movement of the comment and the method into their new homes...
I don't think anything has slipped...all looks good.

so +1 from me. I will work on the change record this afternoon ( in about 3 hours. )

martin107’s picture

Assigned: martin107 » Unassigned
Issue tags: -Needs change record

Here is the draft change record.

https://www.drupal.org/node/2478089

joachim’s picture

Thanks, I'll commit soon.

With the CR... I'd already started on a CR here: https://www.drupal.org/node/2476349

(CRs that point to an issue are listed at the top right in the lists of related stuff.)

joachim’s picture

Status: Needs review » Fixed

  • joachim committed bdede38 on 8.x-4.x authored by martin107
    Issue #2467413 by martin107, socketwench: Moved flag count API functions...
joachim’s picture

Ah yes, and I need to publish the CR.

I'd really like to add headings to it, to explain each different method of the API, but I'm really not clear as to what they do. Eg, flag_get_entity_flag_counts() and flag_get_counts() -- what's the difference?

I'll publish the CR now, and when I find time to sit down and work on #2476499: Give the documentation of the flag count API some love. (or if someone else beats me to it), then the CR can be expanded and clarified with material from the work on that issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.