Created
January 31, 2018 19:11
-
-
Save bmcminn/a63a72a4c1877cc5faa221302d833d52 to your computer and use it in GitHub Desktop.
A memoizer written in PHP that uses SQLite as it's persistence layer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * API Description | |
| * | |
| * # Memoize() utility | |
| * | |
| * ## Public Methods: | |
| * | |
| * getMemo($key) | |
| * setMemo($key, $data) | |
| * | |
| * | |
| * ## Basic workflow | |
| * | |
| * ```php | |
| * 1. Initialize a new Memo DB instance | |
| * $memo = new Memoizer([ | |
| * 'dbPath' => './cache', // default = './cache' | |
| * 'cacheBuffer' => 5, // default = 5 (in minutes) | |
| * ]'); | |
| * | |
| * // NOTE: dbPath defines a path relative to the current working directory | |
| * | |
| * $router->get('/route/{user}/{name}'), function($user, $name) { | |
| * | |
| * // 2. Inside your function, splice a string together using your API route components | |
| * $routeKey = "route_{$user}_{$name}"; | |
| * | |
| * // 3. Use $memo->getMemo($key) to get the record in question | |
| * $memo = $memo->getMemo($routeKey); | |
| * | |
| * if ($memo) { | |
| * return $memo; | |
| * } | |
| * | |
| * // 4. $memo->memoize() does an implicit check if the cache | |
| * }) | |
| * ``` | |
| * | |
| */ | |
| class Memoizer { | |
| private $pdo; | |
| private $dbPath; | |
| private $tableName; | |
| private $cacheBuffer; | |
| /** | |
| * [__construct description] | |
| * @param array $config Associative array for configuring each instance of Memoizer | |
| * @propertu integer cacheBuffer Time in minutes for the cache to be valid | |
| * @property string dbPath Location for where the SQLite DB should be kept | |
| */ | |
| function __construct(array $config = []) { | |
| $config = array_replace_recursive([ | |
| 'dbPath' => '../storage/framework/cache', | |
| 'cacheBuffer' => 5, // defaults to 5 minutes | |
| ], $config); | |
| // ensure dbPath is rotated daily to prevent SQLite DB overloads | |
| $this->dbPath = getcwd() . '/' . $config['dbPath'] . '/' . date('Y-m-d') . '.db'; | |
| $this->cacheBuffer = 60 * $config['cacheBuffer']; | |
| $this->tableName = 'memos'; | |
| $this->_initDB(); | |
| $this->_createTables(); | |
| return $this; | |
| } | |
| /** | |
| * [_initDB description] | |
| * @return [type] [description] | |
| */ | |
| private function _initDB() { | |
| if ($this->pdo == null) { | |
| // try { | |
| $this->pdo = new \PDO('sqlite:' . $this->dbPath); | |
| $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); | |
| // } catch (\PDOException $e) { | |
| // // TODO: handle error | |
| // } | |
| } | |
| } | |
| /** | |
| * [_createTables description] | |
| * @return [type] [description] | |
| */ | |
| private function _createTables() { | |
| $cmds = [ | |
| " | |
| CREATE TABLE IF NOT EXISTS $this->tableName ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| key TEXT NOT NULL UNIQUE, | |
| data TEXT NOT NULL, | |
| cache INTEGER NOT NULL | |
| ) | |
| " | |
| ]; | |
| foreach ($cmds as $cmd) { | |
| $this->pdo->exec($cmd); | |
| } | |
| } | |
| private function _bustCache() { | |
| $params = $this->_getQueryParams(); | |
| // determine if we're busting the cache | |
| return isset($params['resync']) ? true : false; | |
| } | |
| private function _getQueryParams() { | |
| // @sauce: https://stackoverflow.com/a/10298907/3708807 | |
| $https = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : 'off'; | |
| $protocol = ($https === 'on') ? 's://' : '://'; | |
| $url = 'http' . $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; | |
| $parts = parse_url($url); | |
| $params = []; | |
| if (isset($parts['query'])) { | |
| parse_str($parts['query'], $params); | |
| } | |
| return $params; | |
| } | |
| /** | |
| * [_getCurrentTime description] | |
| * @return [type] [description] | |
| */ | |
| private function _getCurrentTime() { | |
| return floor(microtime(true)); | |
| } | |
| /** | |
| * [getMemo description] | |
| * @param string $key [description] | |
| * @return [type] [description] | |
| */ | |
| public function getMemo(string $key) { | |
| $sql = " | |
| SELECT * | |
| FROM memos | |
| WHERE key = :key | |
| LIMIT 1 | |
| "; | |
| $stmt = $this->pdo->prepare($sql); | |
| $stmt->execute([ | |
| ':key' => $key | |
| ]); | |
| // fetch the memo record from the database | |
| $memo = $stmt->fetch(\PDO::FETCH_ASSOC); | |
| // if we're busting the cache | |
| if ($this->_bustCache()) { | |
| return false; | |
| } | |
| // if cache is stale | |
| if ($memo['cache'] + $this->cacheBuffer < $this->_getCurrentTime()) { | |
| return false; | |
| } | |
| return $memo; | |
| } | |
| /** | |
| * [setMemo description] | |
| * @param string $key [description] | |
| * @param [type] $data [description] | |
| */ | |
| public function setMemo(string $key, $data) { | |
| $data = json_encode($data, true); | |
| $sql = " | |
| INSERT OR REPLACE INTO {$this->tableName} (id, key, cache, data) | |
| VALUES ( | |
| coalesce((SELECT id FROM {$this->tableName} WHERE key=:key), null) | |
| , :key | |
| , round(:cache) | |
| , :data | |
| ) | |
| "; | |
| $stmt = $this->pdo->prepare($sql); | |
| $stmt->execute([ | |
| ':key' => $key | |
| , ':cache' => $this->_getCurrentTime() | |
| , ':data' => $data | |
| ]); | |
| return $this->getMemo($key); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment