Closed (fixed)
Project:
Simplenews
Version:
8.x-2.x-dev
Component:
Code
Priority:
Major
Category:
Task
Assigned:
Unassigned
Reporter:
Created:
1 Mar 2013 at 10:29 UTC
Updated:
1 Jul 2019 at 19:49 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #1
berdirYep, doesn't need to be static IMHO.
Having the settings in the class constructor means that sending a node with different settings needs a different constructor.
So my suggestion would be to move $settings from the constructor to the method.
Or we could even move $newsletter too?
Having the $settings as a separate argument means that the implementations of that interface do not need to know how it is stored, even if it would be part of $node.
Comment #2
rlmumfordAre we going to need a recipient handler object to persist for more than one node? If not then is this a big deal? If you move $settings out of the constructor then you also need it in buildRecipientQuery() and buildRecipientCountQuery() (which incidentally means we can no longer be countable - I don't know if we care about that).
I wonder if we want to start passing SimplenewsSource classes around instead of entity/entity_type pairs. Then add methods to those to get the newsletter, handler and settings etc.
Comment #3
berdirHm, good point.
Well, the problem is that $handler and $settings does come from the node.
So it's a bit weird parts of that are passed to the constructor and parts if to the method.
There are very few cases where we need multiple handlers for different nodes (sending of multiple newsletters at once), so what about:
The helper method would extract the necessary values from the node and pass it all to the constructor of the handler.
Creating an object and passing some values in isn't that costly and if we end up doing it many times, we can still add a per-node static cache.
Comment #4
rlmumfordSo here's a very simple first patch for this.
I think we need to remove the buildRecipientQuery() and buildRecipientCountQuery() from the interface (otherwise this is all rather pointless). Once we've done that, we could make an abstract SimplenewsRecipientHandlerQueryBase class that has the add to spool method but not the query methods.
Comment #5
rlmumfordHere's another patch with some changes to the way the interfaces are structured.
Comment #6
adamps commentedI think this is covered in a more up-to-date way in #3054695: Problem with recipient handler interface and spooling. In particular note that in D8, the current implementation of the add to spool logic bypasses the entity query interface so we need a more radical rethink.
Comment #7
adamps commentedComment #8
adamps commentedComment #9
adamps commentedComment #10
jonathanshaw@AdamPS explained his thoughts about this in #3054695: Problem with recipient handler interface and spooling #16:
There's a lot going on this patch beyond simply adding a new method to the Handler, and it seems to break BC in multiple ways.
I'd suggest that removing the mail field is a separate issue? (I also don't yet understand the need.)
To maintain BC while keeping DRY here we could:
Comment #11
jonathanshawWe seem to have lost the code that filters the query to only subscribers of the current newsletter
Comment #12
adamps commented@jonathanshaw Thanks for the comment. Yes this is a rough hack proof of concept!
#11 You are right
#10
NB I don't remove the mail field I just stop using it. The reason was in a comment in the other issue #3054695: Problem with recipient handler interface and spooling #16.
Yes this isn't BC. The module is contrib and beta - it's not core stable. The existing interface is very limited and keeping BC will add complexity/confusion to the code and take more time (your suggestions seem broadly correct but there are further problems you didn't mention). Simplenews fixes have grown hugely beyond my plans and I don't have spare time. My guess is that hardly anyone implements RecipientHandlerInterface. For those that do, it's a fairly easy tidy up and they can copy the changes in RecipientHandlerBase.
Comment #13
adamps commented@Berdir - any objection to non-BC? Would you prefer I start a new branch? It feels overkill to me but I could.
Comment #14
jonathanshawHow about loading from snid if it's not empty, falling back to loading from mail if snid is empty. That solves performance and change of address concerns, but may keep a little flexibility for some use cases.
I'm not sure what the gain is in touching these
Comment #15
adamps commentedThanks @jonathanshaw. I will answer those points, but please after that I think it's best if you don't review any more until I produce a better patch.
1.
If someone has filled in mail but not snid then it has not solved change of address concerns.
I am in favour of flexibility when it comes to "don't restrict options" but not when it means "allow people to do the same thing in multiple ways some of which are buggy". This seems like the second case. If someone has a subscriber then why can't they put the snid and avoid the bug. If they don't then filling in mail won't help - they need to fill in data.
2. I now plan to keep data. I still prefer to remove mail for the reason above. It serves no purposes, encourages people to use the interface wrongly and will occasionally contain an inaccurate value.
Comment #16
adamps commentedOK now ready for review please.
RecipientHandlerNewUsers is there as an example in case you want to try it. I won't check it in like this, but instead move that class to the demo or tests. However I hope you will agree it's pretty neat that you only have to write 3 lines of code.
Comment #18
jonathanshawNice! As you say, the simplicity of the entity query implementation is lovely. If we're doing this, I suggest considering in a follow-up the possibility of automatically create a subscriber for each user.
We're hard breaking BC in 4 different ways here:
1) Removing the simplenews_get_recipient_handler() method
2) Altering RecipientHandlerInterface
3) Making RecipientHandlerBase no longer implement RecipientHandlerInterface, breaking anyone extending it
4) Altering SpoolStorageInterface
You've addressed RecipientHandlerInterface in #10/12 above. I'll discuss the others below, in case you think them worth preventing:
This seems like an unrelated change, and puzzling as you're moving away from allowing non-subscriber recipients.
I have the impression that traits are thought of as a better solution for code reuse, and a hierarchy of base classes might be a code smell. Maybe this should be a trait.
Another question is BC. The fact that the current version is called RecipientHandlerBase would have suggested to other people that they should extend it. But if they have done so, their code will now break as it will no longer implement RecipientHandlerInterface.
We could both preserve BC for extending handlers, and avoid the layered base classes, if we:
a) created a RecipientHandlerTrait with the code you proposed to put in RecipientHandlerBase
b) put in RecipientHandlerBase the code you proposed to put in RecipientHandlerSelectBase
c) Don't have a RecipientHandlerSelectBase
You could deprecate this instead of removing it if you cared about making it BC. But definitely needs to die sometime! Unlikely to be used by anyone outside I would have thought.
I'm not sure why this needs to be public.
Adding this to the interface breaks any alternative implementations in theory, but it's likely that anyone making an alternative will extend the provided implementation so it's likely harmless. Whether it's necessary, I'm not sure.
Comment #19
adamps commentedThanks @jonathanshaw. I feel you are still aiming for a higher level of BC than is required given it's contrib and a beta. For sure we shouldn't break sites that have no customisations/hooks. However in a few cases, the scale of necessary changes are large and it's too restrictive to avoid any change to interfaces.
I am willing to consider creating a new branch, but it seems like more work/disruption without really helping anyone. I have no interest in supporting two branches so I would then only apply fixes to the new branch. So people would still need to change their code.
1. I feel I recently grasped how the current code allows "non-subscriber" recipients. There must be a subscriber object, but it can be a temporary one that is not in the database but instead serialised in the data parameter. In particular I deduced that from this line:
I haven't tested it yet, but assuming it works it seems like a good idea. I'll report back.
2. The case for traits is when you want to reuse code without any common class hierarchy. However a hierarchy is preferred where possible, and it is extremely common in core, e.g. class Node extends EditorialContentEntityBase extends ContentEntityBase extends EntityBase.
RecipientHandlerBase is documented as a base for all recipient handlers, so it should not include any code specific to the particular implementation of select - especially as I see that implementation as a special case, breaking core interfaces.
I've made it as easy for people as I can: existing recipient handlers likely need to change to extend either RecipientHandlerSelectBase or RecipientHandlerAll.
3. See BC 1)
4. It is public for improved BC. I am removing simplenews_get_recipient_handler() so there should be a drop-in replacement.
5. See BC 4). This is following the global direction of D8 to use services rather than functions. Functions prevent customisation to alter the implementation, use dependency injection etc.
Comment #20
adamps commentedNew patch fixes SimplenewsMonitoringTest. I think the other fail might be a random.
Comment #22
adamps commentedComment #23
adamps commentedPlease let me know if there are any further review comments.
Once we have completed code review I would like to commit the patch without tests as a first step. Second step will be to fix some of the related issues. Then at the end when it's all working I will write a new test file for recipient handler.
Comment #24
berdirHad a very quick look, a lot going on here, will need some time to understand this and think about BC. An updated issue title and summary would be helpful to understand this :)
Comment #25
jonathanshawI'm happy with whatever you think best about BC.
I wonder about making this not abstract, but instead providing here an EntityQuery based implementation that already does some of the work. This might both provide an example to guide others, and help with some of the details that are a little obscure like
+ $select->condition('t.subscriptions_status', SIMPLENEWS_SUBSCRIPTION_STATUS_SUBSCRIBED);
+ $select->condition('s.status', SubscriberInterface::ACTIVE);
Alternatively, perhaps the docblock could include a code example that achieved the same thing as the simplenews_all handler, but using EntityQuery instead.
Would it be better to handle newsletter_id as an expression instead of a query field? I suspect not, the way it is (which you've kept) allows the newsletter to vary per recipient which could be desired in some cases.
These seem unrelated to the rest of this issue. I've no idea if they're safe or effective changes.
Comment #26
adamps commentedResponses to #25
1. I'm confused! The lines of code that you referenced seem to be from RecipientHandlerSelectBase but your text refers to RecipientHandlerEntityBase. RecipientHandlerEntityBase already is an implementation that does most of the work - the entire addToSpool function.
I would expect that if someone wants to tweak RecipientHandlerAll then they would override RecipientHandlerAll and add an extra condition.
My RecipientHandlerNewUsers seems to me like a more likely example of how people will user RecipientHandlerEntityBase. Rather than docblock I will put it into the simplenews_demo sub-module - that allows IDEs, auto-documentation etc to discover it in the class hierarchy.
2. I thought about that but decided not to because it would be a backwards step relating to #2556441: Sending an item to multiple newsletters.
3. For this issue we are changing to look up on 'msid' instead of 'mail'. The old code was muddled with two separate places in the code that call simplenews_subscriber_load_by_mail() whereas only one is needed. So I removed one call, and changed the other to simplenews_subscriber_load().
Comment #27
adamps commentedResponse to #24: IS done. I raised a separate issue to discuss BC as it affects multiple issues in development: #3056958: Decide strategy for BC / branches.
Comment #28
adamps commentedSlightly update patch that prepares for multiple newsletters. I'm not necessarily planning to solve that issue soon, but the idea is to ensure that we can solve it without having to change the interface again.
I have also added a list of remaining tasks to the IS.
Comment #29
adamps commentedOn the BC issue, I now favour creating a new branch, and would like to do it ASAP. Please can you comment on that issue to confirm/reject?
I changed my mind because I have drawn up a list of issues that aren't fully BC. I found that there are many parts that would be complex and disruptive to try and release in a "semi-BC" way. Thanks @jonathanshaw for continuing to press me to think about it!
Comment #30
jonathanshawMy thought is that sometimes people might want to filter by something like newsletter subscribers who have a certain user role.
This is much easier using your nice new entity query (rather than just adding conditions to RecipientHandlerAll's select query). But doing it using an entity query involves understanding how one should properly treat these 2 (non obvious) status fields, so I was wondering if it would be nice to provide a base implementation or doc for that. Certainly not important.
Comment #31
adamps commentedOK here is a new patch that I would like to commit to a new 2.x branch. Here are the changes - basically fixed some more bugs and added better examples.
I will write automatic tests later because we need some other fixes first. However it has been tested fairly well by hand.
Comment #32
jonathanshawLooking great Adam, some really good things emerging here. The following comments offered simply in the interest of thoroughness, to make sure you're sure:
Amazing how easy it is to make a temporary subscriber in the recipient handler here.
But it does make me wonder: if we're supporting this possibility, why not leave the mail column undeprecated, and automatically create a subscriber downstream if we have a mail but no data or snid?
Insisting on snid and deprecating mail I could understand, but if snid remains optional and passing mail via data is allowed, we still have the problems of mail being a somewhat unreliable way of looking up a subscriber.
Maybe the reason is simply that mail is an unnecessary convenience being removed for maintainability, as you can do with data anything you can do with mail.
There's an argument for this being on buildEntityQuery() in RecipientHandlerEntityBase as 99% of people extending RecipientHandlerEntityBase will want this code.
Great example to have
If we're doing a 2.x branch, maybe it's time to rename this 'subscriber' as that's what this really is. My drupal expectation was that 'data' was an arbitrary array where a value could store any extra values they liked.
Alternatively, 'subscriber' could be a array key within 'data'.
I don't really see the point of this method, but it's certainly harmless.
Who would use RecipientHandlerIdsBase instead of RecipientHandlerEntityBase or RecipientHandlerSelectBase? I couldn't think of a use case.
Conceivably you could use the value of mail here to create a temporary subscriber object. But given that virtually nobody will use this method, that's probably only worth doing if you use it in tests.
Not sure why we're removing this
Comment #33
adamps commentedThank you for checking. I believe I have everything covered however please follow up if you have any doubts.
1. Interesting questions.
'data' allows passing not only a mail but also a langcode and any additional custom fields such as perhaps a name.
problems of mail being a somewhat unreliable way of looking up a subscriber.No 'data' does not lookup a subscriber in the database - it initialises a temporary subscriber. That's another way that it differs from the previous 'mail' parameter.
2.
I don't agree: in all my use cases the recipient handlers are based on user data and there are no actual subscribers. But even if a high percentage want the code, unless it's 100%, then force adding them is bad because it's messy for code to try and take them off again.
4. It's not a subscriber though - it is data properties to set on a temporary subscriber. The data is unserialised then passed to EntityInterface::create for parameter "array $values" which has comment "array of values to set, keyed by property name".
5. This is for future compatibility. In general, RecipientHandlerBase has a $newsletter_ids array field because one day there might be multiple or none. The wrapper getNewsletterId() is a shortcut to check there is precisely one and return it.
6. I covered the reason briefly in #31:
I have a recipient handler that starts with an EntityQuery on 'commerce_order_item', loads the user from the uid field then looks up the corresponding snid. This needs RecipientHandlerIdsBase because RecipientHandlerEntityBase requires EntityQuery on 'simplenews_subscriber'. I could remove RecipientHandlerEntityBase and insist that everyone use RecipientHandlerIdsBase , but I thought the extra help in RecipientHandlerEntityBase was generally useful.
7. This seems similar to 1. If they want a temporary subscriber, they should pass the 'data' parameter which accepts all the necessary parameters.
8. Because that's a misuse of the 'data' parameter, so a potentially confusing example. The only reason it didn't break the tests before was that the spool was never sent.
Comment #34
jonathanshawI tried thinking about ways to refactor the RecipientHandlerBase classes to help use cases such as wanting to pass an array of temporary subscribers etc. But I can't see a generic way of doing it. The base classes in the patch and demos do a good job of showing some of the range of things that is possible.
The real step forward is moving addtoSpool() into the RecipientHandler, that unlocks things very nicely.
Feels like this is ready.
Comment #35
adamps commentedOK I have figured out how to eliminate RecipientHandlerIdsBase - thanks for asking good questions to prompt me to think further. This feels much better - notice how RecipientHandlerSiteMail has become simpler.
If you are happy, please set RTBC.
Comment #36
jonathanshawI crossposted with you Adam! I don't have time to review you last right now, here's what I was going to say ...
Some thoughts:
1. REQUEST_TIME is deprecated, per https://www.drupal.org/node/2785211
2. We've got know performance concerns with these handlers, so pagination is an interesting possibility.
3. As I said in my last comment , I'm curious about whether we can provide more flexible base methods.
With regard to 2 & 3, I dreamed up the approach below overnight. Feel free to ignore, it's only convenience stuff.
Comment #37
adamps commentedIn that case, I would like to go ahead and commit later in the week. If you later spot a problem then we can tackle it whilst I'm writing the tests.
1. True, but there are 23 matches across the whole module so let's tackle that separately.
2. Actually we have speculative performance concerns. I am a firm believer that performance fixes should only be made in response to actual problems and with clear evidence of an improvement. Your code would introduce a new window condition whereby an issue was partly spooled then timed out, but there isn't any code to track progress and continue from the last success.
3. I think that we had roughly the same idea here.
Comment #38
jonathanshawYes, we're thinking similarly.
I'm not sure it makes sense for addArrayToSpool to return the count. Instead you could do:
Comment #39
adamps commentedTo a degree I see your point - the caller knows the count right. But on the other hand why shouldn't it return the count, I will spend another half hour
Comment #40
adamps commentedSet to 2.x branch. Delete spool 'mail' column rather than deprecate. Fix minor coding standard error.
I will commit this one once the tests pass.
Comment #41
adamps commentedComment #42
adamps commentedComment #44
adamps commentedI think it is clearer if I mark this as fixed because it has been committed. I have created #3062352: Add tests for RecipientHandler for the tests.