Download & Extend

Switch to Symfony2-based session handling

Project:Drupal core
Version:8.x-dev
Component:base system
Category:task
Priority:major
Assigned:Unassigned
Status:active
Issue tags:symfony

Issue Summary

  • As part of the WSCCI initiative, it has been decided that we should use the Symphony2 HTTP Foundation Library (#40). That library comes with a session handler that we could build upon instead of rolling our own.
  • The session library comes with an equivalent function to drupal_set_message() ("flash"). We need to decide if we can use that, get it refactored upstream, or not use the session handler at all.
  • We could also just use their SessionStorage interface, and write our own Session class. This looks to be the way to go as of #50.
  • Since using the Session classes requires HttpFoundation, this issue has been marked as postponed pending the resolution of #1178246: Add Symfony2 HttpFoundation library to core
  • Do not repurpose the $_SESSION global unless it would work *perfectly* to expectations and behavior of general language usage and not impact performance. Otherwise layer a session object over it for use by Drupal. (#31, #32, #47)
AttachmentSizeStatusTest resultOperations
session_uid.patch3.46 KBIdleUnable to apply patch session_uid.patchView details | Re-test

Comments

#2

I agree that it would be a good idea to refactor session management. Two remarks I made on the IRC:

- why not refusing to save the session altogether if session_uid != user->uid ?
- -1 for yet another global variable

And now that I think about it, I guess it would be nice to make that session management self-contained.

#3

well then I either make a static get/set, which i would not like or i simply make sessions into a singleton object and move user_authenticate inside so that you have absolutely no ways to change the session uid aside from providing the proper name and password together. That would be bliss. Coming down the line, we could also provide a drupal_session()->user() instead of global $user.

#4

It is sometimes necessary to log a user in programmatically. Login tickets or URLs such as our current password reminder system come to mind. A dedicated setter would allow this, while making it much harder to accidentally login a user.

#5

Further decision with Heine (and summing up the issue): we will

  1. Make it easy to change the user. Just make drupal_session()->user public. Or we really want, we can make a get-set but hardly necessary.
  2. We wil keep a drupal_session()->uid, initialized in sess_read.
  3. Refuse to write the Edit:usersession if self::$uid != self::$user->uid so that if you want to save the user, then you need to explicitly set the uid as well. If you accidentally change the user object, it wont be saved.

#6

The next step would be to create a "Drupal" object with static methods (ie. the equivalent of a singleton) that will:

- have a Drupal::user member variable
- handle sessions
- handle bootstrap and configuration
- store pointers to other subsystems (Theme, Menu, etc.) or, as a transition, store variables for other subsystems (Drupal::theme, Drupal::theme_key, etc.)

#7

yes for Drupal:: static class instead of a singleton! and we can start with just the user/session stuff. Edit: Maybe Session:: instead? or Drupal_Session:: ? Dont forget, you cant write a class in two includes (unless you include one from the other, meh).

#8

I'd been working at this thus far as a singleton, but I can see an argument for that being overkill. I'll shift my efforts towards just making it a static class, and only use a singleton there's some real reason to do so.

#9

Errr...scratch that. I misread, and I'm tired. I can't see how a purely static class helps us here, so I'm going singleton.

Sorry. Need sleep.

#10

In working with this, I made a nice little discovery - not only can you point session_set_save_handler() to methods, but you can point it to PRIVATE methods and it (can) still work just fine. Which means our session handling can enter a whole new level of locked down. Calling something like session_destroy() from userland still gets hit with method visibility, as it should, but it means for some of the session handling functions that were previously marked in their docblocks as "Don't call these directly," it's now impossible to call them directly.

So I've taken the step of moving a fair bit into that session singleton, and have done my best to adhere to the general principles laid out already in this thread. There are obviously some hacky bits in there - we still need a global $user, and I've commented out the direct call to session_write_close in drupal_goto for the userland reason above. And those hacky bits, among others, are probably responsible for the few hundred failed tests I saw on a full test run I ran locally. And it errored out fatally since the file test called drupal_save_session() which I left out as chx did in his original patch. And we need some kind of other approach to doing that, if we use the singleton.

But I didn't want to do any more until I posted here and got some feedback on the general direction.

AttachmentSizeStatusTest resultOperations
session_singleton.patch19.55 KBIdleFailed: Failed to run tests.View details | Re-test

#11

Status:needs review» needs work

The last submitted patch failed testing.

#12

Honestly I'd rather go with an instantiated singleton than a static class. Static classes are touchy in PHP 5.2, because of the lack of late static binding. An all-static class is also in practice little more than a clumsy namespace. I know we are using one in DBTNG, but I'm actually not too happy with that either and there are legit reasons for it. (Mostly relating to it removing about 300 function calls that way, and making code simpler.)

The best of both worlds would require lazy-instantiation of static class properties, but sadly that didn't make it into PHP 5.3. :-(

Oh yeah, and subscribe. :-) I'll try to review this later.

#13

and we hardly need more than a clumsy namespace :) but I am fine with the singleton just the static class looked like fast, saving a function call here and there... Session::$user has no function calls, drupal_session()->$user has one, and the worst is drupal_session()->get_user() which is two so let's avoid the OOP zealot get/set :P

#14

Cool, I'll reroll later tonight, and start to put an eye to some of those test failures. I don't think we're in for any big surprises, but there is the matter of a few hundred failing tests that needs to be addressed...

#15

OK, here's the next crack at this. Some quick notes:

  1. This patch is actually pretty close to working, now, whereas the last one was more like a skeleton/suggestion. The only outstanding issue I'm aware of right now is that the session persistence test fails. I do want to get the testbot's feedback though, b/c I have other things to do with my system :)
  2. Although there's clearly a lot of different logic, this patch mostly abides by the same flow that the old procedural approach did. We may want to refactor that, but I figure, one step at a time.
  3. There are some private properties in there that indicate the state of the session managing singleton. They're not really serving a purpose right now, but could, if some refactoring is done.
  4. I did a quick grep+sed to replace all the instances of global $user in core. I'd have left it until later, but it turns out a problem I banged my head against for four hours was because I was trying to leave 'legacy' support in there for global $user. So I stopped trying and it went away :)
  5. There are interesting possibilities with making the Session class abstracted, pluggable, etc...but as I discussed with chx, that's down the road and we should get this bit licked first.

Yar!

AttachmentSizeStatusTest resultOperations
session_15.patch61.17 KBIdleFailed: Failed to apply patch.View details | Re-test

#16

Another iteration, here. Added a bitmask to indicate session state, cleaned up some variables, stripped out some global $user and $GLOBALS['user'] calls that I missed, etc. Paved the way a bit more for making this pluggable...or at least, it feels like I did in my head :) Can't really tell if it's reflected in the code. Incorporated the different function ordering introduced by the commit of #280934: Use httponly cookie support when available. Still getting the session propagation error on running the session tests, and though I haven't really tried to understand it, it's leaving me befuddled on a couple levels.

We're gettin closer!

chx, sorry, but I don't see a compelling argument for making this a static class. drupal_session()->get_user() is gone (rightfully so), but I have difficulty believing that the speed difference would be non-negligible. I'm certainly open to some proof to the contrary. But, while I do agree with Crell - it's a class, not a namespace, and I don't see what we're gaining by pretending otherwise - I also just don't care that much. So basically, I'm going the path of least resistance, and will continue on with other aspects of this patch while more enthusiastic folks fight that one out.

AttachmentSizeStatusTest resultOperations
session_singleton_335411-16.patch69.96 KBIdleFailed: Failed to apply patch.View details | Re-test

#17

Do not use Private. Use Protected. That allows us to still have an "out" by extending the Session class and mucking with stuff. "Private" access modifiers should never be used.

Docblocks are a bit off in places. Even for methods and class properties, the first line is limited to a single line sentence, and should be separated from any further comments by a blank line.

If we have a singleton object, I don't quite get why we need to prime it in BOOTSTRAP_SESSION, and mark it as such. Part of the benefit of a singleton is that it can be lazy-instantiated when it's first used, provided all access to it goes through the access method.

I think I'd rather use instance() or getInstance() for the singleton access method. get() just feels too generic to me, and may cause confusion latet with HTTP GET or __get() or any number of other things. In my own code I tend to use instance() I think.

Good stuff using @see. However, I'm pretty sure it has to be on its own line as a new docblock item, not inline with the rest of the text. (I may be wrong on that.)

The docblock for sessionClose() is incomplete and ends in mid sentence. :-)

I'm not sure why you're bothering with the bitflags and direct bitwise comparisons. The more common convention is to expose not a property (which someone else can then modify at any time) but a method, either a isOpen() that returns boolean or a checkState($flag) to which you pass the bitflag to check for. That lets you make $session->state protected, and safe from tampering from other parts of the code.

#18

Great, thanks for the review. Item by item:

Do not use Private. Use Protected. That allows us to still have an "out" by extending the Session class and mucking with stuff. "Private" access modifiers should never be used.

Yup, I stripped 'final' from the class declaration right before I generated the patch (the original parameters presented to me had it being final), but clearly neglected to switch over to protected. Particularly important if we're to make this subsystem pluggable; it's becoming increasingly clear as I work on this more that inheritance will be useful if we go that direction.

Docblocks are a bit off in places. Even for methods and class properties, the first line is limited to a single line sentence, and should be separated from any further comments by a blank line.

I've been playing a little loosey-goosey with the docblocks since I'm not 100% on some of what's in there; for those things I'm not sure of, I'm keeping the docblocks loosely informational. I'll improve em as I get more feedback.

If we have a singleton object, I don't quite get why we need to prime it in BOOTSTRAP_SESSION, and mark it as such. Part of the benefit of a singleton is that it can be lazy-instantiated when it's first used, provided all access to it goes through the access method.

Y'know, I started thinking of this while I was browsing around the other session-related patches, especially #201122: Drupal should support disabling anonymous sessions, and it's true, there's really no reason to initiate session operations unless something explicitly calls drupal_session(). However, as it's written right now, initiating sessions is a necessary precondition for having a current user right now. I'll refactor accordingly; I think there are some interesting possibilities, here...

I think I'd rather use instance() or getInstance() for the singleton access method. get() just feels too generic to me, and may cause confusion latet with HTTP GET or __get() or any number of other things. In my own code I tend to use instance() I think.

Works for me - I had getInstance() initially, I'll add it back.

Good stuff using @see. However, I'm pretty sure it has to be on its own line as a new docblock item, not inline with the rest of the text. (I may be wrong on that.)

Thanks! Yeah, I'm a fan of, y'know, using links. I hear it's what this interweb thing is good at. :)

The usage of @see in this patch is (very nearly) the one-and-only way that I personally think it's acceptable to be used quasi in-line. Doxygen defines @see as taking a 'paragraph' parameter - that is, it takes everything from the termination of the tag up to the next carriage return, and will parse ONLY the first argument provided to it as a function. For example, search for @see tags in the Panels css filter; from them, doxygen produces the fugly output you see in the docs.

Problem is, I forgot that you can't do this properly when using them in a doxygen list, because both look for a blank line to terminate the current item, so either you get too much in the @see section, or you screw up your list. Since you want to provide context in this case - that is, have the link be close to the list item it refers to, it's best to just do it without the @see tag there since doxygen and api.module both automagically transform function names into links anyway.

The docblock for sessionClose() is incomplete and ends in mid sentence. :-)

Yeah. See earlier comment :)

I'm not sure why you're bothering with the bitflags and direct bitwise comparisons. The more common convention is to expose not a property (which someone else can then modify at any time) but a method, either a isOpen() that returns boolean or a checkState($flag) to which you pass the bitflag to check for. That lets you make $session->state protected, and safe from tampering from other parts of the code.

These are two separate issues, one being whether or not bitwise operations are appropriate for internal use in the class, and another for how the state bitmask is presented to the user. For the former, it's clearly a yes: this is a classic case where there are multiple separate components to the overall state, where the state of each flag has varied relationships with the state of the others. Now, I can't think of a use case where anybody's going to really, really need to use these - but I'd rather have them there and be crystal-clear accurate for when someone does come up with something.

I agree, in part, about how to access it. We should take all reasonable steps possible to avoid requiring devs to learn bitwise operations in order to interact with any core subsystem. So some methods to that effect would be a good thing. But I don't know enough about how visibility works (and don't have time to check right now) to verify that what you're saying is accurate about protecting the var. I think the relevant question is: sure, the var is returned directly. Does that mean that if the caller asks for it by reference, they can change it and it will change the value in the class? Or is protection implemented in such a way where that won't happen - pass by ref on internal class properties just doesn't work?

#19

Variables are only returned by reference from a function/method if both the function is defined to do so AND the caller assigns by reference. However, there were places in the patch where you were doing:

<?php
if ($session->state & SOME_CONSTANT) { ... }
?>

I'm saying that's bad because you're directly accessing state, which means state must be public, which means anyone can mess with it. Instead it should be protected and you should have the following method:

<?php
class Session {
  public function
checkState($state) {
    return
$this->state & $state;
  }
 
// ...
}
?>

Make sense?

#20

Sure, it makes sense, but at no place in this patch do I actually DO what you're describing. There are several bitwise operations which are written in there, but they're all done within the class context. If there's an exception to that, it would be of the form:

<?php
$state
= drupal_session()->sessionState();
if (
SOME_BIT & $state) { ... }
?>

or maybe

<?php
if (SOME_BIT & drupal_session()->sessionState()) { ... }
?>

I don't think the latter form would be there because I've only just thought of doing it that way, but if you look at the method definition of Session::sessionState(), the reason for my concern cited in #18 should be clear. In any case, though, there should never be a case where the member is being directly accessed in global context. If there is, it's a typo - c'mon now, give me some credit! ;)

Regardless, the checkState() method you've suggested there is clearly the way to go, and the way I'll do it in the next iteration.

#21

subscribe.

#22

Title:Security: do not allow uid changes accidentally» Refactor session handling (and Security: do not allow uid changes accidentally)
Assigned to:chx» Anonymous
Status:needs work» needs review

It's far better to simply wrap $_SESSION into our own object so that we can do:

<?php
$_SESSION
->user; // was $GLOBALS['user']
$_SESSION['myvar'] = 'myvalue'; // stays the same
$_SESSION->close(); // was session_write_close();
$_SESSION->regenerate(); // was drupal_session_regenerate();
// etc.
?>

After all, it makes little sense to have a drupal_session() singleton *and* a $_SESSION singleton at the same time.

As an added benefit, because $_SESSION is an ArrayIterator, we could manage to only save the session if it really was modified, thus completely avoiding a costly MERGE query during most page request.

AttachmentSizeStatusTest resultOperations
335411-refactor-session-handling.patch71.9 KBIdleFailed: Failed to install HEAD.View details | Re-test

#23

Status:needs review» needs work

The last submitted patch failed testing.

#24

Now THAT'S a great approach. Assuming it actually lets us wrap a superglobal in that way - I dunno if the test failure has anything to do with that. From a brief glance, the only drawbacks to the way you've written it now are that a) allowing different session handling classes via some factory approach isn't possible, aside from a pluggable session.inc (which I believe fails the definition of 'pluggable'). That ought to be an easy fix, though, if we want it. b) we do lose the capacity for lazy instanciation of the session object.

Although...hmm. I haven't played with ArrayIterator and ArrayObject enough yet, just the generalized iterators. But. Oughtn't it extend ArrayObject, then allow iteration via wrapping its contents in an ArrayIterator via IteratorAggregate::getIterator()? In any case, though, I really like the direction here, and am glad you posted it Damien, because I have NO time to do anything for the next two weeks, minimum :)

#25

Status:needs work» needs review

That should solve the installation issue (during install, drupal_init_language() is apparently called before session initialization).

AttachmentSizeStatusTest resultOperations
335411-refactor-session-handling.patch72.89 KBIdleFailed: Failed to install HEAD.View details | Re-test

#26

Status:needs review» needs work

The last submitted patch failed testing.

#27

Status:needs work» needs review

I would like to see another round with the testingbot to make sure the patch broke the install.

#28

Status:needs review» needs work

The last submitted patch failed testing.

#29

I am shocked that replacing $_SESSION with an object would actually work. To be honest I'm not even convinced yet. :-) What exactly is wrong with session()->whatev? Overriding $_SESSION breaks a PHP dev's expectation of what the superglobals do, which is bad DX unless there's a very good reason.

As sdboyer pointed out, a factory function makes it much easier to swap out implementations. Swapping out session.inc is a bad mechanism that breaks the registry.

Which leads me to my next comment: This should be implemented as DrupalSessionInterface and core provides a DrupalSessionDatabase that implements that interface. Other code can then implement alternate implementations of DrupalSessionInterface, possibly by subclassing DrupalSessionDatabase or possibly just implementing the same interface.

That, in turn, necessitates using protected rather than public for things like $session->uid, so that subclasses can tweak them if necessary. That is a requirement. Also, if $session->uid is the uid that originally logged in, not a manipulatable object like $user (which I presume is the reason for not just using $session->user->uid), then it should be named accordingly as $loginUid or something like that.

Similarly, $preventWrite should not be a bare property. Instead, we should have a property protected $writeOnClose = TRUE that we check for in the save routine, and then either expose a method to manipulate it or, even better, have a testing class that extends DruaplSessionDatabase and does nothing but change the value of that property. Then you can just swap into a testing session implementation in a config when you need to. Or, there's no reason we can't do both. :-)

Arguably we could also have a $session->currentUser() method rather than accessing the bare property, too. We could then pass in a param to say whether to clone the object first, allowing us to get a copy of the global $user object that we can mess with without breaking the current login status.

If we do allow ArrayInterface access to the session object, do we want to consider making it "unset safe", so that you can't generate notices by accessing an unset property? Instead, return NULL. That would probably make things break harder, which is a good thing. :-) If you want a default, then we have a $session->value($key, $default) method a la variable_get().

#30

I am shocked that replacing $_SESSION with an object would actually work. To be honest I'm not even convinced yet. :-) What exactly is wrong with session()->whatev? Overriding $_SESSION breaks a PHP dev's expectation of what the superglobals do, which is bad DX unless there's a very good reason.

I don't see why. Accessing $_SESSION as an array works just like before. And because that

As sdboyer pointed out, a factory function makes it much easier to swap out implementations. Swapping out session.inc is a bad mechanism that breaks the registry.

We still have a explicit factory, that is called DrupalSession::init(). Easy to swap out implementations from there.

Which leads me to my next comment: This should be implemented as DrupalSessionInterface and core provides a DrupalSessionDatabase that implements that interface. Other code can then implement alternate implementations of DrupalSessionInterface, possibly by subclassing DrupalSessionDatabase or possibly just implementing the same interface.

Our session implementation is currently non standard. The way to go (if we want to implement that properly) would be to respect PHP settings for sessions. And to allow Drupal and other frameworks (CakePHP, etc.) to share the same session handling. This would require to unbundle our current session and user management... looking at the current implementation of _sess_read() is sufficient to be convinced that we are doing things wrong now.

Similarly, $preventWrite should not be a bare property. Instead, we should have a property protected $writeOnClose = TRUE that we check for in the save routine, and then either expose a method to manipulate it or, even better, have a testing class that extends DruaplSessionDatabase and does nothing but change the value of that property. Then you can just swap into a testing session implementation in a config when you need to. Or, there's no reason we can't do both. :-)

There is no reason to make $preventWrite anything else than a bare property. It is FALSE by default, you can set it to anything non FALSE to prevent session writing. It's as simple as that. There is no validation, no access checking to be done. No need for any cruft here.

Arguably we could also have a $session->currentUser() method rather than accessing the bare property, too. We could then pass in a param to say whether to clone the object first, allowing us to get a copy of the global $user object that we can mess with without breaking the current login status.

That's actually a great idea.

If we do allow ArrayInterface access to the session object, do we want to consider making it "unset safe", so that you can't generate notices by accessing an unset property? Instead, return NULL. That would probably make things break harder, which is a good thing. :-) If you want a default, then we have a $session->value($key, $default) method a la variable_get().

I'm with chx on the "don't babysit broken code" rule. Hiding errors is the best way to allow hidden breakage. I would even argue that we should change it to throw an Exception in that case.

#31

I don't see why. Accessing $_SESSION as an array works just like before.

Yeah, I can't think of a real reason why this would be a problem, either. As I said, I think it's a great way to reduce some of the complexity here; my concern with it really boils down to knowing the PHP does weird, non-obvious-in-userland stuff with its sessions AND with its superglobals. So the combination of the two makes me itch, notwithstanding my excitement.

We still have a explicit factory, that is called DrupalSession::init(). Easy to swap out implementations from there.

I'm loathe to question, because that's worked out so well for me in the recent past, but...it's not a factory if it's a method attached to the same class we'd want to be switching out. The only way to make it pluggable as it exists right now is to switch out session.inc, or go in and change the actual code. And the latter option doesn't qualify as pluggability.

The way to go (if we want to implement that properly) would be to respect PHP settings for sessions. And to allow Drupal and other frameworks (CakePHP, etc.) to share the same session handling. This would require to unbundle our current session and user management... looking at the current implementation of _sess_read() is sufficient to be convinced that we are doing things wrong now.

I've had the thought a few times in working on this that loosening the coupling between users and sessions could be beneficial, but was trying to avoid scope creep...I guess that's less relevant now. Anyway, though, I don't quite get what you mean by this - "respect PHP settings for sessions?" As in, use PHP's default file-based implementation? I don't understand what you're suggesting this interoperability would entail.

There is no reason to make $preventWrite anything else than a bare property. It is FALSE by default, you can set it to anything non FALSE to prevent session writing. It's as simple as that. There is no validation, no access checking to be done. No need for any cruft here.

I agree. Implementing that level of defensiveness is over the top.

Arguably we could also have a $session->currentUser() method...

I also agree - nifty idea, something actually usable for modules, I think. It would add some substantive purpose to the similarly-oriented function I had in earlier iterations.

I'm with chx on the "don't babysit broken code" rule. Hiding errors is the best way to allow hidden breakage. I would even argue that we should change it to throw an Exception in that case.

+1, and +1. Drupal doesn't do backwards-compatibility; in my mind, that goes hand-in-hand not babysitting broken code. If we do, then it just makes it harder to understand why an API change broke something. Same reason I love type hinting!

#32

I don't dispute that doing bizarre things to $_SESSION is cool. I dispute that there's sufficient value in doing so to justify changing the behavior of a standard PHP superglobal rather than the common and self-obvious session() wrapper function. "Because it's a nifty trick" is not a sufficient reason.

Also, while I see from the last patch that we're pulling the array back out to save it, what happens if PHP tries to end the session without that explicit step? Does it still work because the session object is a pseudo-array, or does it die in a twisted pile of molten metal?

In general, I am of the mind that we should be layering functionality, not changing language functionality, unless there's a very good reason to change it. Sometimes there are; I haven't been convinced of that here yet.

I also don't follow the "we're already doing non-standard stuff" claim. Please elaborate.

Regarding $preventWrite, negative properties are non-intuitive. Most properties are affirmative properties, so throwing in a periodic "but don't do this" property is bad for DX. That's why simple setters are more self-documenting. They're an affirmative verb with a negator if appropriate.

#33

Fair enough. I want to say that its appeal is more than mere nifty trickery, but when I really think about it, the best I've got is that "it allows other code to transparently use $_SESSION in the same way they used to, but allows us to effectively know and control its contents." In other words, it itself could end up being a bit of a babysitter for broken code. But...well, I dunno, I'm really waffling there, and I think it's because I just don't have enough experience to have a wise opinion on that one. So I'll defer to whatever y'all figure out.

what happens if PHP tries to end the session without that explicit step?

Which explicit step? Not 100% sure what you mean here, but...

Session closure ordinarily occurs during the PHP shutdown process after object destruction, which is why we used to use the call to register_shutdown_function('session_write_close'); it moved session closure prior to object destruction. Now, the way that session closure is handled is through the DrupalSession::closeHandler() and DrupalSession::writeHandler() methods - both of which are protected, which means that PHP can 'directly' invoke them if a call to session_write_close() (or somethin like it) is made from inside the object. The object destruction problem is handled by adding that call to the destructor, such that object destruction itself triggers session closure. Bottom line, though, is that PHP can't end the session without going through those handlers; that's the whole idea behind session_set_save_handler(). If someone tries to end it from outside class scope, they get a visibility fatal error; we provide methods for them to do it indirectly. So...I get the feeling this doesn't address your question, but it's probably a good explanation to have up anyway :)

Yeah, good point about negative properties. I originally wrote it as Session::allowWrite; chx asked me to take it out because its original use case (we were doing funky stuff with the user's uid and wanted to prevent corrupting session data) was obviated by the other changes in this patch. I think leaving it in for test purposes, though, is perfectly reasonable - but it should be changed back to a positive property.

#34

why would you ever want to have a property for telling yourself that you've been written to that you have to explicitly update?

why not just do this instead:

<?php

class DrupalSession {
  protected
$updated = FALSE;

  public function
__get($name) {
    return
$this->$name;
  }

  public
funtion __set($name, $value) {
   
$this->updated = TRUE;
   
$this->$name = $value;
  }
 
  private function
__destruct() {
    if (
$this->updated === TRUE) {
     
$this->_writeClose();
    }
  }
 
/* ... */
}
?>

#35

I guess this is "the" issue. Sadly, this conversation sort of ended here... what could we do to make the changes simple enough so that they could get accepted on Saturday?

#36

subscribe

#37

sub.

#38

+1

#39

+1.

#40

Now when we more or less have decided to use the HTTP Foundation from Symfony2, I think it makes total sense to use the session handling that comes with the HTTP Foundation from Symfony2. We are already carrying that code weight, so to speak.

It's really solid, comes with a well defined storage interface and a few different implementations, like a PDO implementation that we could use as a base.

Code is here: https://github.com/symfony/HttpFoundation/blob/master/Session.php
Storage implementations are here: https://github.com/symfony/HttpFoundation/tree/master/SessionStorage

This is also mentioned here: #1263478: Identify pieces of Drupal that could be replaced by Symfony code

#41

Version:7.x-dev» 8.x-dev

Missed this first time around.

The Symfony session storage interface looks decent to me, had been thinking of shipping core with a second implementation that does not depend on the database possibly in Drupal 8, if there's a base there already (native and file) that's handy.

I'm less keen on the actual session class though at first look. What is this 'flash' stuff? Is that the rough equivalent of drupal_set_message()?

Also need to figure out what we do with $GLOBALS['user'] if attempting this, that is not a bad thing but it's not a straight conversion at all.

#42

I agree that we definitely need to look closer on both the session class and it's storage implementations. I'd recommend we do that before we start any other refactoring of the session system. I'd happily give this a shot.

Regarding $GLOBALS['user'] it's to my understanding that the issues raised here will be solved by the context system providing $context['user'] instead. The context will always be locked, so changing the user object should be impossible, if I understand things right.

#43

There'll need to be a context provider for the current user, but context system itself is not going to be doing that work.

If we don't want the session system to do it (which seems fine - feels like session should just tell us if there's a session at all and the uid if it's an auth user), then it needs to happen somewhere else. If this patch lands before the context system it'll at least need to move the creation of $GLOBALS['user'] somewhere else.

#44

Correct, there will be a handler that is responsible for $context['user'] that, I suppose, initializes the session in the background. I'm not sure yet on the particulars of that.

I haven't looked at the Symfony session handling at all yet, so I have no opinion there, yet.

#45

subscribe.

#46

Since I worked on the original patch, and have done a fair bit of Symfony tinkering, I'll see if I can't try to rock out some patches. I do agree with @catch that the 'flash' stuff is really an odd thing to have in there...that really smells like something that they've been meaning to separate, but haven't. And just looking at that, it's enough for me to say "no." That's a really confusing interface to present to our devs, but expect them not to use.

That said, I do like their separation of Session from the SessionStorage, and I'd strongly support following that pattern in rolling ours. Of course, we're immediately gonna get into some pluggability pattern questions when we do that...but hey, that's a good thing to keep pushing on :)

Note that in the implementations we've done in patches so far, the session object registers itself (via session_set_save_handler() in its own constructor. That's poor architecture, and ruins testability. Whatever we do, we need to get rid of that.

#47

And one note though, re: #22. Overriding $_SESSION makes me really, really squeamish now, given the challenges with php's objects-as-arrays situation as I documented in #402896-155: Introduce DrupalCacheArray and use it for drupal_get_schema().

And also...it's really just not an API or an interface. I think we should just dismiss that approach. Give folks a class, and document it. That's what the other frameworks are doing, we should feel fine doing it too.

#48

We could possibly use our own Session interface and class but re-use SessionStorage or at least make things compatible, then file an upstream issue with Symfony about the flash stuff. I agree with sdboyer it feels like a showstopper at the moment.

#49

Oh yeah, I could totally dig using their storage but our own Session class/interface.

#50

Chatted with @dixon_ and @catch. We're pretty well agreed that we will discard the Session class and write our own, but utilize the storage. My own list there is:

  • SessionStorageInterface: +1
  • NativeSessionStorage: +1
  • ArraySessionStorage: meh
  • FilesystemSessionStorage: -1, this seems just like Native but done in userspace with a configurable path...unless I missed something?
  • PdoSessionStorage: -1, replace with our own DbtngSessionStorage(that's the idea, don't take the classname literally)

@catch pointed out that we could use NativeSessionStorage in the installer. Maybe even as the default everywhere, pending benchmarks.

We also agreed that we like the idea of simply deleting files from HttpFoundation that we aren't using, in order to reduce developer confusion with yet more APIs they're not supposed to touch.

Sooo...now we just need HttpFoundation and the autoloader to make it in :)

#51

Just FYI, did you look at http://drupal.org/sandbox/pounard/1263216 it's a sandbox project, which makes you able to use either native PHP session, either one more complex with a storage engine. I even wrote a storage engine using any cache backend for storage (when using my Redis cache backends, it really roxxes, believe me). You should also look at the issues of this sandbox, most of them have been opened by a colleague of mine, admin sys and developer, specialized in the LAMP stack and ZF.

EDIT: That said, Symfony's one looks really good.

#52

@#50 Native sessions in PHP are likely to be faster on a single box, single user, with a fast harddrive or in a tmpfs, but it also subject to big locks, and as soon as you will have a lot of AJAX and or many tabs for the same user it can be like hell for performances.

#53

I talked with sdboyer a bit last night. I am on board with #50 with the exception of removing classes we're not using. If there's a class we could use but decided not to (Symfony's session class, for instance), we should document why but not remove it. It's still way too early in the cycle to be pruning code for the sake of pruning. We can revisit that question when Drupal 8 is in alpha or beta.

I also really like the idea of using PHP native session handling for the installer, and for other "degraded" states. That sort of strategy should help make Drupal much more robust, and have fewer wonky hacks for such conditions.

#54

I don't think we should remove the classes immediately, but we should leave the option there. Either way it makes sense to document things like the decision to use or not use specific bits.

#55

Tagging this to make it easier to identify all of the Symfony-related issues.

#56

Subscribe

#57

Subscribe

#58

I like the Symfony inclusion of session handling, but I'm afraid of how to efficiently lock session writing and session reading to avoid concurent access (multiple browser window or parallel AJAX request). Either you can big lock the entire session either you can lock on a per entry key basis: first will generate a "giant lock" (not elegant and really slow) but the other one may create dozens of them even if they do not concur themselves. One think that I really like with actual D7 session handling is the lazzy session read and write (writes only if modified) which greatly diminishes the potential race condition (but still leaves some).

#59

#60

@pounard any solution that loses the lazy session loading we added in D7 will be a non-starter, IMO.

I have no idea how significant that concurrency & contention issue is, but my gut says it's fairly unlikely? If so, the value of adopting an interface and some pluggability would be that an alternate session storage handler can be implemented that does key-level locking. Seems to me to be tough to do that with file-based or the structure of our current sessions table, and I don't think remedying it could be performant. A Redis-backed implementation could prolly manage it more easily, though.

#61

I'm not pro key-level locking, as soon as the session backend is a remote server you will generate an awful lot of I/O. The session concurency situation is a real problem, see this use case: open 10 tabs quickly middle-cilcking 10 links on a news site: your browser loads in parallel 10 pages of the same site: you just generated the concurency situation.

#62

@sdboyer's #50 would the file session storage be easier if say you were using NFS? I can't imagine wanting to ever run that in production but seems like a possible use case for it.

#63

@#62 File storage can be used in tmpfs or ramfs, some web applications are configured this way, it makes sense.

#64

I've opened #1277682: Move responsibility for global $user out of session as a possible interim step for this.

#65

FYI the "FilesystemSessionStorage" is mostly used for functional testing.

#66

@catch I mean, I guess...but yeah, by the time you've got NFS backing your prod, the latency inherent in being file-backed probably makes it a non-starter - to say nothing of the network overhead.

@lsmith77 ah ok, that makes a fair bit more sense.

@pounard haha, I suppose. And I do that on d.o all the time :) It's true, though, that writing to an external system does mean latency, unless maybe you could get by with async writes. But that has its own issues. Really though, this seems like it ought to be a hugely problematic issue, as that's common behavior...so maybe the question is, why isn't it a bigger complaint? Or is it a big known issue, and I've just missed it?

#67

I think most people that uses file storage will use native session handling since PHP does it well since the begining (and uses file locks along with it too!).

#68

re #58 and #60 and 'lazy session handling'. a big part of our lazy session handling in D7 is just not sending down a session cookie for anon users unless they have session data. there is no reason at all for this to be changed if we decide to go for consistent reads and writes for session data.

while not writing session data unless it changed helps us a lot with the race condition, it also doesn't need to change for data-consistency reasons. that's just a straight performance hack (we trade memory usage against writes to the session store backend), and should be assessed as such, regardless of any locking we may introduce.

that being said, i'm on the fence about whether we need to fix the race here - sites that really need consistency in session data can get it via swapping out the session handling code.

IMO, the existing data consistency issues with our variable_get|set|del code are a much bigger concern.

#69

#68 While this discussion is open, I think that it should remains something to think about, any solution may come over time, let's keep this as a background detail but not forget it.

#70

Status:needs work» postponed

Let's be formal about it - postponing this, pending the completion of #1178246: Add Symfony2 HttpFoundation library to core, since we're going to build on those libraries.

#71

.

#72

sub.

#73

Project:Drupal core» WSCCI
Version:8.x-dev» <none>
Component:base system» Code

#74

IMO, there isn't a *huge* issue with having Session containing an API for session messages. After all, a message is always tied directly to a given session, and the API itself is rather light. As is, not being able to store message types is a problem for us.

There's a related issue for flash messages over in the Symfony issue queue: https://github.com/symfony/symfony/issues/1863. If we can get message types in their flash message implementation, we can turn drupal_set_message() into a thin wrapper around that. I've got a fork starting that work over here that I worked on with Greyside at the BADCamp sprint: https://github.com/deviantintegral/symfony/tree/1863-improve-flash

#75

Title:Refactor session handling (and Security: do not allow uid changes accidentally)» Switch to Symfony2-based session handling
Priority:normal» major
Status:postponed» active

The patch is in, this is back in business.

#76

@#66 There is a lot of complaints, you just don't hear them:) Just kidding (not that much, admins often yells but no one hear them) but not a lot of people actually knows that when you use a native session, it will be locked from the moment you read it to the moment you actually write it, i.e. the full page generation time in most case. This means that parallel AJAX requests for the same user will be mutually excluded, this is really bad. There is only two way to break this, and both includes not using the native session: either don't lock the session and let the last one who writes it win, either provide a per key locking.

#77

Project:WSCCI» Drupal core
Version:<none>» 8.x-dev
Component:Code» base system

There is zero reason for this to happen in the sandbox. Refactoring of session handling can happen in core and ought to be a self contained patch apart from some user module evilness.

#78

catch: No, we've concluded that since user and session are so tightly coupled right now (itself a problem) we can't really fix one without fixing the other. We tried in #1260864: Convert global $user to a context key. Making user a context key without also rejiggering session handling is more trouble than its worth. If those have to happen together, it's easier to do them in the sandbox.

#79

@Crell - we have #1277682: Move responsibility for global $user out of session already, that could be done as an interim measure to unblock this for core. Or are you planning to submit the initial context patch including a full refactoring of session handling all at once?

#80

I'd agree that session can refactored using Symfony's one outside of the sandbox, and then when the context API is ready we can provide the final glue between them. Session handling has its whole business stuff to do, it's has enough complexity to be treated in an isolated core issue. Then linking it to context is only a final piece of glue that will probably only provide the lazzy loading as new feature.

As catch says in #79 there is ways to separate concerns here, and this may help progressing faster on context API since we'd not have to worry about this (and the final glue code will be, IMHO, quite easy to write in the end).

#81

someone please remove my name from the node and delete/unpublish #1 as you see fit, i find it a travesty and offending that my simple little issue was taken over by this and yet kept my name on it. Edit: did myself.

#82

Symfony 2.1 will have their Session handling overhauled. Changes are in this PR with the full source code here. These changes fix many issues including stale locks, unexpected flash message clearing, and properly implementing the native session lifecycle. In addition, SessionStorage implementations have been better abstracted, as well as many more handlers added. I'm told by lsmith these changes will drop near the end of March.

+1 to using Symfony SessionStorage as each class is built around session_set_save_handler() fulfilling the session life cycling with various storage engines.

As for the locking issue...is it an issue? As a PHP developer I've always been aware of the shortcoming of PHP sessions. That was the nature of the ease of use coming from using sessions. If I was doing a long lived request I would have to call session_write_close() to remove the lock and dump my changes to the storage.

#83

just FYI .. last I heard https://github.com/symfony/symfony/pull/2853 will be merged but without the flash changes for now. There is an on going discussion about them on the mailing list and Fabien wants to think about it some more:
http://groups.google.com/group/symfony-devs/browse_frm/thread/732e30a240...

#84

Symfony 2 session handling is becoming really impressive with this pull request, I like it. If that is compatible with our usage, we have no excuses not using it.

#85

Has anyone had a chance to evaluate Symfony's support for HTTPS sessions and mixed HTTP/S sessions?

#86

I haven't but was going to ask the same thing. I didn't see any sign of it last time I looked though.

#87

grendzy: Are you talking about something similar to this?

#88

bschussek: more directly, I'm asking if the Secure Pages module (which I maintain) will be compatible with Symfony2 sessions. This has been a bit of a fiasco in Drupal 7.

It's actually enough if the session handler completely ignores the protocol / URI scheme (at least optionally), and lets another module step in. The "secure attributes" you propose aren't really needed for this purpose.

nobody click here