Skip to content

Commit 65823de

Browse files
authored
Merge branch '11.x' into fix-tests
2 parents 21b7de5 + a13b726 commit 65823de

File tree

11 files changed

+303
-44
lines changed

11 files changed

+303
-44
lines changed

src/Illuminate/Cache/DatabaseStore.php

Lines changed: 95 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99
use Illuminate\Database\PostgresConnection;
1010
use Illuminate\Database\QueryException;
1111
use Illuminate\Database\SqlServerConnection;
12+
use Illuminate\Support\Arr;
1213
use Illuminate\Support\InteractsWithTime;
1314
use Illuminate\Support\Str;
1415

1516
class DatabaseStore implements LockProvider, Store
1617
{
17-
use InteractsWithTime, RetrievesMultipleKeys;
18+
use InteractsWithTime;
1819

1920
/**
2021
* The database connection instance.
@@ -98,29 +99,56 @@ public function __construct(ConnectionInterface $connection,
9899
*/
99100
public function get($key)
100101
{
101-
$prefixed = $this->prefix.$key;
102-
103-
$cache = $this->table()->where('key', '=', $prefixed)->first();
102+
return $this->many([$key])[$key];
103+
}
104104

105-
// If we have a cache record we will check the expiration time against current
106-
// time on the system and see if the record has expired. If it has, we will
107-
// remove the records from the database table so it isn't returned again.
108-
if (is_null($cache)) {
109-
return;
105+
/**
106+
* Retrieve multiple items from the cache by key.
107+
*
108+
* Items not found in the cache will have a null value.
109+
*
110+
* @return array
111+
*/
112+
public function many(array $keys)
113+
{
114+
if (count($keys) === 0) {
115+
return [];
110116
}
111117

112-
$cache = is_array($cache) ? (object) $cache : $cache;
118+
$results = array_fill_keys($keys, null);
119+
120+
// First we will retrieve all of the items from the cache using their keys and
121+
// the prefix value. Then we will need to iterate through each of the items
122+
// and convert them to an object when they are currently in array format.
123+
$values = $this->table()
124+
->whereIn('key', array_map(function ($key) {
125+
return $this->prefix.$key;
126+
}, $keys))
127+
->get()
128+
->map(function ($value) {
129+
return is_array($value) ? (object) $value : $value;
130+
});
131+
132+
$currentTime = $this->currentTime();
113133

114134
// If this cache expiration date is past the current time, we will remove this
115135
// item from the cache. Then we will return a null value since the cache is
116136
// expired. We will use "Carbon" to make this comparison with the column.
117-
if ($this->currentTime() >= $cache->expiration) {
118-
$this->forgetIfExpired($key);
137+
[$values, $expired] = $values->partition(function ($cache) use ($currentTime) {
138+
return $cache->expiration > $currentTime;
139+
});
119140

120-
return;
141+
if ($expired->isNotEmpty()) {
142+
$this->forgetManyIfExpired($expired->pluck('key')->all(), prefixed: true);
121143
}
122144

123-
return $this->unserialize($cache->value);
145+
return Arr::map($results, function ($value, $key) use ($values) {
146+
if ($cache = $values->firstWhere('key', $this->prefix.$key)) {
147+
return $this->unserialize($cache->value);
148+
}
149+
150+
return $value;
151+
});
124152
}
125153

126154
/**
@@ -133,11 +161,30 @@ public function get($key)
133161
*/
134162
public function put($key, $value, $seconds)
135163
{
136-
$key = $this->prefix.$key;
137-
$value = $this->serialize($value);
164+
return $this->putMany([$key => $value], $seconds);
165+
}
166+
167+
/**
168+
* Store multiple items in the cache for a given number of seconds.
169+
*
170+
* @param int $seconds
171+
* @return bool
172+
*/
173+
public function putMany(array $values, $seconds)
174+
{
175+
$serializedValues = [];
176+
138177
$expiration = $this->getTime() + $seconds;
139178

140-
return $this->table()->upsert(compact('key', 'value', 'expiration'), 'key') > 0;
179+
foreach ($values as $key => $value) {
180+
$serializedValues[] = [
181+
'key' => $this->prefix.$key,
182+
'value' => $this->serialize($value),
183+
'expiration' => $expiration,
184+
];
185+
}
186+
187+
return $this->table()->upsert($serializedValues, 'key') > 0;
141188
}
142189

143190
/**
@@ -309,9 +356,7 @@ public function restoreLock($name, $owner)
309356
*/
310357
public function forget($key)
311358
{
312-
$this->table()->where('key', '=', $this->prefix.$key)->delete();
313-
314-
return true;
359+
return $this->forgetMany([$key]);
315360
}
316361

317362
/**
@@ -321,9 +366,38 @@ public function forget($key)
321366
* @return bool
322367
*/
323368
public function forgetIfExpired($key)
369+
{
370+
return $this->forgetManyIfExpired([$key]);
371+
}
372+
373+
/**
374+
* Remove all items from the cache.
375+
*
376+
* @param array $keys
377+
* @return bool
378+
*/
379+
protected function forgetMany(array $keys)
380+
{
381+
$this->table()->whereIn('key', array_map(function ($key) {
382+
return $this->prefix.$key;
383+
}, $keys))->delete();
384+
385+
return true;
386+
}
387+
388+
/**
389+
* Remove all expired items from the given set from the cache.
390+
*
391+
* @param array $keys
392+
* @param bool $prefixed
393+
* @return bool
394+
*/
395+
protected function forgetManyIfExpired(array $keys, bool $prefixed = false)
324396
{
325397
$this->table()
326-
->where('key', '=', $this->prefix.$key)
398+
->whereIn('key', $prefixed ? $keys : array_map(function ($key) {
399+
return $this->prefix.$key;
400+
}, $keys))
327401
->where('expiration', '<=', $this->getTime())
328402
->delete();
329403

src/Illuminate/Database/Events/QueryExecuted.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,17 @@ public function __construct($sql, $bindings, $time, $connection)
5656
$this->connection = $connection;
5757
$this->connectionName = $connection->getName();
5858
}
59+
60+
/**
61+
* Get the raw SQL representation of the query with embedded bindings.
62+
*
63+
* @return string
64+
*/
65+
public function toRawSql()
66+
{
67+
return $this->connection
68+
->query()
69+
->getGrammar()
70+
->substituteBindingsIntoRawSql($this->sql, $this->connection->prepareBindings($this->bindings));
71+
}
5972
}

src/Illuminate/Foundation/Testing/Concerns/InteractsWithExceptionHandling.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Closure;
66
use Illuminate\Contracts\Debug\ExceptionHandler;
77
use Illuminate\Support\Testing\Fakes\ExceptionHandlerFake;
8+
use Illuminate\Support\Traits\ReflectsClosures;
89
use Illuminate\Testing\Assert;
910
use Illuminate\Validation\ValidationException;
1011
use Symfony\Component\Console\Application as ConsoleApplication;
@@ -13,6 +14,8 @@
1314

1415
trait InteractsWithExceptionHandling
1516
{
17+
use ReflectsClosures;
18+
1619
/**
1720
* The original exception handler.
1821
*
@@ -169,18 +172,22 @@ public function renderForConsole($output, Throwable $e)
169172
* Assert that the given callback throws an exception with the given message when invoked.
170173
*
171174
* @param \Closure $test
172-
* @param class-string<\Throwable> $expectedClass
175+
* @param \Closure|class-string<\Throwable> $expectedClass
173176
* @param string|null $expectedMessage
174177
* @return $this
175178
*/
176-
protected function assertThrows(Closure $test, string $expectedClass = Throwable::class, ?string $expectedMessage = null)
179+
protected function assertThrows(Closure $test, string|Closure $expectedClass = Throwable::class, ?string $expectedMessage = null)
177180
{
181+
[$expectedClass, $expectedClassCallback] = $expectedClass instanceof Closure
182+
? [$this->firstClosureParameterType($expectedClass), $expectedClass]
183+
: [$expectedClass, null];
184+
178185
try {
179186
$test();
180187

181188
$thrown = false;
182189
} catch (Throwable $exception) {
183-
$thrown = $exception instanceof $expectedClass;
190+
$thrown = $exception instanceof $expectedClass && ($expectedClassCallback === null || $expectedClassCallback($exception));
184191

185192
$actualMessage = $exception->getMessage();
186193
}

src/Illuminate/Http/Request.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ public function get(string $key, mixed $default = null): mixed
405405
public function json($key = null, $default = null)
406406
{
407407
if (! isset($this->json)) {
408-
$this->json = new InputBag((array) json_decode($this->getContent(), true));
408+
$this->json = new InputBag((array) json_decode($this->getContent() ?: '[]', true));
409409
}
410410

411411
if (is_null($key)) {

tests/Cache/CacheDatabaseStoreTest.php

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,26 @@ public function testNullIsReturnedWhenItemNotFound()
2222
$store = $this->getStore();
2323
$table = m::mock(stdClass::class);
2424
$store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table);
25-
$table->shouldReceive('where')->once()->with('key', '=', 'prefixfoo')->andReturn($table);
26-
$table->shouldReceive('first')->once()->andReturn(null);
25+
$table->shouldReceive('whereIn')->once()->with('key', ['prefixfoo'])->andReturn($table);
26+
$table->shouldReceive('get')->once()->andReturn(collect([]));
2727

2828
$this->assertNull($store->get('foo'));
2929
}
3030

3131
public function testNullIsReturnedAndItemDeletedWhenItemIsExpired()
3232
{
3333
$store = $this->getMockBuilder(DatabaseStore::class)->onlyMethods(['forgetIfExpired'])->setConstructorArgs($this->getMocks())->getMock();
34-
$table = m::mock(stdClass::class);
35-
$store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table);
36-
$table->shouldReceive('where')->once()->with('key', '=', 'prefixfoo')->andReturn($table);
37-
$table->shouldReceive('first')->once()->andReturn((object) ['expiration' => 1]);
38-
$store->expects($this->once())->method('forgetIfExpired')->with($this->equalTo('foo'))->willReturn(null);
34+
35+
$getQuery = m::mock(stdClass::class);
36+
$getQuery->shouldReceive('whereIn')->once()->with('key', ['prefixfoo'])->andReturn($getQuery);
37+
$getQuery->shouldReceive('get')->once()->andReturn(collect([(object) ['key' => 'prefixfoo', 'expiration' => 1]]));
38+
39+
$deleteQuery = m::mock(stdClass::class);
40+
$deleteQuery->shouldReceive('whereIn')->once()->with('key', ['prefixfoo'])->andReturn($deleteQuery);
41+
$deleteQuery->shouldReceive('where')->once()->with('expiration', '<=', m::any())->andReturn($deleteQuery);
42+
$deleteQuery->shouldReceive('delete')->once()->andReturnNull();
43+
44+
$store->getConnection()->shouldReceive('table')->twice()->with('table')->andReturn($getQuery, $deleteQuery);
3945

4046
$this->assertNull($store->get('foo'));
4147
}
@@ -45,8 +51,8 @@ public function testDecryptedValueIsReturnedWhenItemIsValid()
4551
$store = $this->getStore();
4652
$table = m::mock(stdClass::class);
4753
$store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table);
48-
$table->shouldReceive('where')->once()->with('key', '=', 'prefixfoo')->andReturn($table);
49-
$table->shouldReceive('first')->once()->andReturn((object) ['value' => serialize('bar'), 'expiration' => 999999999999999]);
54+
$table->shouldReceive('whereIn')->once()->with('key', ['prefixfoo'])->andReturn($table);
55+
$table->shouldReceive('get')->once()->andReturn(collect([(object) ['key' => 'prefixfoo', 'value' => serialize('bar'), 'expiration' => 999999999999999]]));
5056

5157
$this->assertSame('bar', $store->get('foo'));
5258
}
@@ -56,8 +62,8 @@ public function testValueIsReturnedOnPostgres()
5662
$store = $this->getPostgresStore();
5763
$table = m::mock(stdClass::class);
5864
$store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table);
59-
$table->shouldReceive('where')->once()->with('key', '=', 'prefixfoo')->andReturn($table);
60-
$table->shouldReceive('first')->once()->andReturn((object) ['value' => base64_encode(serialize('bar')), 'expiration' => 999999999999999]);
65+
$table->shouldReceive('whereIn')->once()->with('key', ['prefixfoo'])->andReturn($table);
66+
$table->shouldReceive('get')->once()->andReturn(collect([(object) ['key' => 'prefixfoo', 'value' => base64_encode(serialize('bar')), 'expiration' => 999999999999999]]));
6167

6268
$this->assertSame('bar', $store->get('foo'));
6369
}
@@ -68,7 +74,7 @@ public function testValueIsUpserted()
6874
$table = m::mock(stdClass::class);
6975
$store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table);
7076
$store->expects($this->once())->method('getTime')->willReturn(1);
71-
$table->shouldReceive('upsert')->once()->with(['key' => 'prefixfoo', 'value' => serialize('bar'), 'expiration' => 61], 'key')->andReturnTrue();
77+
$table->shouldReceive('upsert')->once()->with([['key' => 'prefixfoo', 'value' => serialize('bar'), 'expiration' => 61]], 'key')->andReturnTrue();
7278

7379
$result = $store->put('foo', 'bar', 60);
7480
$this->assertTrue($result);
@@ -80,7 +86,7 @@ public function testValueIsUpsertedOnPostgres()
8086
$table = m::mock(stdClass::class);
8187
$store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table);
8288
$store->expects($this->once())->method('getTime')->willReturn(1);
83-
$table->shouldReceive('upsert')->once()->with(['key' => 'prefixfoo', 'value' => base64_encode(serialize("\0")), 'expiration' => 61], 'key')->andReturn(1);
89+
$table->shouldReceive('upsert')->once()->with([['key' => 'prefixfoo', 'value' => base64_encode(serialize("\0")), 'expiration' => 61]], 'key')->andReturn(1);
8490

8591
$result = $store->put('foo', "\0", 60);
8692
$this->assertTrue($result);
@@ -99,7 +105,7 @@ public function testItemsMayBeRemovedFromCache()
99105
$store = $this->getStore();
100106
$table = m::mock(stdClass::class);
101107
$store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table);
102-
$table->shouldReceive('where')->once()->with('key', '=', 'prefixfoo')->andReturn($table);
108+
$table->shouldReceive('whereIn')->once()->with('key', ['prefixfoo'])->andReturn($table);
103109
$table->shouldReceive('delete')->once();
104110

105111
$store->forget('foo');
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Database;
4+
5+
use Illuminate\Database\Capsule\Manager as DB;
6+
use Illuminate\Database\Events\QueryExecuted;
7+
use Illuminate\Events\Dispatcher;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class DatabaseIntegrationTest extends TestCase
11+
{
12+
protected function setUp(): void
13+
{
14+
$db = new DB;
15+
$db->addConnection([
16+
'driver' => 'sqlite',
17+
'database' => ':memory:',
18+
]);
19+
$db->setAsGlobal();
20+
$db->setEventDispatcher(new Dispatcher);
21+
}
22+
23+
public function testQueryExecutedToRawSql(): void
24+
{
25+
$connection = DB::connection();
26+
27+
$connection->listen(function (QueryExecuted $query) use (&$queryExecuted): void {
28+
$queryExecuted = $query;
29+
});
30+
31+
$connection->select('select ?', [true]);
32+
33+
$this->assertInstanceOf(QueryExecuted::class, $queryExecuted);
34+
$this->assertSame('select ?', $queryExecuted->sql);
35+
$this->assertSame([true], $queryExecuted->bindings);
36+
$this->assertSame('select 1', $queryExecuted->toRawSql());
37+
}
38+
}

0 commit comments

Comments
 (0)