| 
<?phpnamespace Aws\DynamoDb;
 
 /**
 * Provides an interface for using Amazon DynamoDB as a session store by hooking
 * into PHP's session handler hooks. Once registered, You may use the native
 * `$_SESSION` superglobal and session functions, and the sessions will be
 * stored automatically in DynamoDB. DynamoDB is a great session storage
 * solution due to its speed, scalability, and fault tolerance.
 *
 * For maximum performance, we recommend that you keep the size of your sessions
 * small. Locking is disabled by default, since it can drive up latencies and
 * costs under high traffic. Only turn it on if you need it.
 *
 * By far, the most expensive operation is garbage collection. Therefore, we
 * encourage you to carefully consider your session garbage collection strategy.
 * Note: the DynamoDB Session Handler does not allow garbage collection to be
 * triggered randomly. You must run garbage collection manually or through other
 * automated means using a cron job or similar scheduling technique.
 */
 class SessionHandler implements \SessionHandlerInterface
 {
 /** @var SessionConnectionInterface Session save logic.*/
 private $connection;
 
 /** @var string Session save path. */
 private $savePath;
 
 /** @var string Session name. */
 private $sessionName;
 
 /** @var string The last known session ID */
 private $openSessionId = '';
 
 /** @var string Stores serialized data for tracking changes. */
 private $dataRead = '';
 
 /** @var bool Keeps track of whether the session has been written. */
 private $sessionWritten = false;
 
 /**
 * Creates a new DynamoDB Session Handler.
 *
 * The configuration array accepts the following array keys and values:
 * - table_name:               Name of table to store the sessions.
 * - hash_key:                 Name of hash key in table. Default: "id".
 * - session_lifetime:         Lifetime of inactive sessions expiration.
 * - consistent_read:          Whether or not to use consistent reads.
 * - batch_config:             Batch options used for garbage collection.
 * - locking:                  Whether or not to use session locking.
 * - max_lock_wait_time:       Max time (s) to wait for lock acquisition.
 * - min_lock_retry_microtime: Min time (µs) to wait between lock attempts.
 * - max_lock_retry_microtime: Max time (µs) to wait between lock attempts.
 *
 * @param DynamoDbClient $client Client for doing DynamoDB operations
 * @param array          $config Configuration for the Session Handler
 *
 * @return SessionHandler
 */
 public static function fromClient(DynamoDbClient $client, array $config = [])
 {
 $config += ['locking' => false];
 if ($config['locking']) {
 $connection = new LockingSessionConnection($client, $config);
 } else {
 $connection = new StandardSessionConnection($client, $config);
 }
 
 return new static($connection);
 }
 
 /**
 * @param SessionConnectionInterface $connection
 */
 public function __construct(SessionConnectionInterface $connection)
 {
 $this->connection = $connection;
 }
 
 /**
 * Register the DynamoDB session handler.
 *
 * @return bool Whether or not the handler was registered.
 * @codeCoverageIgnore
 */
 public function register()
 {
 return session_set_save_handler($this, true);
 }
 
 /**
 * Open a session for writing. Triggered by session_start().
 *
 * @param string $savePath    Session save path.
 * @param string $sessionName Session name.
 *
 * @return bool Whether or not the operation succeeded.
 */
 public function open($savePath, $sessionName)
 {
 $this->savePath = $savePath;
 $this->sessionName = $sessionName;
 
 return true;
 }
 
 /**
 * Close a session from writing.
 *
 * @return bool Success
 */
 public function close()
 {
 $id = session_id();
 // Make sure the session is unlocked and the expiration time is updated,
 // even if the write did not occur
 if ($this->openSessionId !== $id || !$this->sessionWritten) {
 $result = $this->connection->write($this->formatId($id), '', false);
 $this->sessionWritten = (bool) $result;
 }
 
 return $this->sessionWritten;
 }
 
 /**
 * Read a session stored in DynamoDB.
 *
 * @param string $id Session ID.
 *
 * @return string Session data.
 */
 public function read($id)
 {
 $this->openSessionId = $id;
 // PHP expects an empty string to be returned from this method if no
 // data is retrieved
 $this->dataRead = '';
 
 // Get session data using the selected locking strategy
 $item = $this->connection->read($this->formatId($id));
 
 // Return the data if it is not expired. If it is expired, remove it
 if (isset($item['expires']) && isset($item['data'])) {
 $this->dataRead = $item['data'];
 if ($item['expires'] <= time()) {
 $this->dataRead = '';
 $this->destroy($id);
 }
 }
 
 return $this->dataRead;
 }
 
 /**
 * Write a session to DynamoDB.
 *
 * @param string $id   Session ID.
 * @param string $data Serialized session data to write.
 *
 * @return bool Whether or not the operation succeeded.
 */
 public function write($id, $data)
 {
 $changed = $id !== $this->openSessionId
 || $data !== $this->dataRead;
 $this->openSessionId = $id;
 
 // Write the session data using the selected locking strategy
 $this->sessionWritten = $this->connection
 ->write($this->formatId($id), $data, $changed);
 
 return $this->sessionWritten;
 }
 
 /**
 * Delete a session stored in DynamoDB.
 *
 * @param string $id Session ID.
 *
 * @return bool Whether or not the operation succeeded.
 */
 public function destroy($id)
 {
 $this->openSessionId = $id;
 // Delete the session data using the selected locking strategy
 $this->sessionWritten
 = $this->connection->delete($this->formatId($id));
 
 return $this->sessionWritten;
 }
 
 /**
 * Satisfies the session handler interface, but does nothing. To do garbage
 * collection, you must manually call the garbageCollect() method.
 *
 * @param int $maxLifetime Ignored.
 *
 * @return bool Whether or not the operation succeeded.
 * @codeCoverageIgnore
 */
 public function gc($maxLifetime)
 {
 // Garbage collection for a DynamoDB table must be triggered manually.
 return true;
 }
 
 /**
 * Triggers garbage collection on expired sessions.
 * @codeCoverageIgnore
 */
 public function garbageCollect()
 {
 $this->connection->deleteExpired();
 }
 
 /**
 * Prepend the session ID with the session name.
 *
 * @param string $id The session ID.
 *
 * @return string Prepared session ID.
 */
 private function formatId($id)
 {
 return trim($this->sessionName . '_' . $id, '_');
 }
 }
 
 |