mindplay / stately
Session abstraction for PSR-7
Installs: 47
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/mindplay/stately
Requires
- php: >=5.5.4
- psr/http-message: ^1
Requires (Dev)
- guzzlehttp/guzzle: ~6
- mindplay/testies: dev-master
- phpunit/php-code-coverage: 2.*@dev
- zendframework/zend-diactoros: ~1
- zendframework/zend-stratigility: ~1
This package is auto-updated.
Last update: 2021-06-05 13:50:35 UTC
README
This library implements a simple session abstraction that integrates with a PSR-7 HTTP abstraction, to replace PHP's built-in session management.
This means you will not be using $_SESSION or the session_*() functions.
The API approach relies on type-hinted closures as a means of creating/updating session state. In practice, this means every session variable will be an object, which means your session state will really be exposed to you as a model, not as an array or a key/value store; which is great for IDE support and for code comprehension in general - for example:
$product_id = 123; $amount = 4; $session->update(function (ShoppingCart $cart) use ($product_id, $amount) { $cart->items[] = new CartItem($product_id, $amount); });
Note that the ShoppingCart model is automatically created, if it isn't already
present in the session data. As such, every session model is a "singleton", as
far as session state goes, though not in the sense that you can't create more
than one instance in a test-suite.
This is analogous to e.g. $_SESSION['ShoppingCart'] = new ShoppingCart(), but
eliminates the unsafe use of arrays and strings, the need to test if the session
variable is already set, the need to type-hint when reading from $_SESSION, and
so on.
If you don't want to auto-create a missing model, you can type-hint the argument
as e.g. ShoppingCart $cart = null - of course, this implies you're going to
handle a missing $cart in your callback with an if-statement.
Also note, it's possible to use multiple arguments to obtain multiple session models in a single call.
You can also remove() session models from the current session, or clear() the
entire contents of the container - see the SessionContainer
interface for documentation.
Implementation
You should create SessionService centrally (usually in a DI container) and
expose it to consumers (usually controllers) as SessionContainer, which is
the public portion of the interface. The internal portion of the interface
includes the commit() method, which should be called centrally, as part of
your request/response pipeline (middleware, framework or front controller),
e.g. immediately after the controller has been dispatched, before you emit
the response.
Example
Note that auto-creation implies that your session models must always have an
empty constructor - which implies it must have no hard dependencies. Try to view
this as "a good thing" - it means you won't be tempted to do things like adding
a getUser() method for a $user_id property, which would imply a dependency
on a UserRepository of some sort.
This is called "feature envy", and it's an anti-pattern - you're trying to provide a more convenient way to interact with the model, which would be great, except your model is now really a service and not just a model, even if it is internally using a repository to provide that service.
The clean approach to this problem, is a separate user service, which internally
uses the session container to interact with the session model - the intention is
is not for a consumer to reach into $_SESSION for your component's session data,
just as the expectation is not that a consumer directly accesses your session
model; but rather to encapsulate that exchange privately inside a service.
For example:
class ActiveUser { /** @var int|null active user ID (or NULL, if no User is logged in) */ public $user_id; } class UserService { private $container; private $repo; public function __construct(SessionContainer $container, UserRepository $repo) { $this->container = $container; $this->repo = $repo; } /** * @return User */ public function getUser() { $user_id = $this->container->update(function (ActiveUser $user) { return $user->user_id; }); return $user_id ? $this->repo->getUserById($user_id) : null; } }
The User model is now conveniently exposed by the UserService, which internally
uses the ActiveUser model for session persistence.