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