diff --git a/framework/core/src/Extend/Session.php b/framework/core/src/Extend/Session.php
new file mode 100644
index 000000000..83d4af8a1
--- /dev/null
+++ b/framework/core/src/Extend/Session.php
@@ -0,0 +1,42 @@
+drivers[$name] = $driverClass;
+
+ return $this;
+ }
+
+ public function extend(Container $container, Extension $extension = null)
+ {
+ $container->extend('flarum.session.drivers', function ($drivers) {
+ return array_merge($drivers, $this->drivers);
+ });
+ }
+}
diff --git a/framework/core/src/Foundation/Console/InfoCommand.php b/framework/core/src/Foundation/Console/InfoCommand.php
index 383710bd5..f7bd3e80b 100644
--- a/framework/core/src/Foundation/Console/InfoCommand.php
+++ b/framework/core/src/Foundation/Console/InfoCommand.php
@@ -14,10 +14,14 @@ use Flarum\Extension\ExtensionManager;
use Flarum\Foundation\Application;
use Flarum\Foundation\Config;
use Flarum\Settings\SettingsRepositoryInterface;
+use Flarum\User\SessionManager;
use Illuminate\Contracts\Queue\Queue;
use Illuminate\Database\ConnectionInterface;
+use Illuminate\Support\Arr;
use Illuminate\Support\Str;
+use InvalidArgumentException;
use PDO;
+use SessionHandlerInterface;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableStyle;
@@ -48,18 +52,32 @@ class InfoCommand extends AbstractCommand
*/
private $queue;
+ /**
+ * @var SessionManager
+ */
+ private $session;
+
+ /**
+ * @var SessionHandlerInterface
+ */
+ private $sessionHandler;
+
public function __construct(
ExtensionManager $extensions,
Config $config,
SettingsRepositoryInterface $settings,
ConnectionInterface $db,
- Queue $queue
+ Queue $queue,
+ SessionManager $session,
+ SessionHandlerInterface $sessionHandler
) {
$this->extensions = $extensions;
$this->config = $config;
$this->settings = $settings;
$this->db = $db;
$this->queue = $queue;
+ $this->session = $session;
+ $this->sessionHandler = $sessionHandler;
parent::__construct();
}
@@ -93,6 +111,7 @@ class InfoCommand extends AbstractCommand
$this->output->writeln('Base URL: '.$this->config->url());
$this->output->writeln('Installation path: '.getcwd());
$this->output->writeln('Queue driver: '.$this->identifyQueueDriver());
+ $this->output->writeln('Session driver: '.$this->identifySessionDriver());
$this->output->writeln('Mail driver: '.$this->settings->get('mail_driver', 'unknown'));
$this->output->writeln('Debug mode: '.($this->config->inDebugMode() ? 'ON' : 'off'));
@@ -169,4 +188,53 @@ class InfoCommand extends AbstractCommand
{
return $this->db->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION);
}
+
+ /**
+ * Reports on the session driver in use based on three scenarios:
+ * 1. If the configured session driver is valid and in use, it will be returned.
+ * 2. If the configured session driver is invalid, fallback to the default one and mention it.
+ * 3. If the actual used driver (i.e `session.handler`) is different from the current one (configured or default), mention it.
+ */
+ private function identifySessionDriver(): string
+ {
+ /*
+ * Get the configured driver and fallback to the default one.
+ */
+ $defaultDriver = $this->session->getDefaultDriver();
+ $configuredDriver = Arr::get($this->config, 'session.driver', $defaultDriver);
+ $driver = $configuredDriver;
+
+ try {
+ // Try to get the configured driver instance.
+ // Driver instances are created on demand.
+ $this->session->driver($configuredDriver);
+ } catch (InvalidArgumentException $e) {
+ // An exception is thrown if the configured driver is not a valid driver.
+ // So we fallback to the default driver.
+ $driver = $defaultDriver;
+ }
+
+ /*
+ * Get actual driver name from its class name.
+ * And compare that to the current configured driver.
+ */
+ // Get class name
+ $handlerName = get_class($this->sessionHandler);
+ // Drop the namespace
+ $handlerName = Str::afterLast($handlerName, '\\');
+ // Lowercase the class name
+ $handlerName = strtolower($handlerName);
+ // Drop everything like sessionhandler FileSessionHandler, DatabaseSessionHandler ..etc
+ $handlerName = str_replace('sessionhandler', '', $handlerName);
+
+ if ($driver !== $handlerName) {
+ return "$handlerName (Code override. Configured to $configuredDriver>)";
+ }
+
+ if ($driver !== $configuredDriver) {
+ return "$driver (Fallback default driver. Configured to invalid driver $configuredDriver>)";
+ }
+
+ return $driver;
+ }
}
diff --git a/framework/core/src/User/SessionDriverInterface.php b/framework/core/src/User/SessionDriverInterface.php
new file mode 100644
index 000000000..7a0b284f7
--- /dev/null
+++ b/framework/core/src/User/SessionDriverInterface.php
@@ -0,0 +1,27 @@
+container->make(Config::class);
+ $driverName = Arr::get($config, 'session.driver');
+
+ try {
+ $driverInstance = parent::driver($driverName);
+ } catch (InvalidArgumentException $e) {
+ $defaultDriverName = $this->getDefaultDriver();
+ $driverInstance = parent::driver($defaultDriverName);
+
+ // But we will log a critical error to the webmaster.
+ $this->container->make(LoggerInterface::class)->critical(
+ "The configured session driver [$driverName] is not available. Falling back to default [$defaultDriverName]. Please check your configuration."
+ );
+ }
+
+ return $driverInstance->getHandler();
+ }
+}
diff --git a/framework/core/src/User/SessionServiceProvider.php b/framework/core/src/User/SessionServiceProvider.php
index 2eb070547..e0f8023a2 100644
--- a/framework/core/src/User/SessionServiceProvider.php
+++ b/framework/core/src/User/SessionServiceProvider.php
@@ -10,7 +10,9 @@
namespace Flarum\User;
use Flarum\Foundation\AbstractServiceProvider;
-use Illuminate\Session\FileSessionHandler;
+use Flarum\Foundation\Config;
+use Flarum\Settings\SettingsRepositoryInterface;
+use Illuminate\Contracts\Container\Container;
use SessionHandlerInterface;
class SessionServiceProvider extends AbstractServiceProvider
@@ -20,12 +22,42 @@ class SessionServiceProvider extends AbstractServiceProvider
*/
public function register()
{
- $this->container->singleton('session.handler', function ($container) {
- return new FileSessionHandler(
- $container['files'],
- $container['config']['session.files'],
- $container['config']['session.lifetime']
- );
+ $this->container->singleton('flarum.session.drivers', function () {
+ return [];
+ });
+
+ $this->container->singleton('session', function (Container $container) {
+ $manager = new SessionManager($container);
+ $drivers = $container->make('flarum.session.drivers');
+ $settings = $container->make(SettingsRepositoryInterface::class);
+ $config = $container->make(Config::class);
+
+ /**
+ * Default to the file driver already defined by Laravel.
+ *
+ * @see \Illuminate\Session\SessionManager::createFileDriver()
+ */
+ $manager->setDefaultDriver('file');
+
+ foreach ($drivers as $driver => $className) {
+ /** @var SessionDriverInterface $driverInstance */
+ $driverInstance = $container->make($className);
+
+ $manager->extend($driver, function () use ($settings, $config, $driverInstance) {
+ return $driverInstance->build($settings, $config);
+ });
+ }
+
+ return $manager;
+ });
+
+ $this->container->alias('session', SessionManager::class);
+
+ $this->container->singleton('session.handler', function (Container $container): SessionHandlerInterface {
+ /** @var SessionManager $manager */
+ $manager = $container->make('session');
+
+ return $manager->handler();
});
$this->container->alias('session.handler', SessionHandlerInterface::class);
diff --git a/framework/core/tests/integration/extenders/SessionTest.php b/framework/core/tests/integration/extenders/SessionTest.php
new file mode 100644
index 000000000..82efc5456
--- /dev/null
+++ b/framework/core/tests/integration/extenders/SessionTest.php
@@ -0,0 +1,116 @@
+expectNotToPerformAssertions();
+ $this->app()->getContainer()->make('session.handler');
+ }
+
+ /**
+ * @test
+ */
+ public function custom_driver_doesnt_exist_by_default()
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->app()->getContainer()->make('session')->driver('flarum-acme');
+ }
+
+ /**
+ * @test
+ */
+ public function custom_driver_exists_if_added()
+ {
+ $this->extend((new Extend\Session())->driver('flarum-acme', AcmeSessionDriver::class));
+
+ $driver = $this->app()->getContainer()->make('session')->driver('flarum-acme');
+
+ $this->assertEquals(NullSessionHandler::class, get_class($driver->getHandler()));
+ }
+
+ /**
+ * @test
+ */
+ public function custom_driver_overrides_laravel_defined_drivers_if_added()
+ {
+ $this->extend((new Extend\Session())->driver('redis', AcmeSessionDriver::class));
+
+ $driver = $this->app()->getContainer()->make('session')->driver('redis');
+
+ $this->assertEquals(NullSessionHandler::class, get_class($driver->getHandler()));
+ }
+
+ /**
+ * @test
+ */
+ public function uses_default_driver_if_driver_from_config_file_not_configured()
+ {
+ $this->config('session.driver', null);
+
+ $handler = $this->app()->getContainer()->make('session.handler');
+
+ $this->assertEquals(FileSessionHandler::class, get_class($handler));
+ }
+
+ /**
+ * @test
+ */
+ public function uses_default_driver_if_configured_driver_from_config_file_unavailable()
+ {
+ $this->config('session.driver', 'nevergonnagiveyouup');
+
+ $handler = $this->app()->getContainer()->make('session.handler');
+
+ $this->assertEquals(FileSessionHandler::class, get_class($handler));
+ }
+
+ /**
+ * @test
+ */
+ public function uses_custom_driver_from_config_file_if_configured_and_available()
+ {
+ $this->extend(
+ (new Extend\Session)->driver('flarum-acme', AcmeSessionDriver::class)
+ );
+
+ $this->config('session.driver', 'flarum-acme');
+
+ $handler = $this->app()->getContainer()->make('session.handler');
+
+ $this->assertEquals(NullSessionHandler::class, get_class($handler));
+ }
+}
+
+class AcmeSessionDriver implements SessionDriverInterface
+{
+ public function build(SettingsRepositoryInterface $settings, Config $config): SessionHandlerInterface
+ {
+ return new NullSessionHandler();
+ }
+}
diff --git a/php-packages/testing/src/integration/TestCase.php b/php-packages/testing/src/integration/TestCase.php
index 79a7a7f25..4efcec34c 100644
--- a/php-packages/testing/src/integration/TestCase.php
+++ b/php-packages/testing/src/integration/TestCase.php
@@ -148,7 +148,7 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
*/
protected function config(string $key, $value)
{
- $this->config[$key] = $value;
+ Arr::set($this->config, $key, $value);
}
/**