Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/Illuminate/Foundation/Exceptions/Renderer/Listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,25 @@ public function queries()
*/
public function onQueryExecuted(QueryExecuted $event)
{
if (count($this->queries) === 101) {
if (count($this->queries) >= 100) {
return;
}

$sql = strlen($event->sql) <= 2000
? $event->sql
: mb_strcut($event->sql, 0, 2000);

$bindings = $event->connection->prepareBindings($event->bindings);

$bindingCount = substr_count($sql, '?');

$this->queries[] = [
'connectionName' => $event->connectionName,
'time' => $event->time,
'sql' => $event->sql,
'bindings' => $event->connection->prepareBindings($event->bindings),
'sql' => $sql,
'bindings' => count($bindings) <= $bindingCount
? $bindings
: array_slice($bindings, 0, $bindingCount),
];
}
}
130 changes: 130 additions & 0 deletions tests/Foundation/Exceptions/Renderer/ListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,134 @@ public function test_queries_returns_expected_shape_after_query_executed()
$this->assertEquals('select * from users where id = ?', $query['sql']);
$this->assertEquals(['foo'], $query['bindings']);
}

public function test_listener_caps_at_100_queries()
{
$listener = new Listener();

$connection = m::mock();
$connection->shouldReceive('getName')->andReturn('testing');
$connection->shouldReceive('prepareBindings')->andReturnUsing(fn ($b) => $b);

for ($i = 0; $i < 150; $i++) {
$listener->onQueryExecuted(
new QueryExecuted("select {$i}", [], 1.0, $connection)
);
}

$this->assertCount(100, $listener->queries());
$this->assertEquals('select 0', $listener->queries()[0]['sql']);
$this->assertEquals('select 99', $listener->queries()[99]['sql']);
}

public function test_large_sql_is_truncated()
{
$listener = new Listener();

$connection = m::mock();
$connection->shouldReceive('getName')->andReturn('testing');
$connection->shouldReceive('prepareBindings')->andReturnUsing(fn ($b) => $b);

$largeSql = str_repeat('x', 5000);
$listener->onQueryExecuted(
new QueryExecuted($largeSql, [], 1.0, $connection)
);

$this->assertLessThanOrEqual(2000, strlen($listener->queries()[0]['sql']));
}

public function test_bindings_match_placeholder_count_in_truncated_sql()
{
$listener = new Listener();

$connection = m::mock();
$connection->shouldReceive('getName')->andReturn('testing');
$connection->shouldReceive('prepareBindings')->andReturnUsing(fn ($b) => $b);

// Build SQL with 500 placeholders — when truncated to 2000 bytes,
// only some ? will remain, and bindings should match that count.
$placeholders = implode(', ', array_fill(0, 500, '?'));
$sql = "INSERT INTO t (a) VALUES ({$placeholders})";
$bindings = array_fill(0, 500, 'value');

$listener->onQueryExecuted(
new QueryExecuted($sql, $bindings, 1.0, $connection)
);

$storedQuery = $listener->queries()[0];
$storedPlaceholders = substr_count($storedQuery['sql'], '?');

$this->assertCount($storedPlaceholders, $storedQuery['bindings']);
}

public function test_excess_bindings_are_trimmed_to_match_placeholders()
{
$listener = new Listener();

$connection = m::mock();
$connection->shouldReceive('getName')->andReturn('testing');
$connection->shouldReceive('prepareBindings')->andReturnUsing(fn ($b) => $b);

// 1 placeholder but 1000 bindings — only 1 binding should be kept
$listener->onQueryExecuted(
new QueryExecuted('select ?', array_fill(0, 1000, 'v'), 1.0, $connection)
);

$this->assertCount(1, $listener->queries()[0]['bindings']);
}

public function test_short_sql_and_bindings_are_not_modified()
{
$listener = new Listener();

$connection = m::mock();
$connection->shouldReceive('getName')->andReturn('testing');
$connection->shouldReceive('prepareBindings')->andReturnUsing(fn ($b) => $b);

$sql = 'select * from users where name = ?';
$listener->onQueryExecuted(
new QueryExecuted($sql, ['John'], 1.0, $connection)
);

$this->assertEquals($sql, $listener->queries()[0]['sql']);
$this->assertEquals(['John'], $listener->queries()[0]['bindings']);
}

public function test_query_with_no_bindings_is_unchanged()
{
$listener = new Listener();

$connection = m::mock();
$connection->shouldReceive('getName')->andReturn('testing');
$connection->shouldReceive('prepareBindings')->andReturnUsing(fn ($b) => $b);

$listener->onQueryExecuted(
new QueryExecuted('select count(*) from users', [], 1.0, $connection)
);

$this->assertEquals('select count(*) from users', $listener->queries()[0]['sql']);
$this->assertEmpty($listener->queries()[0]['bindings']);
}

public function test_normal_query_skips_truncation()
{
$listener = new Listener();

$connection = m::mock();
$connection->shouldReceive('getName')->andReturn('testing');
$connection->shouldReceive('prepareBindings')->andReturnUsing(fn ($b) => $b);

$sql = 'select * from users where id = ? and name = ? and email = ?';
$bindings = [1, 'John', 'john@example.com'];

$listener->onQueryExecuted(
new QueryExecuted($sql, $bindings, 1.0, $connection)
);

$storedQuery = $listener->queries()[0];

// Nothing should be modified — SQL is short and bindings match placeholders
$this->assertEquals($sql, $storedQuery['sql']);
$this->assertEquals($bindings, $storedQuery['bindings']);
}
}
Loading