1<?php 2/* 3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 * 15 * This software consists of voluntary contributions made by many individuals 16 * and is licensed under the MIT license. For more information, see 17 * <http://www.doctrine-project.org>. 18 */ 19 20namespace Doctrine\Common\Cache; 21 22use SQLite3; 23use SQLite3Result; 24 25/** 26 * SQLite3 cache provider. 27 * 28 * @since 1.4 29 * @author Jake Bell <jake@theunraveler.com> 30 */ 31class SQLite3Cache extends CacheProvider 32{ 33 /** 34 * The ID field will store the cache key. 35 */ 36 const ID_FIELD = 'k'; 37 38 /** 39 * The data field will store the serialized PHP value. 40 */ 41 const DATA_FIELD = 'd'; 42 43 /** 44 * The expiration field will store a date value indicating when the 45 * cache entry should expire. 46 */ 47 const EXPIRATION_FIELD = 'e'; 48 49 /** 50 * @var SQLite3 51 */ 52 private $sqlite; 53 54 /** 55 * @var string 56 */ 57 private $table; 58 59 /** 60 * Constructor. 61 * 62 * Calling the constructor will ensure that the database file and table 63 * exist and will create both if they don't. 64 * 65 * @param SQLite3 $sqlite 66 * @param string $table 67 */ 68 public function __construct(SQLite3 $sqlite, $table) 69 { 70 $this->sqlite = $sqlite; 71 $this->table = (string) $table; 72 73 list($id, $data, $exp) = $this->getFields(); 74 75 return $this->sqlite->exec(sprintf( 76 'CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY NOT NULL, %s BLOB, %s INTEGER)', 77 $table, 78 $id, 79 $data, 80 $exp 81 )); 82 } 83 84 /** 85 * {@inheritdoc} 86 */ 87 protected function doFetch($id) 88 { 89 if ($item = $this->findById($id)) { 90 return unserialize($item[self::DATA_FIELD]); 91 } 92 93 return false; 94 } 95 96 /** 97 * {@inheritdoc} 98 */ 99 protected function doContains($id) 100 { 101 return null !== $this->findById($id, false); 102 } 103 104 /** 105 * {@inheritdoc} 106 */ 107 protected function doSave($id, $data, $lifeTime = 0) 108 { 109 $statement = $this->sqlite->prepare(sprintf( 110 'INSERT OR REPLACE INTO %s (%s) VALUES (:id, :data, :expire)', 111 $this->table, 112 implode(',', $this->getFields()) 113 )); 114 115 $statement->bindValue(':id', $id); 116 $statement->bindValue(':data', serialize($data), SQLITE3_BLOB); 117 $statement->bindValue(':expire', $lifeTime > 0 ? time() + $lifeTime : null); 118 119 return $statement->execute() instanceof SQLite3Result; 120 } 121 122 /** 123 * {@inheritdoc} 124 */ 125 protected function doDelete($id) 126 { 127 list($idField) = $this->getFields(); 128 129 $statement = $this->sqlite->prepare(sprintf( 130 'DELETE FROM %s WHERE %s = :id', 131 $this->table, 132 $idField 133 )); 134 135 $statement->bindValue(':id', $id); 136 137 return $statement->execute() instanceof SQLite3Result; 138 } 139 140 /** 141 * {@inheritdoc} 142 */ 143 protected function doFlush() 144 { 145 return $this->sqlite->exec(sprintf('DELETE FROM %s', $this->table)); 146 } 147 148 /** 149 * {@inheritdoc} 150 */ 151 protected function doGetStats() 152 { 153 // no-op. 154 } 155 156 /** 157 * Find a single row by ID. 158 * 159 * @param mixed $id 160 * @param bool $includeData 161 * 162 * @return array|null 163 */ 164 private function findById($id, $includeData = true) 165 { 166 list($idField) = $fields = $this->getFields(); 167 168 if (!$includeData) { 169 $key = array_search(static::DATA_FIELD, $fields); 170 unset($fields[$key]); 171 } 172 173 $statement = $this->sqlite->prepare(sprintf( 174 'SELECT %s FROM %s WHERE %s = :id LIMIT 1', 175 implode(',', $fields), 176 $this->table, 177 $idField 178 )); 179 180 $statement->bindValue(':id', $id, SQLITE3_TEXT); 181 182 $item = $statement->execute()->fetchArray(SQLITE3_ASSOC); 183 184 if ($item === false) { 185 return null; 186 } 187 188 if ($this->isExpired($item)) { 189 $this->doDelete($id); 190 191 return null; 192 } 193 194 return $item; 195 } 196 197 /** 198 * Gets an array of the fields in our table. 199 * 200 * @return array 201 */ 202 private function getFields() 203 { 204 return array(static::ID_FIELD, static::DATA_FIELD, static::EXPIRATION_FIELD); 205 } 206 207 /** 208 * Check if the item is expired. 209 * 210 * @param array $item 211 * 212 * @return bool 213 */ 214 private function isExpired(array $item) 215 { 216 return isset($item[static::EXPIRATION_FIELD]) && 217 $item[self::EXPIRATION_FIELD] !== null && 218 $item[self::EXPIRATION_FIELD] < time(); 219 } 220} 221