1 |
efrain |
1 |
<?php
|
|
|
2 |
namespace Aws\DynamoDb;
|
|
|
3 |
|
|
|
4 |
/**
|
|
|
5 |
* Provides an interface for using Amazon DynamoDB as a session store by hooking
|
|
|
6 |
* into PHP's session handler hooks. Once registered, You may use the native
|
|
|
7 |
* `$_SESSION` superglobal and session functions, and the sessions will be
|
|
|
8 |
* stored automatically in DynamoDB. DynamoDB is a great session storage
|
|
|
9 |
* solution due to its speed, scalability, and fault tolerance.
|
|
|
10 |
*
|
|
|
11 |
* For maximum performance, we recommend that you keep the size of your sessions
|
|
|
12 |
* small. Locking is disabled by default, since it can drive up latencies and
|
|
|
13 |
* costs under high traffic. Only turn it on if you need it.
|
|
|
14 |
*
|
|
|
15 |
* By far, the most expensive operation is garbage collection. Therefore, we
|
|
|
16 |
* encourage you to carefully consider your session garbage collection strategy.
|
|
|
17 |
* Note: the DynamoDB Session Handler does not allow garbage collection to be
|
|
|
18 |
* triggered randomly. You must run garbage collection manually or through other
|
|
|
19 |
* automated means using a cron job or similar scheduling technique.
|
|
|
20 |
*/
|
|
|
21 |
class SessionHandler implements \SessionHandlerInterface
|
|
|
22 |
{
|
|
|
23 |
/** @var SessionConnectionInterface Session save logic.*/
|
|
|
24 |
private $connection;
|
|
|
25 |
|
|
|
26 |
/** @var string Session save path. */
|
|
|
27 |
private $savePath;
|
|
|
28 |
|
|
|
29 |
/** @var string Session name. */
|
|
|
30 |
private $sessionName;
|
|
|
31 |
|
|
|
32 |
/** @var string The last known session ID */
|
|
|
33 |
private $openSessionId = '';
|
|
|
34 |
|
|
|
35 |
/** @var string Stores serialized data for tracking changes. */
|
|
|
36 |
private $dataRead = '';
|
|
|
37 |
|
|
|
38 |
/** @var bool Keeps track of whether the session has been written. */
|
|
|
39 |
private $sessionWritten = false;
|
|
|
40 |
|
|
|
41 |
/**
|
|
|
42 |
* Creates a new DynamoDB Session Handler.
|
|
|
43 |
*
|
|
|
44 |
* The configuration array accepts the following array keys and values:
|
|
|
45 |
* - table_name: Name of table to store the sessions.
|
|
|
46 |
* - hash_key: Name of hash key in table. Default: "id".
|
|
|
47 |
* - data_attribute: Name of the data attribute in table. Default: "data".
|
|
|
48 |
* - session_lifetime: Lifetime of inactive sessions expiration.
|
|
|
49 |
* - session_lifetime_attribute: Name of the session life time attribute in table. Default: "expires".
|
|
|
50 |
* - consistent_read: Whether or not to use consistent reads.
|
|
|
51 |
* - batch_config: Batch options used for garbage collection.
|
|
|
52 |
* - locking: Whether or not to use session locking.
|
|
|
53 |
* - max_lock_wait_time: Max time (s) to wait for lock acquisition.
|
|
|
54 |
* - min_lock_retry_microtime: Min time (µs) to wait between lock attempts.
|
|
|
55 |
* - max_lock_retry_microtime: Max time (µs) to wait between lock attempts.
|
|
|
56 |
*
|
|
|
57 |
* You can find the full list of parameters and defaults within the trait
|
|
|
58 |
* Aws\DynamoDb\SessionConnectionConfigTrait
|
|
|
59 |
*
|
|
|
60 |
* @param DynamoDbClient $client Client for doing DynamoDB operations
|
|
|
61 |
* @param array $config Configuration for the Session Handler
|
|
|
62 |
*
|
|
|
63 |
* @return SessionHandler
|
|
|
64 |
*/
|
|
|
65 |
public static function fromClient(DynamoDbClient $client, array $config = [])
|
|
|
66 |
{
|
|
|
67 |
$config += ['locking' => false];
|
|
|
68 |
if ($config['locking']) {
|
|
|
69 |
$connection = new LockingSessionConnection($client, $config);
|
|
|
70 |
} else {
|
|
|
71 |
$connection = new StandardSessionConnection($client, $config);
|
|
|
72 |
}
|
|
|
73 |
|
|
|
74 |
return new static($connection);
|
|
|
75 |
}
|
|
|
76 |
|
|
|
77 |
/**
|
|
|
78 |
* @param SessionConnectionInterface $connection
|
|
|
79 |
*/
|
|
|
80 |
public function __construct(SessionConnectionInterface $connection)
|
|
|
81 |
{
|
|
|
82 |
$this->connection = $connection;
|
|
|
83 |
}
|
|
|
84 |
|
|
|
85 |
/**
|
|
|
86 |
* Register the DynamoDB session handler.
|
|
|
87 |
*
|
|
|
88 |
* @return bool Whether or not the handler was registered.
|
|
|
89 |
* @codeCoverageIgnore
|
|
|
90 |
*/
|
|
|
91 |
public function register()
|
|
|
92 |
{
|
|
|
93 |
return session_set_save_handler($this, true);
|
|
|
94 |
}
|
|
|
95 |
|
|
|
96 |
/**
|
|
|
97 |
* Open a session for writing. Triggered by session_start().
|
|
|
98 |
*
|
|
|
99 |
* @param string $savePath Session save path.
|
|
|
100 |
* @param string $sessionName Session name.
|
|
|
101 |
*
|
|
|
102 |
* @return bool Whether or not the operation succeeded.
|
|
|
103 |
*/
|
|
|
104 |
#[\ReturnTypeWillChange]
|
|
|
105 |
public function open($savePath, $sessionName)
|
|
|
106 |
{
|
|
|
107 |
$this->savePath = $savePath;
|
|
|
108 |
$this->sessionName = $sessionName;
|
|
|
109 |
|
|
|
110 |
return true;
|
|
|
111 |
}
|
|
|
112 |
|
|
|
113 |
/**
|
|
|
114 |
* Close a session from writing.
|
|
|
115 |
*
|
|
|
116 |
* @return bool Success
|
|
|
117 |
*/
|
|
|
118 |
#[\ReturnTypeWillChange]
|
|
|
119 |
public function close()
|
|
|
120 |
{
|
|
|
121 |
$id = session_id();
|
|
|
122 |
// Make sure the session is unlocked and the expiration time is updated,
|
|
|
123 |
// even if the write did not occur
|
|
|
124 |
if ($this->openSessionId !== $id || !$this->sessionWritten) {
|
|
|
125 |
$result = $this->connection->write($this->formatId($id), '', false);
|
|
|
126 |
$this->sessionWritten = (bool) $result;
|
|
|
127 |
}
|
|
|
128 |
|
|
|
129 |
return $this->sessionWritten;
|
|
|
130 |
}
|
|
|
131 |
|
|
|
132 |
/**
|
|
|
133 |
* Read a session stored in DynamoDB.
|
|
|
134 |
*
|
|
|
135 |
* @param string $id Session ID.
|
|
|
136 |
*
|
|
|
137 |
* @return string Session data.
|
|
|
138 |
*/
|
|
|
139 |
#[\ReturnTypeWillChange]
|
|
|
140 |
public function read($id)
|
|
|
141 |
{
|
|
|
142 |
$this->openSessionId = $id;
|
|
|
143 |
// PHP expects an empty string to be returned from this method if no
|
|
|
144 |
// data is retrieved
|
|
|
145 |
$this->dataRead = '';
|
|
|
146 |
|
|
|
147 |
// Get session data using the selected locking strategy
|
|
|
148 |
$item = $this->connection->read($this->formatId($id));
|
|
|
149 |
|
|
|
150 |
$dataAttribute = $this->connection->getDataAttribute();
|
|
|
151 |
$sessionLifetimeAttribute = $this->connection->getSessionLifetimeAttribute();
|
|
|
152 |
|
|
|
153 |
// Return the data if it is not expired. If it is expired, remove it
|
|
|
154 |
if (isset($item[$sessionLifetimeAttribute]) && isset($item[$dataAttribute])) {
|
|
|
155 |
$this->dataRead = $item[$dataAttribute];
|
|
|
156 |
if ($item[$sessionLifetimeAttribute] <= time()) {
|
|
|
157 |
$this->dataRead = '';
|
|
|
158 |
$this->destroy($id);
|
|
|
159 |
}
|
|
|
160 |
}
|
|
|
161 |
|
|
|
162 |
return $this->dataRead;
|
|
|
163 |
}
|
|
|
164 |
|
|
|
165 |
/**
|
|
|
166 |
* Write a session to DynamoDB.
|
|
|
167 |
*
|
|
|
168 |
* @param string $id Session ID.
|
|
|
169 |
* @param string $data Serialized session data to write.
|
|
|
170 |
*
|
|
|
171 |
* @return bool Whether or not the operation succeeded.
|
|
|
172 |
*/
|
|
|
173 |
#[\ReturnTypeWillChange]
|
|
|
174 |
public function write($id, $data)
|
|
|
175 |
{
|
|
|
176 |
$changed = $id !== $this->openSessionId
|
|
|
177 |
|| $data !== $this->dataRead;
|
|
|
178 |
$this->openSessionId = $id;
|
|
|
179 |
|
|
|
180 |
// Write the session data using the selected locking strategy
|
|
|
181 |
$this->sessionWritten = $this->connection
|
|
|
182 |
->write($this->formatId($id), $data, $changed);
|
|
|
183 |
|
|
|
184 |
return $this->sessionWritten;
|
|
|
185 |
}
|
|
|
186 |
|
|
|
187 |
/**
|
|
|
188 |
* Delete a session stored in DynamoDB.
|
|
|
189 |
*
|
|
|
190 |
* @param string $id Session ID.
|
|
|
191 |
*
|
|
|
192 |
* @return bool Whether or not the operation succeeded.
|
|
|
193 |
*/
|
|
|
194 |
#[\ReturnTypeWillChange]
|
|
|
195 |
public function destroy($id)
|
|
|
196 |
{
|
|
|
197 |
$this->openSessionId = $id;
|
|
|
198 |
// Delete the session data using the selected locking strategy
|
|
|
199 |
$this->sessionWritten
|
|
|
200 |
= $this->connection->delete($this->formatId($id));
|
|
|
201 |
|
|
|
202 |
return $this->sessionWritten;
|
|
|
203 |
}
|
|
|
204 |
|
|
|
205 |
/**
|
|
|
206 |
* Satisfies the session handler interface, but does nothing. To do garbage
|
|
|
207 |
* collection, you must manually call the garbageCollect() method.
|
|
|
208 |
*
|
|
|
209 |
* @param int $maxLifetime Ignored.
|
|
|
210 |
*
|
|
|
211 |
* @return bool Whether or not the operation succeeded.
|
|
|
212 |
* @codeCoverageIgnore
|
|
|
213 |
*/
|
|
|
214 |
#[\ReturnTypeWillChange]
|
|
|
215 |
public function gc($maxLifetime)
|
|
|
216 |
{
|
|
|
217 |
// Garbage collection for a DynamoDB table must be triggered manually.
|
|
|
218 |
return true;
|
|
|
219 |
}
|
|
|
220 |
|
|
|
221 |
/**
|
|
|
222 |
* Triggers garbage collection on expired sessions.
|
|
|
223 |
* @codeCoverageIgnore
|
|
|
224 |
*/
|
|
|
225 |
public function garbageCollect()
|
|
|
226 |
{
|
|
|
227 |
$this->connection->deleteExpired();
|
|
|
228 |
}
|
|
|
229 |
|
|
|
230 |
/**
|
|
|
231 |
* Prepend the session ID with the session name.
|
|
|
232 |
*
|
|
|
233 |
* @param string $id The session ID.
|
|
|
234 |
*
|
|
|
235 |
* @return string Prepared session ID.
|
|
|
236 |
*/
|
|
|
237 |
private function formatId($id)
|
|
|
238 |
{
|
|
|
239 |
return trim($this->sessionName . '_' . $id, '_');
|
|
|
240 |
}
|
|
|
241 |
}
|