joefallon / phpsession
A simple age and activity based session library.
Installs: 131
Dependents: 2
Suggesters: 0
Security: 0
Stars: 2
Watchers: 2
Forks: 0
Open Issues: 0
pkg:composer/joefallon/phpsession
Requires
- php: >=7.4.0
Requires (Dev)
- phpunit/phpunit: ^9.6
README
phpsession is a small, well-tested PHP library that wraps the PHP session API and provides two common session-expiration strategies:
- max age (time since the session was created)
- last-activity timeout (time since the session was last accessed)
It is intentionally tiny, dependency-free, and designed for safe use in web applications where predictable session-expiration and non-blocking session access are important.
Why use phpsession?
- Small and focused: one class that does one job well.
- Non-blocking-friendly: the implementation opens the session only for the duration of an operation and closes it immediately afterwards, which reduces the chance of session-lock contention in concurrent requests (for example, AJAX-heavy frontends).
- Well tested: comes with unit tests that exercise the public API and edge cases.
- Composer-friendly and PSR-4 compatible.
Requirements
- PHP 7.4 or later
Installation
Install with Composer (recommended):
composer require joefallon/phpsession
or add this to your composer.json and run composer install:
{
"require": {
"joefallon/phpsession": "*"
}
}
Quick start
The class is namespaced under JoeFallon\PhpSession. Typical usage:
use JoeFallon\PhpSession\Session; $session = new Session(); $session->write('user_id', '42'); $userId = $session->read('user_id'); // Remove a key $session->unsetSessionValue('user_id'); // Destroy the whole session (and its cookie) $session->destroy();
Constructor and configuration
The constructor accepts two optional integer parameters (seconds):
__construct(int $maxAgeInSecs = Session::HOUR, int $lastActivityInSecs = Session::HOUR)$maxAgeInSecs— maximum age of the session since creation. Set to0to disable.$lastActivityInSecs— timeout measured from the last read/write/unset. Set to0to disable.
Note: The constructor validates inputs and throws InvalidArgumentException if
values are negative or if maxAgeInSecs is less than lastActivityInSecs.
This is deliberate: the maximum age must be at least as long as the activity
window.
Public API (methods)
-
public function read(string $key)- Return the stored value for
$keyornullif not present or if an empty key is passed. Reading updates the last-activity timestamp.
- Return the stored value for
-
public function write(string $key, string $val): void- Stores
$valunder$key. ThrowsInvalidArgumentExceptionif$keyis empty. Writing updates the last-activity timestamp.
- Stores
-
public function unsetSessionValue(string $key): void- Removes a session key (if present) and updates the last-activity timestamp.
-
public function isMaxAgeTimeoutExpired(): bool- Returns
truewhen the time since session creation is greater than or equal to the configured max age. On first call this method initializes the creation timestamp and will returnfalse.
- Returns
-
public function isLastActivityTimeoutExpired(): bool- Returns
truewhen the time since the last activity is greater than or equal to the configured activity timeout. On first call this method initializes the last-activity timestamp and will returnfalse.
- Returns
-
public function destroy(): void- Clears session data, removes the session cookie (if cookies are used), regenerates the session id to remove old data, and destroys the session.
Constants
Session::HOUR— 3600Session::DAY— 86400Session::WEEK— 604800
Detailed examples
- Short-lived session that expires after 30 seconds of inactivity:
$session = new Session(Session::HOUR, 30); $session->write('cart', ['sku' => 'x', 'qty' => 1]); // Later if ($session->isLastActivityTimeoutExpired()) { // Force re-login or refresh the session }
- Max-age-only session (ignore activity):
// Max age 24 hours, activity never expires $session = new Session(Session::DAY, 0);
- Checking and rotating sessions safely
If you use isMaxAgeTimeoutExpired() or isLastActivityTimeoutExpired() in
request logic, prefer to let the session management decide when to destroy the
session. Example flow:
$session = new Session(); if ($session->isMaxAgeTimeoutExpired() || $session->isLastActivityTimeoutExpired()) { // Tear down and force a re-auth $session->destroy(); // redirect to login }
Security and best practices
- Use secure cookies (HTTPS) and set
session.cookie_secure=1in PHP when serving over TLS. - Consider setting
session.cookie_httponly=1to reduce the risk of XSS-based cookie theft. - Regenerate session ids on privilege changes (e.g. login) using
session_regenerate_id(true)in your authentication code path. This library intentionally does not regenerate an id on every open to keep behavior predictable and to avoid interfering with timeout logic. - Store only non-sensitive identifiers in sessions when possible. If you must store secrets, ensure your session storage is protected (e.g. server-side storage, not client-supplied).
Performance considerations
- This library opens and closes the session around each operation which helps avoid long-lived session locks in high-concurrency environments (AJAX requests, concurrent fetches).
- Avoid storing extremely large datasets in sessions to reduce IO and lock durations.
Testing
This project uses PHPUnit 9.6 for unit tests (the last PHPUnit series that
supports PHP 7.4). The repository includes a phpunit.xml.dist and a
tests/bootstrap.php bootstrap file.
Install dev dependencies (recommended):
Unix/macOS:
composer install --no-interaction --prefer-dist
Windows (cmd.exe / PowerShell):
composer install --no-interaction --prefer-dist
Run the test suite:
Unix/macOS:
./vendor/bin/phpunit --configuration phpunit.xml.dist
Windows (cmd.exe / PowerShell):
vendor\bin\phpunit --configuration phpunit.xml.dist
Notes and troubleshooting
-
If you previously used the project's legacy KissTest runner, it has been replaced by PHPUnit as part of the migration. There is no need to run
php tests/index.phpfor automated test runs anymore. -
If you see messages from Xdebug such as "Could not connect to debugging client" or other output before tests run, those messages can cause
session_start()to fail because headers were already sent. The test bootstrap starts output buffering and theSessionclass includes a safe emulation fallback for environments where headers are already sent to keep tests deterministic. If you still encounter issues:- Temporarily disable Xdebug while running tests, or
- Ensure your terminal environment is not emitting extra startup output.
-
To run a single test file or method, supply the path and optionally a filter, for example:
./vendor/bin/phpunit tests/JoeFallon/PhpSession/SessionTest.php
# or run a single test method
./vendor/bin/phpunit --filter test_read_write tests/JoeFallon/PhpSession/SessionTest.php
Continuous Integration
Example GitHub Actions job snippet (runs on Ubuntu):
steps: - uses: actions/checkout@v3 - name: Set up PHP uses: shivammathur/setup-php@v2 with: php-version: '7.4' - name: Install dependencies run: composer install --no-progress --no-suggest --prefer-dist - name: Run tests run: vendor/bin/phpunit --configuration phpunit.xml.dist
Contributing
Contributions are welcome. A good contribution path:
- Fork the repository.
- Create a small, focused branch for your change.
- Add tests that cover the behavior you change or add.
- Run the test suite and ensure everything is green.
- Open a pull request with a clear description of the change.
License
MIT — see the LICENSE file for details.
Acknowledgements
Built by Joe Fallon. The test framework was migrated from the small KissTest runner to PHPUnit 9.6 to keep compatibility with PHP 7.4; thanks to the authors of both projects.
Contact
If you have questions or need help integrating phpsession, open an issue on GitHub: https://github.com/joefallon/phpsession
Built with ❤️ and the KISS principle.