Skip to content

Commit 174044c

Browse files
authored
feat(http): support database-based sessions (#1605)
1 parent cd51bcf commit 174044c

File tree

16 files changed

+730
-30
lines changed

16 files changed

+730
-30
lines changed

docs/1-essentials/01-routing.md

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -732,22 +732,48 @@ public function store(Todo $todo): Redirect
732732

733733
### Session configuration
734734

735-
Currently, there's only a built-in file session driver. More drivers are to be added in the future. By default, the session will stay valid for 10 hours, which you can overwrite by creating a `session.config.php` file:
735+
Tempest supports file and database-based sessions, the former being the default option. Sessions can be configured by creating a `session.config.php` file, in which the expiration time and the session driver can be specified.
736+
737+
#### File sessions
738+
739+
When using file-based sessions, which is the default, session data will be stored in files within the specified directory, relative to `.tempest`. You may configure the path and expiration duration like so:
736740

737741
```php app/Config/session.config.php
738-
<?php
739742
use Tempest\Http\Session\Config\FileSessionConfig;
740743
use Tempest\DateTime\Duration;
741744

742745
return new FileSessionConfig(
743-
path: 'sessions', // The path is relative to the project's cache folder
746+
expiration: Duration::days(30),
747+
path: 'sessions',
748+
);
749+
```
750+
751+
#### Database sessions
752+
753+
Tempest provides a database-based session driver, particularly useful for applications that run on multiple servers, as the session data can be shared across all instances.
754+
755+
Before using database sessions, a dedicated table is needed. Tempest provides a migration, which may be installed in your project using its installer:
756+
757+
```sh
758+
./tempest install sessions:database
759+
```
760+
761+
This installer will also suggest creating the configuration file that sets up database sessions, with a default expiration of 30 days:
762+
763+
```php app/Sessions/session.config.php
764+
use Tempest\Http\Session\Config\DatabaseSessionConfig;
765+
use Tempest\DateTime\Duration;
766+
767+
return new DatabaseSessionConfig(
744768
expiration: Duration::days(30),
745769
);
746770
```
747771

748772
### Session cleaning
749773

750-
Outdated sessions should occasionally be cleaned up. Tempest comes with a built-in command to do so: `tempest session:clean`. This command makes use of the [scheduler](/2.x/features/scheduling). If you have scheduling enabled, it will automatically run behind the scenes.
774+
Sessions expire based on the last activity time. This means that as long as a user is actively using your application, their session will remain valid.
775+
776+
Outdated sessions must occasionally be cleaned up. Tempest comes with a built-in command to do so, `session:clean`. This command makes use of the [scheduler](/2.x/features/scheduling). If you have scheduling enabled, it will automatically run behind the scenes.
751777

752778
## Deferring tasks
753779

packages/database/src/Builder/QueryBuilders/HasConvenientWhereMethods.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ public function whereLastYear(string $field): self
393393
}
394394

395395
/**
396-
* Adds a `WHERE` condition for records created after a specific date.
396+
* Adds a `WHERE` condition for records which specified field is after a specific date.
397397
*
398398
* @return self<TModel>
399399
*/
@@ -403,7 +403,7 @@ public function whereAfter(string $field, DateTimeInterface|string $date): self
403403
}
404404

405405
/**
406-
* Adds a `WHERE` condition for records created before a specific date.
406+
* Adds a `WHERE` condition for records which specified field is before a specific date.
407407
*
408408
* @return self<TModel>
409409
*/

packages/http/src/Session/CleanupSessionsCommand.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
use Tempest\Console\Scheduler\Every;
1111
use Tempest\EventBus\EventBus;
1212

13-
use function Tempest\listen;
14-
1513
final readonly class CleanupSessionsCommand
1614
{
1715
public function __construct(
@@ -20,10 +18,7 @@ public function __construct(
2018
private EventBus $eventBus,
2119
) {}
2220

23-
#[ConsoleCommand(
24-
name: 'session:clean',
25-
description: 'Finds and removes all expired sessions',
26-
)]
21+
#[ConsoleCommand(name: 'session:clean', description: 'Finds and removes all expired sessions', aliases: ['session:cleanup'])]
2722
#[Schedule(Every::MINUTE)]
2823
public function __invoke(): void
2924
{
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Http\Session\Config;
6+
7+
use Tempest\Container\Container;
8+
use Tempest\DateTime\Duration;
9+
use Tempest\Http\Session\Managers\DatabaseSessionManager;
10+
use Tempest\Http\Session\SessionConfig;
11+
12+
final class DatabaseSessionConfig implements SessionConfig
13+
{
14+
/**
15+
* @param Duration $expiration Time required for a session to expire.
16+
*/
17+
public function __construct(
18+
private(set) Duration $expiration,
19+
) {}
20+
21+
public function createManager(Container $container): DatabaseSessionManager
22+
{
23+
return $container->get(DatabaseSessionManager::class);
24+
}
25+
}

packages/http/src/Session/Config/FileSessionConfig.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,17 @@
55
use Tempest\Container\Container;
66
use Tempest\DateTime\Duration;
77
use Tempest\Http\Session\Managers\FileSessionManager;
8-
use Tempest\Http\Session\Resolvers\CookieSessionIdResolver;
98
use Tempest\Http\Session\SessionConfig;
109

1110
final class FileSessionConfig implements SessionConfig
1211
{
12+
/**
13+
* @param string $path Path to the sessions storage directory, relative to the internal storage.
14+
* @param Duration $expiration Time required for a session to expire.
15+
*/
1316
public function __construct(
14-
/**
15-
* Path to the sessions storage directory, relative to the internal storage.
16-
*/
17-
public string $path,
18-
1917
public Duration $expiration,
18+
public string $path = 'sessions',
2019
) {}
2120

2221
public function createManager(Container $container): FileSessionManager

packages/http/src/Session/Config/session.config.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55

66
return new FileSessionConfig(
77
path: 'sessions',
8-
expiration: Duration::hours(10),
8+
expiration: Duration::days(30),
99
);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Http\Session\Installer;
6+
7+
use Tempest\Database\MigratesUp;
8+
use Tempest\Database\QueryStatement;
9+
use Tempest\Database\QueryStatements\CreateTableStatement;
10+
use Tempest\Discovery\SkipDiscovery;
11+
12+
#[SkipDiscovery]
13+
final class CreateSessionsTable implements MigratesUp
14+
{
15+
private(set) string $name = '0000-00-00_create_sessions_table';
16+
17+
public function up(): QueryStatement
18+
{
19+
return new CreateTableStatement('sessions')
20+
->primary('id')
21+
->string('session_id')
22+
->text('data')
23+
->datetime('created_at')
24+
->datetime('last_active_at')
25+
->index('session_id');
26+
}
27+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Http\Session\Installer;
6+
7+
use Tempest\Console\Console;
8+
use Tempest\Console\Input\ConsoleArgumentBag;
9+
use Tempest\Container\Container;
10+
use Tempest\Core\Installer;
11+
use Tempest\Core\PublishesFiles;
12+
use Tempest\Database\Migrations\MigrationManager;
13+
14+
use function Tempest\root_path;
15+
use function Tempest\src_path;
16+
use function Tempest\Support\Namespace\to_fqcn;
17+
18+
final class DatabaseSessionInstaller implements Installer
19+
{
20+
use PublishesFiles;
21+
22+
private(set) string $name = 'sessions:database';
23+
24+
public function __construct(
25+
private readonly MigrationManager $migrationManager,
26+
private readonly Container $container,
27+
private readonly Console $console,
28+
private readonly ConsoleArgumentBag $consoleArgumentBag,
29+
) {}
30+
31+
public function install(): void
32+
{
33+
$migration = $this->publish(
34+
source: __DIR__ . '/CreateSessionsTable.stub.php',
35+
destination: src_path('Sessions/CreateSessionsTable.php'),
36+
);
37+
38+
$this->publish(
39+
source: __DIR__ . '/session.config.stub.php',
40+
destination: src_path('Sessions/session.config.php'),
41+
);
42+
43+
$this->publishImports();
44+
45+
if ($migration && $this->shouldMigrate()) {
46+
$this->migrationManager->executeUp(
47+
migration: $this->container->get(to_fqcn($migration, root: root_path())),
48+
);
49+
}
50+
}
51+
52+
private function shouldMigrate(): bool
53+
{
54+
$argument = $this->consoleArgumentBag->get('migrate');
55+
56+
if ($argument === null || ! is_bool($argument->value)) {
57+
return $this->console->confirm('Do you want to execute migrations?', default: false);
58+
}
59+
60+
return (bool) $argument->value;
61+
}
62+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
use Tempest\DateTime\Duration;
4+
use Tempest\Http\Session\Config\DatabaseSessionConfig;
5+
use Tempest\Http\Session\Models\DatabaseSession;
6+
7+
return new DatabaseSessionConfig(
8+
expiration: Duration::days(30),
9+
);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Http\Session\Managers;
6+
7+
use Tempest\Database\PrimaryKey;
8+
use Tempest\Database\Table;
9+
use Tempest\DateTime\DateTime;
10+
11+
#[Table('sessions')]
12+
final class DatabaseSession
13+
{
14+
public PrimaryKey $id;
15+
16+
public string $session_id;
17+
18+
public string $data;
19+
20+
public DateTime $created_at;
21+
22+
public DateTime $last_active_at;
23+
}

0 commit comments

Comments
 (0)