SQLite3Cache.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <?php
  2. namespace Doctrine\Common\Cache;
  3. use SQLite3;
  4. use SQLite3Result;
  5. use function array_search;
  6. use function implode;
  7. use function serialize;
  8. use function sprintf;
  9. use function time;
  10. use function unserialize;
  11. use const SQLITE3_ASSOC;
  12. use const SQLITE3_BLOB;
  13. use const SQLITE3_TEXT;
  14. /**
  15. * SQLite3 cache provider.
  16. *
  17. * @deprecated Deprecated without replacement in doctrine/cache 1.11. This class will be dropped in 2.0
  18. */
  19. class SQLite3Cache extends CacheProvider
  20. {
  21. /**
  22. * The ID field will store the cache key.
  23. */
  24. public const ID_FIELD = 'k';
  25. /**
  26. * The data field will store the serialized PHP value.
  27. */
  28. public const DATA_FIELD = 'd';
  29. /**
  30. * The expiration field will store a date value indicating when the
  31. * cache entry should expire.
  32. */
  33. public const EXPIRATION_FIELD = 'e';
  34. /** @var SQLite3 */
  35. private $sqlite;
  36. /** @var string */
  37. private $table;
  38. /**
  39. * Calling the constructor will ensure that the database file and table
  40. * exist and will create both if they don't.
  41. *
  42. * @param string $table
  43. */
  44. public function __construct(SQLite3 $sqlite, $table)
  45. {
  46. $this->sqlite = $sqlite;
  47. $this->table = (string) $table;
  48. $this->ensureTableExists();
  49. }
  50. private function ensureTableExists(): void
  51. {
  52. $this->sqlite->exec(
  53. sprintf(
  54. 'CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY NOT NULL, %s BLOB, %s INTEGER)',
  55. $this->table,
  56. static::ID_FIELD,
  57. static::DATA_FIELD,
  58. static::EXPIRATION_FIELD
  59. )
  60. );
  61. }
  62. /**
  63. * {@inheritdoc}
  64. */
  65. protected function doFetch($id)
  66. {
  67. $item = $this->findById($id);
  68. if (! $item) {
  69. return false;
  70. }
  71. return unserialize($item[self::DATA_FIELD]);
  72. }
  73. /**
  74. * {@inheritdoc}
  75. */
  76. protected function doContains($id)
  77. {
  78. return $this->findById($id, false) !== null;
  79. }
  80. /**
  81. * {@inheritdoc}
  82. */
  83. protected function doSave($id, $data, $lifeTime = 0)
  84. {
  85. $statement = $this->sqlite->prepare(sprintf(
  86. 'INSERT OR REPLACE INTO %s (%s) VALUES (:id, :data, :expire)',
  87. $this->table,
  88. implode(',', $this->getFields())
  89. ));
  90. $statement->bindValue(':id', $id);
  91. $statement->bindValue(':data', serialize($data), SQLITE3_BLOB);
  92. $statement->bindValue(':expire', $lifeTime > 0 ? time() + $lifeTime : null);
  93. return $statement->execute() instanceof SQLite3Result;
  94. }
  95. /**
  96. * {@inheritdoc}
  97. */
  98. protected function doDelete($id)
  99. {
  100. [$idField] = $this->getFields();
  101. $statement = $this->sqlite->prepare(sprintf(
  102. 'DELETE FROM %s WHERE %s = :id',
  103. $this->table,
  104. $idField
  105. ));
  106. $statement->bindValue(':id', $id);
  107. return $statement->execute() instanceof SQLite3Result;
  108. }
  109. /**
  110. * {@inheritdoc}
  111. */
  112. protected function doFlush()
  113. {
  114. return $this->sqlite->exec(sprintf('DELETE FROM %s', $this->table));
  115. }
  116. /**
  117. * {@inheritdoc}
  118. */
  119. protected function doGetStats()
  120. {
  121. // no-op.
  122. }
  123. /**
  124. * Find a single row by ID.
  125. *
  126. * @param mixed $id
  127. *
  128. * @return mixed[]|null
  129. */
  130. private function findById($id, bool $includeData = true): ?array
  131. {
  132. [$idField] = $fields = $this->getFields();
  133. if (! $includeData) {
  134. $key = array_search(static::DATA_FIELD, $fields);
  135. unset($fields[$key]);
  136. }
  137. $statement = $this->sqlite->prepare(sprintf(
  138. 'SELECT %s FROM %s WHERE %s = :id LIMIT 1',
  139. implode(',', $fields),
  140. $this->table,
  141. $idField
  142. ));
  143. $statement->bindValue(':id', $id, SQLITE3_TEXT);
  144. $item = $statement->execute()->fetchArray(SQLITE3_ASSOC);
  145. if ($item === false) {
  146. return null;
  147. }
  148. if ($this->isExpired($item)) {
  149. $this->doDelete($id);
  150. return null;
  151. }
  152. return $item;
  153. }
  154. /**
  155. * Gets an array of the fields in our table.
  156. *
  157. * @psalm-return array{string, string, string}
  158. */
  159. private function getFields(): array
  160. {
  161. return [static::ID_FIELD, static::DATA_FIELD, static::EXPIRATION_FIELD];
  162. }
  163. /**
  164. * Check if the item is expired.
  165. *
  166. * @param mixed[] $item
  167. */
  168. private function isExpired(array $item): bool
  169. {
  170. return isset($item[static::EXPIRATION_FIELD]) &&
  171. $item[self::EXPIRATION_FIELD] !== null &&
  172. $item[self::EXPIRATION_FIELD] < time();
  173. }
  174. }