To start using rowcast-profiler in a Laravel project, follow these steps:
Install the Package
composer require ascetic-soft/rowcast-profiler
Wrap Your Rowcast Connection
In your Laravel service provider (e.g., AppServiceProvider), bind the profiled connection:
use AsceticSoft\Rowcast\Connection;
use AsceticSoft\RowcastProfiler\ConnectionProfiler;
use AsceticSoft\RowcastProfiler\InMemoryQueryProfileStore;
use AsceticSoft\RowcastProfiler\DefaultParameterSanitizer;
use AsceticSoft\RowcastProfiler\RowcastProfiler;
public function register()
{
$this->app->bind(
AsceticSoft\Rowcast\ConnectionInterface::class,
function ($app) {
$inner = Connection::create(env('DB_CONNECTION'));
$store = new InMemoryQueryProfileStore();
$sanitizer = new DefaultParameterSanitizer();
$profiler = new RowcastProfiler($store, $sanitizer, slowQueryThresholdMs: 50.0);
return new ConnectionProfiler($inner, $profiler);
}
);
}
First Use Case: Debugging Slow Queries Run a query and inspect the profiled data:
$connection = $this->app->make(AsceticSoft\Rowcast\ConnectionInterface::class);
$result = $connection->fetchOne('SELECT * FROM users WHERE email = ?', ['test@example.com']);
// Access the in-memory store (for demo purposes)
$store = $this->app->make(AsceticSoft\RowcastProfiler\InMemoryQueryProfileStore::class);
foreach ($store->getProfiles() as $profile) {
echo "SQL: {$profile->sql}, Duration: {$profile->durationMs}ms\n";
}
Symfony Integration (if using RowcastBundle)
Enable the profiler in your config/packages/rowcast.yaml:
rowcast:
profiler: true
Decorator Pattern
The ConnectionProfiler wraps the original ConnectionInterface, intercepting all method calls (e.g., fetchOne, execute) to record timing and parameters. This avoids modifying Rowcast’s core.
Query Profiling Lifecycle
slowQueryThresholdMs are flagged (e.g., for alerts).Storage Strategies
InMemoryQueryProfileStore): Best for development or short-lived processes.
$store = new InMemoryQueryProfileStore();
class DatabaseQueryProfileStore implements QueryProfileStore {
public function addProfile(QueryProfile $profile) {
DB::table('query_profiles')->insert([
'sql' => $profile->sql,
'duration_ms' => $profile->durationMs,
'parameters' => json_encode($profile->parameters),
'created_at' => now(),
]);
}
}
Parameter Sanitization Customize how parameters are sanitized for logging/audits:
$sanitizer = new class implements ParameterSanitizer {
public function sanitize(array $parameters): array {
return array_map(fn($param) => str_replace('[REDACTED]', '**', (string)$param), $parameters);
}
};
Integration with Laravel Tools
Telescope::channel('rowcast-queries', function () {
return app(DatabaseQueryProfileStore::class)->getProfiles();
});
Artisan::command('rowcast:profiles', function () {
foreach (app(InMemoryQueryProfileStore::class)->getProfiles() as $profile) {
$this->info("{$profile->durationMs}ms: {$profile->sql}");
}
});
Conditional Profiling Disable profiling in production by binding a no-op profiler:
$this->app->when(AsceticSoft\Rowcast\ConnectionInterface::class)
->needs(AsceticSoft\RowcastProfiler\RowcastProfiler::class)
->give(function () {
return new class implements RowcastProfiler {
public function profile(callable $callback) { return $callback(); }
};
});
Parameter Sanitization Overhead
$sanitizer = new class implements ParameterSanitizer {
public function sanitize(array $parameters): array {
return array_map(function ($param) {
if (is_string($param) && str_contains($param, 'password')) {
return '[REDACTED]';
}
return $param;
}, $parameters);
}
};
In-Memory Store Limitations
InMemoryQueryProfileStore clears on process restart and isn’t thread-safe.$store = new DatabaseQueryProfileStore();
Performance Impact in Loops
$profiler = new RowcastProfiler($store, $sanitizer, slowQueryThresholdMs: 0, maxQueries: 0);
// Disable by setting maxQueries to 0 (no profiling)
Error Handling Gaps
$profiler = new RowcastProfiler($store, $sanitizer);
$profiler->setErrorHandler(function (Throwable $e) {
Log::error("Rowcast query failed: " . $e->getMessage());
});
Query Plan Missing
EXPLAIN queries or integrate with pdo_profiler for deeper insights.Symfony/RowcastBundle Quirks
RowcastBundle, ensure the profiler is enabled in the correct config file (config/packages/rowcast.yaml).Log Raw Profiles Dump raw profiles to debug issues:
$store = app(InMemoryQueryProfileStore::class);
dd($store->getProfiles());
Isolate Profiling Test profiling on a single connection to avoid noise:
$testConnection = new ConnectionProfiler(
Connection::create('sqlite::memory:'),
new RowcastProfiler(new InMemoryQueryProfileStore(), new DefaultParameterSanitizer())
);
Benchmark Overhead Measure the impact of profiling:
$start = microtime(true);
$connection->fetchAll('SELECT 1');
$duration = microtime(true) - $start;
// Compare with/without profiling
Custom Thresholds
Adjust slowQueryThresholdMs based on your environment:
// Stricter in production
$profiler = new RowcastProfiler($store, $sanitizer, slowQueryThresholdMs: 10.0);
Custom QueryProfileStore Implement your own store for advanced use cases (e.g., Elasticsearch, Kafka):
class ElasticsearchQueryProfileStore implements QueryProfileStore {
public function addProfile(QueryProfile $profile) {
$client = new Elasticsearch\Client();
$client->index([
'index' => 'query_profiles',
'body' => [
'sql' => $profile->sql,
'duration_ms' => $profile->durationMs,
'timestamp' => now()->toIso8601String(),
]
]);
}
}
Dynamic Thresholds Make thresholds dynamic (e.g., based on environment):
$threshold = config('rowcast.profiler.slow_query_threshold
How can I help you explore Laravel packages today?