<?php 
namespace 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, '_'); 
    } 
} 
 
 |