Skip to content

Commit a7676ec

Browse files
committed
Fix database cache on PostgreSQL
1 parent 94b7ef9 commit a7676ec

File tree

2 files changed

+70
-4
lines changed

2 files changed

+70
-4
lines changed

src/Illuminate/Cache/DatabaseStore.php

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
use Closure;
66
use Exception;
7+
use Illuminate\Support\Str;
78
use Illuminate\Contracts\Cache\Store;
89
use Illuminate\Support\InteractsWithTime;
10+
use Illuminate\Database\PostgresConnection;
911
use Illuminate\Database\ConnectionInterface;
1012

1113
class DatabaseStore implements Store
@@ -78,7 +80,7 @@ public function get($key)
7880
return;
7981
}
8082

81-
return unserialize($cache->value);
83+
return $this->unserialize($cache->value);
8284
}
8385

8486
/**
@@ -93,7 +95,7 @@ public function put($key, $value, $minutes)
9395
{
9496
$key = $this->prefix.$key;
9597

96-
$value = serialize($value);
98+
$value = $this->serialize($value);
9799

98100
$expiration = $this->getTime() + (int) ($minutes * 60);
99101

@@ -157,7 +159,7 @@ protected function incrementOrDecrement($key, $value, Closure $callback)
157159

158160
$cache = is_array($cache) ? (object) $cache : $cache;
159161

160-
$current = unserialize($cache->value);
162+
$current = $this->unserialize($cache->value);
161163

162164
// Here we'll call this callback function that was given to the function which
163165
// is used to either increment or decrement the function. We use a callback
@@ -172,7 +174,7 @@ protected function incrementOrDecrement($key, $value, Closure $callback)
172174
// since database cache values are encrypted by default with secure storage
173175
// that can't be easily read. We will return the new value after storing.
174176
$this->table()->where('key', $prefixed)->update([
175-
'value' => serialize($new),
177+
'value' => $this->serialize($new),
176178
]);
177179

178180
return $new;
@@ -253,4 +255,36 @@ public function getPrefix()
253255
{
254256
return $this->prefix;
255257
}
258+
259+
/**
260+
* Serialize the given value.
261+
*
262+
* @param mixed $value
263+
* @return string
264+
*/
265+
protected function serialize($value)
266+
{
267+
$result = serialize($value);
268+
269+
if ($this->connection instanceof PostgresConnection && Str::contains($result, "\0")) {
270+
$result = base64_encode($result);
271+
}
272+
273+
return $result;
274+
}
275+
276+
/**
277+
* Unserialize the given value.
278+
*
279+
* @param string $value
280+
* @return mixed
281+
*/
282+
protected function unserialize($value)
283+
{
284+
if ($this->connection instanceof PostgresConnection && ! Str::contains($value, [':', ';'])) {
285+
$value = base64_decode($value);
286+
}
287+
288+
return unserialize($value);
289+
}
256290
}

tests/Cache/CacheDatabaseStoreTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ public function testDecryptedValueIsReturnedWhenItemIsValid()
4848
$this->assertEquals('bar', $store->get('foo'));
4949
}
5050

51+
public function testValueIsReturnedOnPostgres()
52+
{
53+
$store = $this->getPostgresStore();
54+
$table = m::mock('stdClass');
55+
$store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table);
56+
$table->shouldReceive('where')->once()->with('key', '=', 'prefixfoo')->andReturn($table);
57+
$table->shouldReceive('first')->once()->andReturn((object) ['value' => base64_encode(serialize('bar')), 'expiration' => 999999999999999]);
58+
59+
$this->assertEquals('bar', $store->get('foo'));
60+
}
61+
5162
public function testValueIsInsertedWhenNoExceptionsAreThrown()
5263
{
5364
$store = $this->getMockBuilder('Illuminate\Cache\DatabaseStore')->setMethods(['getTime'])->setConstructorArgs($this->getMocks())->getMock();
@@ -74,6 +85,17 @@ public function testValueIsUpdatedWhenInsertThrowsException()
7485
$store->put('foo', 'bar', 1);
7586
}
7687

88+
public function testValueIsInsertedOnPostgres()
89+
{
90+
$store = $this->getMockBuilder('Illuminate\Cache\DatabaseStore')->setMethods(['getTime'])->setConstructorArgs($this->getPostgresMocks())->getMock();
91+
$table = m::mock('stdClass');
92+
$store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table);
93+
$store->expects($this->once())->method('getTime')->will($this->returnValue(1));
94+
$table->shouldReceive('insert')->once()->with(['key' => 'prefixfoo', 'value' => base64_encode(serialize("\0")), 'expiration' => 61]);
95+
96+
$store->put('foo', "\0", 1);
97+
}
98+
7799
public function testForeverCallsStoreItemWithReallyLongTime()
78100
{
79101
$store = $this->getMockBuilder('Illuminate\Cache\DatabaseStore')->setMethods(['put'])->setConstructorArgs($this->getMocks())->getMock();
@@ -186,8 +208,18 @@ protected function getStore()
186208
return new DatabaseStore(m::mock('Illuminate\Database\Connection'), 'table', 'prefix');
187209
}
188210

211+
protected function getPostgresStore()
212+
{
213+
return new DatabaseStore(m::mock('Illuminate\Database\PostgresConnection'), 'table', 'prefix');
214+
}
215+
189216
protected function getMocks()
190217
{
191218
return [m::mock('Illuminate\Database\Connection'), 'table', 'prefix'];
192219
}
220+
221+
protected function getPostgresMocks()
222+
{
223+
return [m::mock('Illuminate\Database\PostgresConnection'), 'table', 'prefix'];
224+
}
193225
}

0 commit comments

Comments
 (0)