Not sure if this is the right thing to do, as:

- a) we're overloading Entity::save() with a new exception throw, which the parent method doesn't throw. This is fine in PHP, but goes against pure OO principles
- b) we've moving application logic out of the service, which is a clean place for it to be

On the other hand, without this, other modules that manipulate entities could save flaggings that violate Flag's application logic.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

joachim created an issue. See original summary.

joachim’s picture

Status: Active » Needs review
FileSize
3.12 KB

Rough patch for discussion. Probably needs more clean-up and docs.

Status: Needs review » Needs work

The last submitted patch, 2: 2640944-2.flag_.app-logic-service-save.patch, failed testing.

Berdir’s picture

save() is definitely the wrong place, you need to use pre or postSave.

save() is just a shortcut for calling $storage->save($flagging), then your code wouldn't run.

joachim’s picture

Status: Needs work » Needs review
FileSize
3.16 KB

Ah, using preSave looks much better. The base Entity class throws an exception here for bundle ID length, so there's no longer an overloading problem (just a documentation one -- #2662152: Entity::preSave() throws a exception, but this is not documented).

Thanks for the tip!

Status: Needs review » Needs work

The last submitted patch, 5: 2640944-5.flag_.app-logic-service-save.patch, failed testing.

martin107’s picture

I am triggering a retest because the errors I get locally are just different from from those listed in testbot's report.

Also I think things may have changed a bit see #2657384: Flagging should use some standard entity reference

I am posting a patch with functionally identical code

the entity manger is deprecated ...and another pending issue is removing all references to it .. and so I am just smoothing out any potential conflicts.

Status: Needs review » Needs work

The last submitted patch, 7: 2640944-7.flag_.app-logic-service-save.patch, failed testing.

martin107’s picture

Just proving to myself what is wrong ... I would be the first to admit that my change is clumsy

But before I fix it properly I want to open a side issue

From Flagging:baseFieldDefinition() both

$fields['flagged_entity'] and $fields['uid'] should set the cardinality to 1 as it does not make sense for there to be more than one.

But that is a change I would like to see in an isolated issue.

Status: Needs review » Needs work

The last submitted patch, 9: 2640944-9.flag_.app-logic-service-save.patch, failed testing.

martin107’s picture

In #9 I introduced a clumsy way of getting at the user behind $this->uid

So I am fixing that ... the tests should be better now that #2669994: Failing tests has been committed.

Status: Needs review » Needs work

The last submitted patch, 11: 2640944-11.flag_.app-logic-service-save.patch, failed testing.

martin107’s picture

Fixing the tests is trivial .....

Now that the flagging verification tests are run in preSave() .. and because preSave position in the call stack they are rethrown as EntityStorageExceptions
by SqlContentEntityStorage::save() hence the test errors.

But before I supply a patch I want establish a good place to run our integrity test .. As if differs for flagging and unflagging and will result in a significant refactoring of unflagging.

A summary of flagging MkII ( FlagService::flag() )

1) Flagging::create()
2) Flagging::save() which triggers integrity checks in preSave()
3) dispatch FlaggingEvent

So far so good..

The difference is that the unflagging event must precede Flagging::delete() so the event can make use of the data in the existing flagging.
Consequently I want to move all the integrity checks from FlagService::unflag() and FlagService::getFlaggging() into FlagService::getFlaggings()
That way all the checks are common when ever a flagging is requested.

so the summary of FlagService::unflag() :-

1) FlagService::getFlagging() -- triggers integrity checks
2) Flagging::delete()
3) Dispatch Unflagging event.

Anyway I will get to this soon..

joachim’s picture

> rethrown as EntityStorageExceptions by SqlContentEntityStorage::save() hence the test errors.

Oh that's less than ideal.

martin107’s picture

Title: move enforcing of application logic from service to Flagging::save() » Move flagging integrity checks from service to Flagging::save() and unflagging checks to a better place.
Status: Needs work » Needs review
FileSize
9.32 KB
6.64 KB

Ah yes sorry .. thanks for pointing out my err.

Changed the title to reflect that fact that unflagging integrity checks are now in a place where they will be of more general use.

as per #13 I fixed tests and refactored unflagging logic.
The only deviation is that I decided not to move logic from FlagService::getFlaggging() into getFlaggings()

I have fixed up the documentation so I had to added preSave to the interface.

Looking at FlagServiceTest - There is a design outcome/kink which is a natural outcome of what I described in #13.

flagging integrity checks are rethrown and need to be caught as a EntityStorageException
unflagging integrity checks are not rethrown and come out as logic Exceptions.

locally FlagServiceTest now passes. but now FlagService::getFlagging is more exacting in what it accepts other tests might fall over.

Status: Needs review » Needs work

The last submitted patch, 15: 2640944-14.flag_.app-logic-service-save.patch, failed testing.

martin107’s picture

From #14

Oh that's less than ideal.

Messages from Flagging::presave() like

'The flag does not apply to entities of this type.'

do filter up - but it is true that the stack trace becomes fragmented, taking more detective work to figure it out than it should.

There are other directions to try, not without issues of their own, an access control handler which rejects requests before entry into the controller?

At the moment I have socket wenches "review all the things" mantra in my head.

joachim’s picture

I remember Berdir mentioning on another issue that we might consider implementing our own storage controller. Can't remember what the purpose was, but it would allow us to intercept calls to save():

  return $this->entityManager()->getStorage($this->entityTypeId)->save($this);
joachim’s picture

It is starting to feel that what we're trying to do here is a hack -- that core entities don't allow for enforcement of application logic, just access.

martin107’s picture

I am trying a new approach.

What is relevant from https://drupalwatchdog.com/volume-5/issue-2/introducing-drupal-8s-entity...
( see the subsection "Creating Your Own Constraint Plugins" )
is the implied instruction "Copy what you need from Comment, CommentNameConstraint and CommentNameContraintValidator"

So I copied the classes and converted them for our purposes. this is a very early version so we can discuss the pros and cons

Pros
1) It abstracts the validation into a location inherently tied to the Flagging entity so it can easily be called whenever we want in future code.
2) No more inappropriate EntityStorageExceptions breaking the stack trace.

Cons
1) Complexity - OMG all the extra boiler plate!
2) I have not yet added all the code to loop through and dealing with any generated violations
3) It covers only flagging not unflagging.

Some variable names are too long, I am looking at you FlagEntityUserConstraint::messageEntityTypeFlagTypeMismatch

joachim’s picture

I need to find time to have a proper look at this, but I want to say in the meantime that this is sounding like the right approach.

With regard to it not handling unflagging, I'm thinking that's actually OK. There's actually no application logic with regard to deleting a flagging. We have verifications in the service, because we need to check that the (flag, user, flaggable) triple makes sense. But if you were going in via the entity API and just doing $flagging->delete(), then that's fine.

(This does though make me think that we need to change where flag counts are updated -- they shouldn't be triggered by the flag events that we trigger, but by the Entity CRUD API. I'll file an issue for that.)

joachim’s picture

Priority: Normal » Major