Problem/Motivation
When running tests with Flag enabled, the views hook tries to get a session for the current user. But if the tests are running cli, getId() tries to create a fake session.
[PHPUnit\Framework\Exception] session_id(): Cannot change session id when headers already sent at ../../vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php:94
Steps to reproduce
Create PHPUnit tests with Flag enabled.
Log a user in programmatically.
Operate on entities that flag is attached to.
Proposed resolution
Check that there's an session before asking for the id.
Comments
Comment #3
minoroffense commentedComment #4
minoroffense commentedComment #6
mrweiner commentedJust SEO purposes for people looking for this error, it comes from flag_views_query_substitutions(). The error that users have been reporting on our production site is "Warning: session_id(): Session ID cannot be changed after headers have already been sent in Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy->setId() (lin 94 of ../vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php"
Comment #7
mrweiner commentedComment #8
jaapjan commentedI noticed that after applying this patch I saw this error in one of my tests in which I execute a view.
It appears the session does not exist yet in this case. I noticed that Drupal core PrivateTempStore calls the protected function startSession before trying to retrieve the session. See also related issue: https://www.drupal.org/node/2865991. I'm wondering.. should we make sure the session always exists?
Comment #9
mrweiner commentedYea, actually, I ran into this with a view yesterday as well. I just returned an empty array if there was no session, but your suggestion seems like a better approach. Just updated the MR to ensure the session exists. I wonder if this is something that needs test coverage? I don't have a ton of experience writing tests, though, so not sure of the best way to test it.
Comment #10
jaapjan commentedI've tested the latest patch and it seems to be working fine for me. I haven't tried to use Flag with anonymous user by the way.
Comment #11
franzI noticed this issue shows up when using a one-time login link that eventually triggers cron. Potentially running cron as anonymous (i.e. drush) would cause similar issue? Here is the stack:
Comment #12
mrweiner commented@franz it does look like FlagService::populateFlaggerDefaults() calls $this->sessionManager->getId(), which I do think is the same issue solved in the MR elsewhere. Your trace includes that, so it must get hit during cron one way or the other. It looks like it could be slightly more complicated, though, as there are a couple of other methods (commented in populateFlaggerDefaults) that are supposed to "ensure that the session exists." I'd wager the handling from this MR would need to be broken apart through those methods.
Comment #13
casey commentedPlease ignore. This is a manual copy of the latest MR (https://git.drupalcode.org/project/flag/-/merge_requests/8/diffs.diff?di...); We use cweagans/composer-patches but don't want to link to gitlab MR patch urls as their content can change.
Comment #14
casey commentedComment #15
casey commentedThe flag module should be refactored so it no longer uses \Drupal::service('session')->getId().
Change record: https://www.drupal.org/node/3006306
Comment #16
casey commentedComment #18
casey commentedThe failing test is because the patch no longer uses the actual session id. Not sure how to fix the test.
Comment #19
berdirYou have the session ID, so you could read the value from the sessions table?
Comment #20
berdirAny chance that you can find to look into updating the test soon? I'd like to roll a new 9.3 compatible release asap. I'll try to find some time myself if not.
Comment #21
berdirwill have a quick look at this.
Comment #22
berdirWell, it's not pretty because PHP session data is annoying, but I think the partial regex there should be fairly safe, and if the session data serialization ever changes then we'll deal with it.
One thing I'm a bit concerned about is that this will initialize a session all the time, even if you don't use anonymous flags. Will have a look at that, as a starting point, we could check permissions and if anonymous flagging isn't allowed, then we don't return a placeholder value.
Comment #23
berdirCloser look at the patch, it's probably OK, while we do generate such a session ID every time that function is called, only the ensureSession() method really does persist it into the session.
made some documentation improvements, also ensured that the function returns the same value if called multiple times, otherwise the property that it defines seems useless.
Comment #25
berdirCommitted.
This will break existing anonymous flag listings on deploy, dealing with that would be quite hard.