Intent component¶
Some use cases require a state to be preserved between two stateless requests. Password reset is a typical example: user requests a password reset, receives an email containing a link and, at some point in time, clicks on that link in order to complete the process which has been started within some earlier request.
Session storage is not always an option here, since the request which completes the use case may originate from a different browser, a different device, or even a different machine.
This library allows you to store an object into a persistent storage and to retrieve it later by using a randomly generated identifier which you may safely put into an URL.
Features¶
Store any serializable object and retrieve it later by using its identifier.
Time to live is defined per intent, after which intent is no longer available and is removed from the storage.
Deferred availability allows you to store an intent which becomes available at some moment in the future.
Invalidated on read by default, so a single intent may be used only once, which is a sane default for one time links.
Doctrine Dbal and PSR-6 storages are provided out of the box, while other storages may be added with ease.
Symfony ready via dedicated
runopencode/intent-bundlepackage, see Intent Bundle for integration details.
Table of Contents¶
Quick example¶
Assume that you are implementing a password reset. Within the request which initiates the process, you will store an intent describing what has to be done and send its identifier to the user:
1<?php
2
3declare(strict_types=1);
4
5namespace App\Security\Controller;
6
7use App\Security\Intent\ResetPassword;
8use RunOpenCode\Component\Intent\Contract\IntentStorageInterface;
9
10final readonly class RequestPasswordResetController
11{
12 public function __construct(private IntentStorageInterface $storage)
13 {
14 // noop.
15 }
16
17 public function __invoke(User $user): Response
18 {
19 // Intent is valid for one hour only.
20 $identifier = $this->storage->store(new ResetPassword($user->getId()), 3600);
21
22 $this->mailer->send(new PasswordResetEmail($user, (string)$identifier));
23
24 // ...
25 }
26}
Within the request which completes the process, you will fetch the intent by using its identifier and proceed with the use case:
1<?php
2
3declare(strict_types=1);
4
5namespace App\Security\Controller;
6
7use RunOpenCode\Component\Intent\Contract\IntentStorageInterface;
8use RunOpenCode\Component\Intent\Exception\NotExistsException;
9use Symfony\Component\Uid\Ulid;
10
11final readonly class ResetPasswordController
12{
13 public function __construct(private IntentStorageInterface $storage)
14 {
15 // noop.
16 }
17
18 public function __invoke(Ulid $identifier): Response
19 {
20 try {
21 /** @var ResetPassword $intent */
22 $intent = $this->storage->fetch($identifier);
23 } catch (NotExistsException) {
24 // Link is invalid, expired, or it has been used already.
25 // ...
26 }
27
28 // ...
29 }
30}
Note that intent is invalidated as soon as it is fetched, which means that the link which you have sent to the user may be used only once.
Password reset is just one example, intents may be used for various cases where session storage can not, or should not be used.