itsgoingd / clockwork

Clockwork - php dev tools in your browser - server-side component
https://underground.works/clockwork
MIT License
5.56k stars 320 forks source link

addDatabaseQuery does not increase queries count #703

Open MarkusJLechner opened 2 weeks ago

MarkusJLechner commented 2 weeks ago

We have converted a legacy project to Laravel and still have many vanilla DB fetches.

We created a wrapper above PDOStatement to log all queries, excluding those made by Laravel's query builder. This works well, but I noticed that the total query count is incorrect. It only counts when the Laravel query event is triggered, but not when calling clock()->addDatabaseQuery as described in the docs.

Is there a way to increase the count? Instead of using addDatabaseQuery, I tried manually triggering the Laravel query event, but then I cannot pass options like trace skipping.

Any advice on how to properly increase the count?

itsgoingd commented 2 weeks ago

Hey, it would totally make sense if the addDatabaseQuery helper incremented query counts, I'll look into adding it for the next release.

As a workaround for now, you can increment the database queries yourself in your own wrapper like this:

$request = clock()->request();

// Increment the total query count
$request->databaseQueriesCount++;

// Increment query count of a particular type
$request->databaseSelects++;
$request->databaseInserts++;
$request->databaseUpdates++;
$request->databaseDeletes++;
$request->databaseOthers++;
MarkusJLechner commented 2 weeks ago

Thanks for the workaround.

Its a littly hacky but does the job. Here to share my code, which tracks all PDO executes:

<?php

namespace App\Database;

use PDOStatement;
use Clockwork\Helpers\{ Serializer, StackTrace };

class TrackPDOStatement extends PDOStatement
{
    protected function __construct()
    {
    }

    public function execute($params = null): bool
    {
        return $this->trackSql(fn() => parent::execute($params), $this->queryString, $params ?? [], 2);
    }

    private function trackSql(callable $callback, string $query, array $bindings, int $tracesSkip = 0)
    {
        if (str_starts_with(strtolower(trim($query)), 'set ')) {
            return $callback();
        }

        $trace = (new Serializer(['tracesSkip' => $tracesSkip]))->trace(StackTrace::get());
        $containsQueryBuilder = false;
        foreach ($trace as $traceStep) {
            if (isset($traceStep['call']) && str_contains($traceStep['call'], 'Illuminate\Database\Query\Builder')) {
                $containsQueryBuilder = true;
                break;
            }
        }

        $time = microtime(true);
        $result = $callback();
        $executionTime = (microtime(true) - $time) * 1000;
        // Add the database query only if "Illuminate\Database\Query\Builder" is not in the call stack
        if (!$containsQueryBuilder) {
            clock()->addDatabaseQuery($query, $bindings, $executionTime, ['trace' => $trace, 'model' => 'PDO', 'connection' => 'mysql']);

            // See https://github.com/itsgoingd/clockwork/issues/703#issuecomment-2177763487
            // Maybe fixed in future
            $request = clock()->request();
            $sql = ltrim($query);
            $request->databaseQueriesCount++;
            if (preg_match('/^select\b/i', $sql)) {
                $request->databaseSelects++;
            } elseif (preg_match('/^insert\b/i', $sql)) {
                $request->databaseInserts++;
            } elseif (preg_match('/^update\b/i', $sql)) {
                $request->databaseUpdates++;
            } elseif (preg_match('/^delete\b/i', $sql)) {
                $request->databaseDeletes++;
            } else {
                $request->databaseOthers++;
            }
        }
        return $result;
    }
}

in laravel i just add to DatabaseProvider

class DatabaseProvider extends ServiceProvider
{
    public function register(): void
    {
        if ($this->app->environment('local')) {
            config()->set('database.connections.mysql.options.' . PDO::ATTR_STATEMENT_CLASS, [TrackPDOStatement::class, []]);
        }
    }
}