NEW Add additional session handler implementations#11783
NEW Add additional session handler implementations#11783emteknetnz merged 4 commits intosilverstripe:6from
Conversation
fa09ce0 to
2a27e43
Compare
There was a problem hiding this comment.
Implementing the session handler for cache adapters as a single abstracted handler ended up being simpler than if I tried to implement Redis and Memcached handlers separately - with the bonus that it's also easier to test since we can use Symfony's ArrayAdapter.
There was a problem hiding this comment.
The session lifetime is the same across all session handlers so it made sense to move into its own abstract class (pulled from FileSessionHandler).
2a27e43 to
644e9c3
Compare
| /** | ||
| * @dataProvider provideDefaultSort | ||
| */ | ||
| #[DataProvider('provideDefaultSort')] |
There was a problem hiding this comment.
Unrelated MNT fix. Noticed a warning about this in the test run so I figured I may as well just get it fixed.
|
|
||
| ini_set('session.gc_maxlifetime', $gcLifetime); | ||
| Session::config()->set('lifetime', $configLifetime); | ||
| Session::config()->set('timeout', $configLifetime); |
There was a problem hiding this comment.
This and the implementation in FileSessionHandler were looking at the wrong config name.
25821f6 to
7bdad43
Compare
14cf7dc to
8665121
Compare
| private function isDatabaseReady() | ||
| { | ||
| if (!DB::connection_attempted() || !DB::is_active()) { | ||
| return false; | ||
| } | ||
| return DB::get_schema()->hasTable(static::config()->get('table_name')); | ||
| } |
There was a problem hiding this comment.
This was originally going to use the new DataObjectSchema::tablesAreReadyForClass() method, but since we aren't using a DataObject (see phpdoc comment for requireTable() for reasons why not) that's not suitable.
Ultimately since the schema for this table isn't configurable, the table will either exist and have the correct schema, or not exist.
b9884bf to
b034bcb
Compare
| use SilverStripe\Core\Config\Config; | ||
| use SilverStripe\Core\Injector\Injector; | ||
| use PHPUnit\Framework\Attributes\DataProvider; | ||
| use PHPUnit\Framework\Attributes\PreserveGlobalState; |
There was a problem hiding this comment.
Noticed this use statement was missing... I have no idea why it wasn't causing CI failures but this class is referenced in some of the tests higher up in this class.
6716e3d to
f5e2054
Compare
f5e2054 to
a51be7e
Compare
| public function testGc(int $gcLifetime, int $configLifetime, array $sessionLifetimeMap, array $expectDeleted): void | ||
| { | ||
| $tableName = DatabaseSessionHandler::config()->get('table_name'); | ||
| ini_set('session.gc_maxlifetime', $gcLifetime); |
There was a problem hiding this comment.
Shouldn't this be reset after the test?
There was a problem hiding this comment.
I don't think it particularly matters (sessions aren't used in CI anyway and this doesn't actually set anything permanently, only for the current process) - but have reset anyway for this and FileSessionHandlerTest to avoid further ping pong.
|
|
||
| private function getSessionHandler(): ?DatabaseSessionHandler | ||
| { | ||
| $sessionHandlerServiceName = Session::getSaveHandler(); |
There was a problem hiding this comment.
Session::getSaveHandler() returns an object, not a service name?
Probably means we don't need the $sessionHandler = Injector::inst()->get($sessionHandlerServiceName); below?
There was a problem hiding this comment.
Oops, I must have had an earlier implementation of getSaveHandler() and not updated this or something. Fixed.
a51be7e to
f0bf1d4
Compare
|
The CI failures are unrelated - compare with the 6 branch |
emteknetnz
left a comment
There was a problem hiding this comment.
I set my .env to
SS_SESSION_SAVE_HANDLER_CLASS="SilverStripe\Control\SessionHandler\DatabaseSessionHandler"
and I ran dev/build flush
I got this on the login form when I went to /admin (which then redirects to /Security/login?BackURL=%2Fadmin%2Fpages)
// Session start emits a cookie, but only if there's no existing session. If there is a session timeout
336 // tied to this request, make sure the session is held for the entire timeout by refreshing the cookie age.
337 if ($cookieParams['lifetime'] && $this->requestContainsSessionId()) {
338 Cookie::set(
339 session_name(),
Trace
session_start()
Session.php:333
SilverStripe\Control\Session->start(SilverStripe\Control\HTTPRequest)
Session.php:252
SilverStripe\Control\Session->init(SilverStripe\Control\HTTPRequest)
SessionMiddleware.php:17
SilverStripe\Control\Middleware\SessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
AllowedHostsMiddleware.php:71
SilverStripe\Control\Middleware\AllowedHostsMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
TrustedProxyMiddleware.php:179
SilverStripe\Control\Middleware\TrustedProxyMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
HTTPMiddlewareAware.php:65
SilverStripe\Control\Director->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
Director.php:382
SilverStripe\Control\Director->handleRequest(SilverStripe\Control\HTTPRequest)
HTTPApplication.php:114
SilverStripe\Control\HTTPApplication::SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
call_user_func(Closure, SilverStripe\Control\HTTPRequest)
HTTPApplication.php:137
SilverStripe\Control\HTTPApplication->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
HTTPMiddlewareAware.php:65
SilverStripe\Control\HTTPApplication->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
HTTPApplication.php:130
SilverStripe\Control\HTTPApplication->execute(SilverStripe\Control\HTTPRequest, Closure, )
HTTPApplication.php:113
SilverStripe\Control\HTTPApplication->handle(SilverStripe\Control\HTTPRequest)
index.php:24
f0bf1d4 to
f7c4336
Compare
|
Huh. For some reason |
There was a problem hiding this comment.
I'm getting the following error on dev/build when I attempt to use memcached
Note there's a good chance one of my setup steps is incorrect. I've never used memcached before
In InjectionCreator.php line 20:
[SilverStripe\Core\Injector\InjectorNotFoundException]
Class `SS_SESSION_CACHE_FACTORY` does not exist
# .env
SS_SESSION_SAVE_HANDLER_CLASS="SilverStripe\Control\SessionHandler\CacheSessionHandler"
SS_SESSION_CACHE_FACTORY="SilverStripe\Core\Cache\MemcachedCacheFactory"
SS_MEMCACHED_DSN="memcached://localhost:11211"
I setup memcached in my docker container as follows, as ssh'd in as root user
apt update && apt install -y memcached php8.3-memcached
memcached -d -m 64 -p 11211 -u memcache
Exception trace:
at /var/www/vendor/silverstripe/framework/src/Core/Injector/InjectionCreator.php:20
SilverStripe\Core\Injector\InjectionCreator->create() at /var/www/vendor/silverstripe/framework/src/Core/Injector/Injector.php:631
SilverStripe\Core\Injector\Injector->instantiate() at /var/www/vendor/silverstripe/framework/src/Core/Injector/Injector.php:1027
SilverStripe\Core\Injector\Injector->getNamedService() at /var/www/vendor/silverstripe/framework/src/Core/Injector/Injector.php:979
SilverStripe\Core\Injector\Injector->get() at /var/www/vendor/silverstripe/framework/src/Core/Injector/Injector.php:623
SilverStripe\Core\Injector\Injector->instantiate() at /var/www/vendor/silverstripe/framework/src/Core/Injector/Injector.php:1027
SilverStripe\Core\Injector\Injector->getNamedService() at /var/www/vendor/silverstripe/framework/src/Core/Injector/Injector.php:979
SilverStripe\Core\Injector\Injector->get() at /var/www/vendor/silverstripe/framework/src/Core/Injector/Injector.php:521
SilverStripe\Core\Injector\Injector->convertServiceProperty() at /var/www/vendor/silverstripe/framework/src/Core/Injector/Injector.php:513
SilverStripe\Core\Injector\Injector->convertServiceProperty() at /var/www/vendor/silverstripe/framework/src/Core/Injector/Injector.php:497
SilverStripe\Core\Injector\Injector->updateSpecConstructor() at /var/www/vendor/silverstripe/framework/src/Core/Injector/Injector.php:1023
SilverStripe\Core\Injector\Injector->getNamedService() at /var/www/vendor/silverstripe/framework/src/Core/Injector/Injector.php:979
SilverStripe\Core\Injector\Injector->get() at /var/www/vendor/silverstripe/framework/src/Control/Session.php:209
SilverStripe\Control\Session::getSaveHandler() at /var/www/vendor/silverstripe/framework/src/Control/SessionHandler/DbBuildSessionExtension.php:56
SilverStripe\Control\SessionHandler\DbBuildSessionExtension->getSessionHandler() at /var/www/vendor/silverstripe/framework/src/Control/SessionHandler/DbBuildSessionExtension.php:42
SilverStripe\Control\SessionHandler\DbBuildSessionExtension->onAfterBuild() at /var/www/vendor/silverstripe/framework/src/Core/Extension.php:141
SilverStripe\Core\Extension->invokeExtension() at /var/www/vendor/silverstripe/framework/src/Core/Extensible.php:437
SilverStripe\Dev\Command\DbBuild->extend() at /var/www/vendor/silverstripe/framework/src/Dev/Command/DbBuild.php:178
SilverStripe\Dev\Command\DbBuild->doBuild() at /var/www/vendor/silverstripe/framework/src/Dev/Command/DbBuild.php:78
SilverStripe\Dev\Command\DbBuild->execute() at /var/www/vendor/silverstripe/framework/src/Dev/Command/DevCommand.php:37
SilverStripe\Dev\Command\DevCommand->run() at /var/www/vendor/silverstripe/framework/src/Cli/Command/PolyCommandCliWrapper.php:39
SilverStripe\Cli\Command\PolyCommandCliWrapper->execute() at /var/www/vendor/symfony/console/Command/Command.php:318
Symfony\Component\Console\Command\Command->run() at /var/www/vendor/symfony/console/Application.php:1092
Symfony\Component\Console\Application->doRunCommand() at /var/www/vendor/silverstripe/framework/src/Cli/Sake.php:219
SilverStripe\Cli\Sake->doRunCommand() at /var/www/vendor/symfony/console/Application.php:341
Symfony\Component\Console\Application->doRun() at /var/www/vendor/symfony/console/Application.php:192
Symfony\Component\Console\Application->run() at /var/www/vendor/silverstripe/framework/src/Cli/Sake.php:118
SilverStripe\Cli\Sake->run() at /var/www/vendor/silverstripe/framework/bin/sake:19
include() at /var/www/vendor/bin/sake:119
db:build [--no-populate] [--dont_populate]
|
I think I must have only tested that before setting up the env var. The new injector config doesn't seem to be working based on that first error. I'll look into it. |
|
For some reason with the cached session handler it's forcing dev/build if I'm not authenticated (after fixing the above).... will look into that now. |
31130fd to
8fe18ae
Compare
|
Rebased in case something where's happening as a result of recent changes |
8fe18ae to
a06e507
Compare
|
The dev/build thing is caused by #11806 - I'll need to rebase again after the fix is merged. |
7b03459 to
e1abf42
Compare
The intention is to use in-memory cache adapters such as Redis and Memcached - though theoretically any PSR16 adapter would work.
This is an easy option for multi-server hosting environments, though is likely less performant than the other session handlers on offer.
This allows hosting providers to set specific session save handlers if they have infrastructure set up for one that is preferred (e.g. if using redis).
This allows us to use environment variables to set a factory class name in injector configuration.
e1abf42 to
e8e6948
Compare
Doing this in several commits - DO NOT SQUASH
Commit 1: The cache-based session handler that can be used for Redis and Memcached
Commit 2: The database session handler
Commit 3: Add new environment variable to set the save handler
Commit 4: Injector allows backticks in the
factorykey now. This isn't a breaking change, because using backticks for that before would be trying to get a class name that has backticks in it, which PHP doesn't allow.Issue