Passed
Push — master ( b2988e...9f380c )
by Jonathan
33:05
created

SQLite3Cache::findById()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

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