Problem/Motivation
#2278383: Create an injectible service for drupal_set_message() was added in a preemptive move to convert the proceedural functions drupal_set_message() and drupal_get_messages() into a dedicated "messenger" service.
Even though this was added, it was done so with full knowledge that it wasn't yet finalized and more work needed to be done to make this a stable service.
Once this issue is in, #2924538: [META] Remove all usages of drupal_set_message and drupal_get_messages needs to be tackled.
Proposed resolution
- Create a stable Messenger service using Symfony's FlashBag service.
- Refactor the existing
\Drupal\Core\Messenger\LegacyMessengerimplementation to provide backwards compatibility that will ultimately be removed in 9.x. - Create the
\Drupal::messenger()helper method to be used in procedural functions. - Deprecate
drupal_set_message(),drupal_get_messages()and<code>\Drupal\Core\Messenger\LegacyMessenger- triggering deprecation errors as needed
Remaining tasks
Create patchCreate tests
User interface changes
None
API changes
The "messenger" service now uses <code>\Drupal\Core\Messenger\Messenger instead of \Drupal\Core\Messenger\LegacyMessenger (which is now deprecated).
Data model changes
None
| Comment | File | Size | Author |
|---|---|---|---|
| #175 | 2760167-173-175-interdiff.txt | 612 bytes | markhalliwell |
| #175 | 2760167-175.patch | 28.92 KB | markhalliwell |
Comments
Comment #3
didebruUnfortunately I got zero occurrences in code search if i search for "Messanger" or "SessionMessenger" .
Comment #4
dawehnerLet me quote myself ...
:)
Comment #6
jibranComment #8
larowlanComment #9
jibranFirst try.
Comment #11
jibranComment #13
dawehnerWow this
FlashBagInterfaceis massively confusing.Could't you set two types and then remove the first by using the first type? I'm just a bit worried that we accidentally loose some test coverage
Comment #14
jibranI tried to fix rest of the fails but no luck. Any suggestion @dawehner? I think the issue is because session service and messenger service both are using the same flashbag. Maybe, we should change the name of the messenger flashbag.
RE: #13
\Symfony\Component\HttpFoundation\Session\Flash\FlashBag::setAllor\Symfony\Component\HttpFoundation\Session\Flash\FlashBag::set. I think we shouldComment #15
dawehnerI've done some debugging, but I'm not 100% sure yet what is causing it.
This test fires 3 request to Drupal.
1. Front page, from the setup method
2. Front page, from the test method itself
3. Front page, from the test method itself
Note: 2 and 3 are cached by page cache as part of this patch. ... It feels weird that they are actually cached, given that
\Drupal\service_provider_test\TestClasssends a message on every request.Further research shows, that the call to session save in
\Drupal\Core\StackMiddleware\Session::handledoens't actually save anything.\Drupal\Core\Session\SessionManager::savecalls to\Drupal\Core\Session\SessionManager::isSessionObsoletewhich returns TRUE in HEAD but FALSE with the patch. Looking at\Drupal\Core\Session\SessionManager::getSessionDataMaskit seems to look directly into$_SESSION, which seems to not have any of the messages data.I thought, okay, we need to maybe look into the messages back properly ... and made changes according to this interdiff ... this didn't worked though, because at that point the messages back no longer has the message as they go printed out already, I think.
Well, this is a bit out over my head right now :) Maybe you can figure it out
Comment #16
jibranTime to summon @znerol!
Comment #17
znerol commentedLet's restore the kill-switch for the moment.
Comment #18
jibranWoW it passed.
Comment #19
znerol commentedI'm looking closer at the
D\s\T\System\FrontPageTestnow. First looking through all verbose output to check for printed messages. I see the following:Set a breakpoint inside
D\C\P\ResponsePolicy\KillSwitch::trigger()and confirm that inside the test nothing else than those messages invoke the page cache kill switch.Previous test results and observations of @dawehner indicate that without the kill switch pages get cached even though a message is printed on the page. This indicates that we currently do not cancel page caching when printing messages. So maybe we can fix that and then try to eliminate the kill switch again.
Comment #20
znerol commentedD\s\Plugin\Block\SystemMessagesBlock:getCacheMaxAgereturnsCache::PERMANENT, the comment says that messages get loaded lazily. Maybe,FrontPageTestshouldn't rely on messages to assert whether or not the front page is displayed? Unless there is a way to assert on lazy loaded content maybe.Unassigning myself, ideas?
Maybe we should tackle the elimination of the kill switch in another issue?
Comment #21
jibranFirst of all, thank you @znerol for jumping in and making this green again your help is much appreciated.
> Maybe we should tackle the elimination of the kill switch in another issue?
Right now
LegacyMessengeris purely internal. Not elimination the kill switch means we can't makeMessengerpart of public API not until we provide the BC in follow up. Personally, I'd like to kill the kill switch in this issue.Let's ping @Wim Leers about caching. I think we need to implement cache tag to get around
Cache::PERMANENT. Some kind of method on Messanger service to add the hash of flashbag to the cache tags.Comment #22
znerol commentedWe need a replacement response policy which is able to detect whether it is safe to cache a page. I'm not quite certain about the logic needed here.
We cannot rely on the contents of the session because that will be already empty when the response policy is invoked.
Depending on whether the lazy-loader decides to include the message fragment directly or rather output a placeholder, we need to vary the behavior of the response policy. In the former case, the page is cachable, in the latter it is not.
Do we need to disable the page cache if any cache contexts bubble up to the page?
Comment #23
znerol commentedThis is impossible because we always seem to have some cache contexts bubbling all the way up.
Another interesting observation: Set a breakpoint at line 26 in
KillSwitch:check(the line which saysreturn static::Deny;). When the debugger hits that breakpoint while running theFrontPageTestthe stack trace indicates that the response policy was invoked from the Dynamic Page Cache.Comment #24
dawehnerTo understand you 100%, this is the main difference from before and after? Previously, $_SESSION was kept around vs. with the new change, this is no longer the case?
Comment #25
znerol commentedInteresting: D/C/Render/Element/StatusMessages::renderMessages() does not specify any cache metadata. Even though it is lazy-loaded, it is still supposed to return the
sessioncache context, isn't it? Maybe even unconditionally (if there is no message), since you never know whether there is one on the next request?If the above is true, then it might be beneficial for the messanger service to implement the
CacheableDependencyInterfacesuch that theStatusMessageselement simply can forward the cache metadata.Guess that's a question for @Wim Leers.
Comment #26
wim leersNit: s/.././
Why remove these but not everything below? IOW: +1 to the concern in #13.2.
I'll try to answer the things that I was pinged for here. But first of all: the issue scope ("let's kill the kill switch") does NOT match what the issue title & summary say.
The killswitch is fundamental to how storing/processing/rendering of status messages works in HEAD (as well as in D7). If you want to change that, then yes, there are going be consequences elsewhere.
In HEAD:
drupal_set_message()while running the code that is building a response to the current request would cause PageCache (= for anon users) to not cache the response. But also Dynamic Page Cache (= authenticated users) respects the killswitch:\Drupal\system\Plugin\Block\SystemMessagesBlocksimply renders['#type' => 'status_messages']and the block itself says it's endlessly cacheable, and it even includes the explanation in its comment:\Drupal\Core\Render\Element\StatusMessages(which corresponds to['#type' => 'status_messages']), we see that it generates a placeholder unconditionally, i.e. 100% of the time. This means it's perfectly cacheable by Render Cache & Dynamic Page Cache: because there is no variation.\Drupal\Core\Render\Element\StatusMessages::renderMessages()is the#lazy_buildercallback that runs once on every page. It doesn't return cacheability metadata because it doesn't matter anymore at this point: the#cachekey on the render array it returns is not specified because we don't ever need/want rendered status messages to be render cached anyway. And this lazy builder callback is invoked for something that is placeholdered 100% of the time (see previous point.)Apparently we want to remove the kill switch. Okay, that'd be lovely to remove indeed. So that's what we want to change. What remains the same? We still want a placeholder 100% of the time, we just don't know if it'll get some content or not ahead of time. What matters, is whether a message was set during this request or not.
It should be possible to use a cache context to communicate this. In HEAD, we should be able to remove the call to the kill switch and instead always let the lazy builder callback return the
sessioncache context (just like @znerol suggested). This will cause Page Cache to not cache the page, thanks to\Drupal\Core\PageCache\RequestPolicy\NoSessionOpen.However, if that's where we stop, then the test still fails. Why? Because only Page Cache respects
\Drupal\Core\PageCache\RequestPolicy\NoSessionOpen. Dynamic Page Cache does not. So we have to dig a bit deeper: in HEAD, we 1) render the overall page, 2) which included a call todrupal_set_message()and therefore to the killswitch, then 3) let Dynamic Page Cache store the result, then 4) render the placeholders, and finally 5) let Page Cache store the result.In HEAD, the killswitch is invoked at step 2, and hence it prevents both Page Cache and Dynamic Page Cache from caching the response. With this patch, Dynamic Page Cache is not yet being informed that it shouldn't be caching the response. Because it's only in step 4) that the messages placeholder is rendered, and hence the
sessioncache context is returned there — after Dynamic Page Cache has already stored the result.Therefore
system_test_page_attachments()doesn't run again — upon the next visit. Only lazy builders for placeholders on the page are invoked again. Therefore thedrupal_set_message(t('On front page.'));is not invoked again.Now, honestly,
system_test_page_attachments()is an abuse of what that hook is designed to do. It's a hook designed for page-level render array attachments, not for sending messages!So, really, I think that the test is at fault here. Adding
$page['#cache']['max-age'] = 0;fixes it.Using cache tags for this doesn't make sense, because no identifiable data is being stored across requests that may change in the future and hence need invalidation.
Comment #27
jibranThanks, @Wim Leers. I have to read it 4 times to understand it.
The issue started as removing
$_SESSIONand then I removed kill switch in new implementation and we all went down 'remove kill switch' rabbit hole.If #26 patch will pass
FrontPageTestthen I'm going to create a new issue to remove the kill switch, move patch from #26 to new issue, and postpone this one on the new issue.LegacyMessengeris internal so we can play around with API before making it public here.Instead of using
drupal_set_messageinsystem_test_page_attachments()why not add the lazy builder for frontpage in the function and usedrupal_set_messagethere. The main goal of\Drupal\system\Tests\System\FrontPageTestis to test the front page functionality which we can achive like this.It is clearly stated in
hook_page_attachmentsdocumentation thatAdd attachments (typically assets) to a page before it is rendered.so I don't think removing the kill switch will introduce the regression as shown byFrontPageTest.Comment #29
jibranCreated #2908026: Remove killswitch from LegacyMessenger and uploaded #26 there. Removing the tags as out of scope changes are moved to #2908026.
Comment #30
znerol commentedOh, right! This is actually quite obvious. In fact it would be useful to formulate this as a generic rule (also for the change-notice because contrib test cases might fail as well due to this change). Maybe something like this:
Messages should only be emitted in response to an action, i.e. on an uncacheable request. For example when a node is submitted or a comment was changed. If a messages is emitted on a cacheable response, then it is completely unpredictable when this message is going to be displayed to the user. This is due to the fact that the content might be served from the cache and hence the code emitting the message is only invoked during a cache MISS.
Comment #31
jibranAny help in making the tests green in #2908026: Remove killswitch from LegacyMessenger would be appreciated.
Comment #32
jibranI had a discussion with @Wim Leers about this issue at DC Vienna. He purposed that instead of injecting kill switch we could use the service directly from the container that would not block us on #2908026: Remove killswitch from LegacyMessenger and we wouldn't have to make any changes to public API (
\Drupal\Core\Messenger\Messenger) once we would be able to remove kill switch. Also, he thinks fixing #2908026: Remove killswitch from LegacyMessenger is a good idea.Here is the updated patch. The interdiff is against #11. #12 is still to be addressed.
Comment #33
kim.pepperJibran and I discussed this issue at DrupalSouth and agreed to add a comment to explain that 'get' from a flashbag actually deletes the message.
Comment #34
jibranAdded the docs.
Comment #35
kim.pepperLGTM
Comment #36
larowlanDiscussed with @kim.pepper and @jibran at Drupal South- let's leave this test in, but change the implementation. I don't think we want the ability to unset as part of the public API (i.e. we don't want to add methods to the MessagerInterface) but we should retain the test to prove that it is still possible. We can do the removal by calling
deleteByTypewhich will return the array with two entries, then unset one and re add the other.Comment #37
jibranThanks, for the review. Addressed #36.
I tried not to use deprecated
drupal_get_messages('status')and I tried to keep changes to the minimum because after injecting messenger service we can replace alldrupal_set_messagebut we can do that leter.Comment #38
kim.pepperBack to RTBC
Comment #39
xjmThanks for working on this.
I almost NWed this for a BC break (even an internal one), until I blamed and saw it was only added in 8.5.x.
Updating the summary to explain why this is allowed change up until alpha1, and to indicate that it must deprecate rather than removing thereafter.
Comment #40
jibranJust a small note that these lagecy methods are not deprecated here and after #2278383: Create an injectible service for drupal_set_message() we didn't create a followup for that so I created one #2924538: [META] Remove all usages of drupal_set_message and drupal_get_messages.Edit: I blame sleep deprivation for this reply :). We should definitely add deprecation error here as @xjm explained in the next comment. Sorry about the noise.
Comment #41
xjmThanks @jibran.
@larowlan and I discussed this issue briefly. Since we're deprecating
drupal_set_message()anddrupal_get_messages(), we should also add the@trigger_error()calls within their code paths. @larowlan mentioned that we'd likely add a lot of test failures for that, but we should be able to add the messages to the list of ignored deprecated messages in getSkippedDeprecations(), and file a followup issue to re-remove it and fix the many fails that'd result.Comment #42
kim.pepperAdded the trigger errors. Discussed with @larowlan at DrupalSouth and split out the DeprecationListener changes so we can see if the trigger_error stuff fails the builds.
Comment #43
kim.pepper@larowlan pointed out the pass patch needs to be last, so re-uploading.
Comment #47
tim.plunkettBoth need to use " and ', or ' and \'. But they need to match exactly!
Comment #48
kim.pepperAh thanks Tim!
Comment #50
kim.pepperHmm. Pretty sure the message matches now.
Comment #51
tim.plunkettYep, it does match. And I ran this locally and put a breakpoint in
\Drupal\Tests\Listeners\DeprecationListener::endTest()where theunset()is.No idea what's happening here, unless getenv('SYMFONY_DEPRECATIONS_SERIALIZE') is false-y.
Comment #52
kim.pepperDiscussed at DrupalSouth with @larowlan about whether we should reference the method on the interface instead of Drupal::service(). If the issue is with quotes, then at least we can also rule that out.
Also fixed incorrect method names in the deprecation annotation for drupal_get_message().
Comment #54
kim.pepperShould this be changed to use the interface as well? (also typo with > instead of ->)
Comment #55
kim.pepperDiscussed with @larowlan in slack and agreed to update annotations to match the trigger_error message, ie. reference the methods on the interface.
Comment #56
jibranRemoving the needs followup tag as #2924538: [META] Remove all usages of drupal_set_message and drupal_get_messages is turned into that followup. Everything mentioned in #41 is addressed so RTBC.
Thanks, @xjm, @kim.pepper, @tim.plunkett and @larowlan!
Comment #58
jibranPHPCS error.
Comment #59
kim.pepperFixes #58
Comment #61
markhalliwellOne tiny trivial request:
Considering that getting/setting messages can and will still likely be used in a lot of procedural code, can we add a static
\Drupal::messenger()helper here that does the same thing as this code? This is really no different than what was done for\Drupal::logger()and will help reduce the need for "hasService" and type hints elsewhere.Comment #62
dawehnerI think I agree with @markcarver. Messages are just like a logger also not really worth to unit test, if you ask me.
Comment #63
kim.pepperAdded
\Drupal::messenger()Comment #64
kim.pepperRealised I didn't address the 'hasService' check comment in #61.
Since we will always have the service now, do we actually need this? Is
Drupal::messenger()enough?Comment #65
kim.pepperI updated the change record to refer to
\Drupal::messenger()in the examples.Comment #67
markhalliwellI'm not entirely sure. This logic is only in
drupal_get_message()and from what I can deduce of #2278383-80: Create an injectible service for drupal_set_message(), this was only ever added as a workaround for Drush.Based on the above comment, it was removed from
drupal_set_message(), but notdrupal_get_message(). Perhaps it was simply overlooked, or perhaps it's still needed and is specific to onlydrupal_get_message()(a proceedural function used by Drush).It's definitely not needed in
\Drupal::messenger()though, so current implementation here is fine.---
One more tiny nit: until #2908026: Remove killswitch from LegacyMessenger actual lands (which will likely be after this), the injected
page_cache_kill_switchservice should still be kept.Comment #68
kim.pepperThis was discussed already in #32
Comment #69
kim.pepperI manually tested successfully using this patch using drush site-install.
Comment #70
jibranThanks! @kim.pepper for answering #67 back to RTBC.
Comment #71
markhalliwellYes, I am aware of that. I failed to explain why. Here goes:
This use of a, currently, vital service in such a potentially high traffic method (addMessage) is quite simply wrong.
The primary reasoning behind doing this was centered around the idea of "we wouldn't have to make any changes to public API".
IMO, this is a very weak argument and kind of defeats the whole point of using services in the first place.
MessengerInterfaceis actually the public facing API, notMessenger.MessengerInterfacedoes not contain a defined__construct()method (like all other services) for this exact reason: to utilize new or different services should the need arise.The reality is that, yes, we're hoping that this kill switch will also be removed before 8.5.0.
If it does, then great, we're not breaking any BC code because this entire API was created in 8.5.x.
If it's not, then we will have to ship with the kill switch and simply deal with that reality.
C'est la vie. We shouldn't sacrifice proper code on the hopes and dreams that something "may land" in an entirely separate issue.
edit: Even that wouldn't be a BC break though because we wouldn't be removing anything from
MessengerInterface, so we'd be fine.I imagine that is because
drupal_get_message()is still using the previously introduced ternary\Drupal::hasService()condition, not the new\Drupal::messenger().Comment #72
xjmAbove discussion makes me wonder if we should profile this?
Comment #73
xjmUnrelated nitpick: Can we please remove all the comma splices? "Deprecated in 8.5.x, will be removed before 9.0.0" isn't quite grammatically correct in English. It can be made correct by replacing the comma with the word "and". :)
Comment #74
xjm@markcarver, can you clarify what, specifically, you are suggesting to change here? We weren't quite sure what the issue was NW for. Thanks!
Comment #75
kim.pepperYes, you are right. I removed the check, and it does fail.
Discussed with @larowlan who suggested an in-memory MemoryMessenger, which only stores messages in memory for early bootstrap when the service doesn't exist. Had a first go at this.
Also, happy to inject KillSwitch if there are no issues with BC breaks.
Fixes @xjm comma splices too.
Comment #76
kim.pepperSuccessfully manually tested
drush site-installwith the above patch.Comment #77
jibranOther than this change it looks ok to me. Can we add tests to replicate
drush sifail?Comment #78
markhalliwellI was specifically referring to my review in #67 about prematurely removing the dependency injected kill switch service which is actually what #2908026: Remove killswitch from LegacyMessenger is for.
I don't think this actually needs to be profiled. Having a ton of messages added in a single page request isn't very common, but it theoretically could happen. An example, while a poor design for sure, is a batch operation affecting 1000s of objects that also adds a message for each item. I doubt that would ever happen in core.
Regardless, it was less about performance and more so just ensuring that we use existing and proper coding paradigms: services that depend on other services are injected; they don't call
\Drupal::service()statically.---
Almost there :D
drupal_set_message()should also use\Drupal::messenger()now as well.This gets a little tricky here I think.
On one hand, this is creating a new
MemoryMessengereach and every time this method is invoked, thus eating any existing object. It needs to be statically saved.On the other hand, because this could potentially be called before the container is available and is set using
MemoryMessenger, what happens if the container suddenly becomes available after it has been properly initialized in the bootstrap process? The existing messages in memory are lost. I think these messages need to be merged into the service if there's an existingMemoryMessengerobject.This is a rough draft code rewrite (written here):
---
AFAIK, core's policy is not to officially support 3rd party tools like Drush. I don't think there needs to be specific tests around drush specifically, but yes I think we could test using
\Drupal::messenger()and adding messages before and after the container has been initialized to ensure that both can be retrieved at the end.Comment #79
jibranInstead of introducing new Memory messenger how about we do this. Interdiff is from #63.
Comment #80
markhalliwellNo, this still doesn’t address the problem of losing messages when the container finally initializes. The problem isn’t MemoryMessenger, but rather how to handle the static method when it’s invoked before th container is initialized.
Comment #81
jibranYeah, I ignored the case in #79 when container is not available. Can we please move this discussion in the follow and not deprecate
drupal_get_messagehere. This whole discussion is correct but seems out of scope.Comment #82
kim.pepperI assume we are just dropping these messages in head now?
Comment #83
kim.pepperDiscussed with @larowlan (I need to stop saying that) and came up with the idea of a ChainedMessenger which would abstract the logic out of \Drupal::messenger().
The chained messenger would basically have 2 messengers, and have a method with the following logic:
Comment #84
markhalliwellTechnically, yes. But this is likely only happening for 3rd party services that are calling the procedural
drupal_get_messages()anddrupal_set_message()functions before actually instantiated the container (i.e. drush). Core itself is most likely "fine" since it relies on a fully instantiated container.drupal_get_messages()does absolutely nothing if there is no service anddrupal_set_message()would actually throw an error if there is no service just by attempting to retrieve it or at the very least when attempting to invoke a method on NULL.Cool :D Sounds like a good idea given the complexity.
Comment #85
markhalliwellUltimately, MemoryMessenger and ChainedMessenger both are just boilerplate code for not breaking BC. In the future, we won't need either because the
drupal_get_messages()anddrupal_set_message()procedural functions will have been removed and the code using them will be forced to properly instantiate the container to use the normal Messenger service.Comment #86
kim.pepperOK here goes.
Added a ChainedMessenger as per above.
I also abstracted out a few methods to an abstract BaseMessenger class.
Again, manually tested locally with
drush site-install.Comment #87
larowlanWe should mark this as @internal
We should mark this as @internal
Same here
Comment #88
kim.pepperFixes #87
Comment #89
jibranThanks, @larowlan and @kim.pepper for coming up with the new solution. The changes look good just some minor observations.
I think we should also mention to use messenger service for all the code run after bootstrap and never use this service.
Let's call it $messengerService.
Can we uninject(eject is the right word I think) this as per #32?
Comment #90
markhalliwellNo. See #71/#78.
Comment #91
markhalliwellThis needs to now be using
\Drupal::messenger()as well.Comment #92
kim.pepperFixes #89 1. and 2.
Fixes #91
Also moved some more duplication into the base class.
Comment #94
markhalliwellI think that broke things. It's only a few lines of code. Do we really need a
doAddMessagemethod just for determining if a message should be added?Comment #95
dawehnerMh, I don't think this is useful. Messages are almost exclusively sent on POST request where we don't even have render caching enabled.
Could we still do some kind of warning, because ideally this case really should not happen, right?
Do you mind using a strict
in_arrayhere?I think we should have a test which ensures in a unit test that copying the messages over actually happens.
Comment #96
kim.pepperRe: #94 reverted those changes.
Re: #95
Comment #98
kim.pepperI suspect this change is causing the fails, but no idea why.
Comment #99
markhalliwellI think the problem is that these tests are ensuring that the status messages are visible. Could it be because of
trigger_error()? I know the tests are ignoring these, but that doesn't mean the messages are actually being added does it? I know there's a@preceding it to prevent execution halt, but I don't know how reliable that really is. Does the rest of the code in the function actually get executed?Regardless, I don't think this issue should be deprecating any code until core actually stops using these procedural functions. This issue should really just be about adding a real service so core can actually use it.
If anything, I think the
trigger_error()bit and the@deprecatedcomments should ultimately be added as part of #2924538: [META] Remove all usages of drupal_set_message and drupal_get_messages.Comment #100
markhalliwellP.S. I think there should be a test added that actually checks that
ChainedMessenger::getMessengerreturns the proper class based on whether the container has been initialized or not. The test added for merging messages is manually constructing the classes, butChainedMessenger::mergeMessagesshould really be internal and only happen as part ofChainedMessenger::getMessenger.Comment #101
kim.pepper@xjm asked for this in #39 and #41.
Comment #102
markhalliwellI'm well aware that it was asked for, but that is only because
@deprecatedwas already added to the DOXYGEN block.I'm just saying that we shouldn't deprecate anything at all in this issue right now.
It's rare and generally bad practice to deprecate something that is still technically being used internally.
Most deprecations happen when core switches to the new implementation and no longer uses deprecated code, i.e. what will happen in #2924538: [META] Remove all usages of drupal_set_message and drupal_get_messages.
Comment #103
dawehnerAt least for me #2924538: [META] Remove all usages of drupal_set_message and drupal_get_messages might take a long while, so it might be better to have the deprecation being there, so contrib modules can adapt already. On top of that it is a bit weird that we should do something different here than on all the other deprecation issues.
Comment #104
larowlan@mark.carver - @xjm asked for the deprecation and trigger_error here
Comment #105
jibranInstead of injecting this we can have a private setter for this which can be removed once it is not needed anymore.
Comment #106
larowlan@xjm are you happy to forego the deprecation/trigger_error until we've removed all usages?
Comment #107
markhalliwellNo. This is a service that, currently, depends on another service. Please see #71 and #78 for more detailed explainations.
Comment #108
kim.pepperI'm posting a patch with out the
Drupal::messenger()change in drupal_set_message() to see if its the cause of the fails.Comment #110
kim.pepperRe-roll of #108
Comment #111
markhalliwellI still think the 2 test failures in #96 are because those tests and
trigger_error()don't mix.drupal_set_message()should definitely use\Drupal::messenger().Could you remove the
@deprecatedverbiage andtrigger_error()to see if that passes?If it does, I think we should just add those as part of #2924538: [META] Remove all usages of drupal_set_message and drupal_get_messages.
Comment #112
xjm@markcarver, the
@trigger_error()is suppressed with an@, so it should never have any effect except for tests using the PHPUnit bridge. https://www.drupal.org/pift-ci-job/820652 shows two failures: one inInstallUninstallTest, and another inExperimentalModuleTest.InstallUninstallTestis a simpletest web test, so I'm pretty sure the fails in it cannot be coming from the PHPUnit bridge. The message is also being added to the list of ignored warnings, so it should not raise a fail in PHPUnit tests using the bridge either.It'd be simple to prove whether the
@trigger_error()was causing fails: roll a version #96 without the@trigger_error()for testing, post it, and see if it passes. I suspect it still won't.This should not be committed without the
@trigger_error(). If somehow that was what was causing the fail, that'd indicate a deeper problem that we'd need to address.InstallUninstallTestis unfortunately a bit of a nightmare to run locally, butExperimentalModuleTestshould be less painful. I'd recommend running the test with #96 locally and seeing what's actually in the verbose output on the steps right before the fails, and possibly stepping through it in a debugger if that doesn't work.Comment #113
xjmOops, forgot to unassign. Answer to #106 is "No, keep the
@trigger_error()and deprecation, and debug why the tests were actually failing." :)Comment #114
dawehnerI debugged why the test originally failed:
The tests installed the "experimental_module_test" module, which triggers the confirm form.
\Drupal\system\Form\ModulesListExperimentalConfirmFormis then submitted.As part of building the form before the submission on the POST request
\Drupal\system\Form\ModulesListExperimentalConfirmForm::buildMessageListis called (which calls out to drupal_set_message()).After that the actual form is executed (
\Drupal\system\Form\ModulesListConfirmForm::submitForm) which rebuilds the container.When we use
\Drupal::messages(), we effectively has a static, which survives container rebuilds, so the message from\Drupal\system\Form\ModulesListExperimentalConfirmForm::buildMessageListsurvives the module install and container rebuild.Note: you can totally reproduce the second failure by simply installing the experimental test module with the patch applied.
I think overall having a static in
\Drupalsmells really dangerous. What we should rather do is to solve the problem limited to our usecase. Ideally we would rather have something which is not a real static. I though about doing something like:but this did not solved it.
Comment #115
markhalliwellOk, I've been debugging this and this is the code that is causing the test failures:
Basically, if the container gets rebuilt (thanks for pointing us in the right direction @dawehner!) then it doesn't merge in any messages from MemoryMessenger.
I think I've got a fix and will upload the patch shortly.
Comment #116
markhalliwellComment #117
kim.pepperThanks Mark!
This will create a new MemoryMessenger each time getMessenger() is called? Wouldn't messages be lost potentially?
Comment #118
markhalliwellHeh, you're right! Needs to be wrapped in
isset(). I'll upload another patch shortly with this change.edit: after test results come back
Comment #119
markhalliwellYay #116 passed!
Here's a change that fixes #117 and adds some better test coverage for determining which class is returned in
ChainedMessenger.Comment #120
kim.pepperLooks great! RTBC if green.
Comment #121
markhalliwellI suddenly realized that the
MemoryMessengeris actually the wrong approach since it does not accurately provide 100% BC functionality.If messages are added/retrieved between requests (prior to using the service), then they are lost. Despite current implementation, this is not the expected behavior and
LegacyMessengeractually broke this aspect of the BC code.Also,
FlashBaguses$_SESSION['_sf2_flashes']which is different from what core used to use:$_SESSION['messages'], soFlashBagcan never be a 100% BC replacement.3rd party code (like Drush) can and does included
bootstrap.incto utilize thedrupal_set_message()anddrupal_get_messages()procedural functions, prior to instantiating the container (e.g. creating a new site before a container exists).If there is ever a new request, messages are lost because they're not using the previous stable method:
$_SESSION['messages']which can persist across requests.I raised this in Slack and I have to steal a line from @kim.pepper and say that I discussed this with @larowlan :D
MemoryMessengertoLegacyMessenger- edit: reuse existing name and clone the previous$_SESSION['messages']implementation from proceedural functions@internaltags on the BC messenger classes to@deprecated(they only exist to provide BC capabilities)@trigger_errorwheneverLegacyMessengeris used so we can start tracking who is actually using messages prior to a container being built (which may affect what we do in 9.x).Comment #122
markhalliwellI meant to assign this to myself.
Comment #123
markhalliwellActually, we may as well reuse
LegacyMessengerinstead ofPreBootstrapMessengersince that is basically what this is and it reads better.Comment #124
markhalliwellHere's a patch that reworks the existing
LegacyMessengerclass to mimic core's previous$_SESSION['messages']code (#121). I also went ahead and removed a couple things:@internalfromMessengerInterfaceandMessengeras was originally intended per: #2278383-230: Create an injectible service for drupal_set_message()BaseMessenger, this really didn't add much benefit given that it would have been deprecated (used for BC) and removed eventually anyway.Comment #125
dawehnerWell, now I could imagine that we no longer need the static on the Drupal class to make it working?
Comment #126
markhalliwellYep
Comment #127
kim.pepperNot sure we can do this? From what I can tell, you're going to get a new
ChainedMessengereach time you call\Drupal::messenger(),which will create a newLegacyMessengereach time, up until the container is available.Comment #128
markhalliwellYes and that's fine because this will always reference
$_SESSION['messages']now which survives and is kind of like its own static.Comment #129
jibranIsn't the order of messages important?
Why don't we need a kill switch in this method?
Comment #130
jibranRE: #127 We could make
ChainedMessengera single instance class just like\Drupal\Core\Site\SettingsComment #131
kim.pepperRe #127 Ah ok. Makes sense then.
Re #130 We have
\Drupal::messenger()as our single instance class. We really want people to injectMessengerInterfacegoing forward.Comment #132
markhalliwell#129.1: Yes and they're added in the order the were originally added.
#129.2: Because
LegacyMessengerwould only ever be used when there is no container available, which is required to actually use the kill switch. This class only exists to temporarily store messages until a container can be instantiated and then it switches over to it.Comment #134
markhalliwellSo by changing
$messenger = new ChainedMessenger();to$messenger = \Drupal::messenger();inChainedMessengerTest, the test fails.\Drupal::messenger()needs to be statically cached because otherwise thelegacyMessengerproperty inChainedMessengerwould always beNULLand thus never transfer the service (#127 is correct).Reverting that change and updating test to include testing
\Drupal::messenger().Comment #135
markhalliwellDo we want to be a little more explicit with this error message? Maybe something along the lines of:
You have attempted to add or retrieve a message prior to the container being initialized. This functionality was deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. Please use the "messenger" service or report your legitimate use case at https://www.drupal.org/node/123456.Obviously, we'd need to create a new issue to track these but it may be worthwhile considering that we really don't have any clue who is doing this aside from Drush. Then we can re-evaluate this functionality before actually removing it.
Comment #136
dawehnerThe alternative way to fix this is.
I still try to understand why we need to support pre container messages. I get that we don't want to fatal, no question at all, but why can't we follow the strategy of HEAD aka. ignore messages? Dealing with messages, which are inherently session related, feels utterly wrong, as long we don't have a real session initially yet through our session system (see
\Drupal\Core\StackMiddleware\Session).#2760167-75: Add \Drupal\Core\Messenger\Messenger introduced that, so I'm wondering whether this direction was maybe a bit to drastic.
Comment #138
markhalliwellBecause the procedural functions
drupal_set_message()anddrupal_get_message()have existed for 14 years and have been heavily used by core, contrib and 3rd party alike.These aren't some obscure functions that we can "fudge" with BC support. We know, for a fact, that Drush uses these functions prior to a container actually being available (i.e. creating a new site).
The reality is: we don't really know the extent of who or what uses these functions prior to the container being built/initialized because these functions never made the OO cut before 8.x was released. Thus, we will need 100% BC compliant code until we can answer this definitively and figure out a path moving forward in 9.x. This is why I raised #135.
Because HEAD is wrong. #2278383: Create an injectible service for drupal_set_message() introduced "ignoring messages" which breaks 100% BC. This issue needs to fix that.
---
$this->legacyMessengerwill always beNULLhere because\Drupal::messenger()is creating a newChainedMessengerinstance each time. This is what @kim.pepper was saying in #127 and I misunderstood.On a side note, this why I had originally intended the
ChainedMessenger::getMessengercode to be in\Drupal::messenger()(#78). Keeping track of this static relationship between these two classes is confusing and requires thatChainedMessengeradds more unnecessary LOC to become a "proxy".I really think that we should just remove
ChainedMessengeraltogether, which the exception ofChainedMessenger::getMessengerwhich should be moved into\Drupal::messenger. I don't see the benefit of having a dedicated class for this process.Comment #139
markhalliwellI think removing
ChainedMessengerentirely will help clear up some of this confusion regarding the static.Comment #140
markhalliwellHm, maybe if we just move the code in
::getMessengertoLegacyMessenger, this would satisfy the "want for a separate class" and the static issue both.Plus, this code will just disappear when
LegacyMessengeris finally removed, which makes more sense.I'm also adding the related issue for tracking usage of
LegacyMessenger.Comment #141
markhalliwellOk, see if this makes more sense.
Comment #142
jibran> 80
Comment #144
markhalliwellIt's a @todo. It really shouldn't matter and IDEs like PHPStorm don't continue the @todo styling to newlines.
edit: sigh, it'll fail the coding standards automated tests though. this kind of stuff is ridiculous.
Comment #145
markhalliwellComment #146
markhalliwellComment #148
jibranPlease see https://www.drupal.org/node/1354#todo
Comment #149
markhalliwellNot the point I was making...
Comment #150
markhalliwellRe: #136
Yes, prematurely creating a
$_SESSIONvariable causes issues. It's why the patch in that comment, #146 and #141 all failed: it couldn't retrieve the proper session instance in other tests.Previous patches didn't have this issue because it only did this when the service wasn't available. Thus, I've moved this code from the constructor to just below the
@trigger_error()inLegacyMessenger::getMessenger()and added more documentation around why. This still allows us to avoid putting a static in\Drupal::messenger().This patch should now pass.
Comment #151
markhalliwellI lied. This isn't true and
\Drupal::messenger()will require a static if we want to transfer messages properly... sigh.Comment #152
markhalliwellComment #155
markhalliwellSeems the testing CI had a quite a few drinks tonight...
Comment #156
markhalliwellIt would seem that it's not re-triggering, so here's the same patch.
edit: note to self, there's an "Add test / retest" link underneath the patches, even if they're not on the PIFT results page.
Comment #159
kim.pepperThis should really be LegacyMessengerTest now all the logic is in there.
Comment #160
markhalliwellSure, that makes sense.
Comment #161
markhalliwellMinor c&p doc fix. Adds an explanation of why the static is needed and a @todo indicating that the static should be removed once
LegacyMessengerhas been removed.Comment #162
markhalliwellOne too many "proper"...
--English in the AM
Comment #163
kim.pepperThanks Mark. The IS needs an update to explain what the proposed solution is now.
Comment #164
markhalliwellIS updated. Patch is ready for review and RTBC if nothing glaring is found.
Comment #165
kim.pepperLGTM!
Comment #166
larowlanIf we're checking for isset, shouldn't we be setting this to NULL, also ensure's we're cleaning up the $_SESSION value too?
I think we should unset here too, because an empty array inside an array isn't considered empty.
Comment #167
markhalliwellAddresses #166 as well as being a little more explicit on when to use the the previous
$_SESSION['messages']method (to avoid accidently creating a session).Comment #168
kim.pepperAddresses #166 so back to RTBC
Comment #170
kim.pepperRe-ran the tests and they passed. Some weirdness with CI bots I think?
Back to RTBC
Comment #171
larowlanThis is so close, one nit and one api surface issue
Are we sure we want to make this public?
Looking at the code the only place its called is from tests.
On that basis I think we should make this protected, and use reflection in the test to make it accessible.
A smaller API = easier to maintain
nit, two .
Comment #172
kim.pepperFixes for #171
Comment #173
markhalliwellAddresses #171
Comment #175
markhalliwellWhoops, forgot to change the
@coversmethod too.Comment #176
kim.pepperAddresses #171 to back to RTBC
Comment #178
markhalliwellSigh, #175 passed, random test bot change again.
Comment #180
markhalliwellThis is getting ridiculous...
Comment #181
larowlanAdding review credits
Comment #183
larowlanCommitted as 40856cc and pushed to 8.5.x.
Published the changed record.
Unpostponed followups.
Thanks again for all the sustained effort here, glad to see we're still moving with these modernisation initiative.
Comment #184
alphawebgroupcould someone explain, please: why we have named "pulling out messages" methods as "deleting messages"?
I believe, it will mislead developers, especially new developers who is just started working with Drupal
usually "deleting" means "deleting". it usually returns either
$thisor void.but our "deleting" does "pulling out", not a "deleting" in common sense.
Comment #185
dawehnerCan someone please open up a follow up to get rid of this static? I am kind of a strong believer that this static will hurt us rather sooner than later.
Comment #186
markhalliwellThe static for
LegacyMessengeris needed. I'm curious how this would "hurt us" given that this is only for BC purposes and is, ultimately just proxied to theMessengerservice.If there is no
$_SESSION, it uses an empty array. This behaves just like the previousMemoryMessengerand requires that\Drupal::messenger()persists. Simply callingnew LegacyMessenger()each time would blow away any previously set messages (and would now fail tests that were added here).There is no risk with statically storing
LegacyMessengerbecause the messenger service itself is always retrieved and never stored statically. Otherwise, it would never pass tests (as was proven above when you dug into why they were failing on the form submits).Comment #187
markhalliwellI created the follow-up though, so we can investigate this further if you really feel so strongly about it.
Comment #188
almaudoh commentedGreat work going on this issue. It's nice to have drupal_{set|get}_message() "OO'ified". Even though this is coming late in the day, but I've observed something:
The
$repeatflag is not propagated to the::addMessage()method calls in the simplified versions::addError(),::addStatus()and::addWarning(). This also implies there is no test coverage for adding repeat messages.Comment #189
larowlan@almaudoh - thanks can you open a new follow up to resolve that? nice catch
Comment #190
kim.pepperI created #2931598: Messenger methods drop repeat flag
Comment #192
heddnMaybe I missed a follow-up issue, but I don't see an @deprecated and @see to the CR on drupal_set_message/drupal_get_messages. Do we need to open one?
From the CR:
Comment #193
heddnIgnore that last comment. Needed to switch branches.
Comment #194
kim.pepperTagging