Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Rowcast Profiler Laravel Package

ascetic-soft/rowcast-profiler

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

To start using rowcast-profiler in a Laravel project, follow these steps:

  1. Install the Package

    composer require ascetic-soft/rowcast-profiler
    
  2. 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);
            }
        );
    }
    
  3. 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";
    }
    
  4. Symfony Integration (if using RowcastBundle) Enable the profiler in your config/packages/rowcast.yaml:

    rowcast:
        profiler: true
    

Implementation Patterns

Core Workflow

  1. 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.

  2. Query Profiling Lifecycle

    • Before Execution: Records start time and sanitizes parameters.
    • After Execution: Calculates duration, captures errors, and stores the profile.
    • Thresholds: Queries exceeding slowQueryThresholdMs are flagged (e.g., for alerts).
  3. Storage Strategies

    • In-Memory (InMemoryQueryProfileStore): Best for development or short-lived processes.
      $store = new InMemoryQueryProfileStore();
      
    • Database-Backed: For production, implement a custom store (e.g., using Laravel’s query builder):
      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(),
              ]);
          }
      }
      
    • Redis: For distributed systems, use Redis to aggregate profiles across workers.
  4. 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);
        }
    };
    
  5. Integration with Laravel Tools

    • Telescope: Surface profiles via a custom channel:
      Telescope::channel('rowcast-queries', function () {
          return app(DatabaseQueryProfileStore::class)->getProfiles();
      });
      
    • Artisan Commands: Create a command to dump recent queries:
      Artisan::command('rowcast:profiles', function () {
          foreach (app(InMemoryQueryProfileStore::class)->getProfiles() as $profile) {
              $this->info("{$profile->durationMs}ms: {$profile->sql}");
          }
      });
      
  6. 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(); }
            };
        });
    

Gotchas and Tips

Pitfalls

  1. Parameter Sanitization Overhead

    • Issue: Aggressive sanitization (e.g., masking all strings) may hide debugging details.
    • Fix: Use a targeted sanitizer:
      $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);
          }
      };
      
  2. In-Memory Store Limitations

    • Issue: InMemoryQueryProfileStore clears on process restart and isn’t thread-safe.
    • Fix: Use a persistent store (e.g., database/Redis) in production:
      $store = new DatabaseQueryProfileStore();
      
  3. Performance Impact in Loops

    • Issue: Profiling adds overhead, especially in tight loops (e.g., batch processing).
    • Fix: Disable profiling for non-critical paths or use a conditional profiler:
      $profiler = new RowcastProfiler($store, $sanitizer, slowQueryThresholdMs: 0, maxQueries: 0);
      // Disable by setting maxQueries to 0 (no profiling)
      
  4. Error Handling Gaps

    • Issue: Profiled errors may not surface in Laravel’s exception handler.
    • Fix: Extend the profiler to log errors separately:
      $profiler = new RowcastProfiler($store, $sanitizer);
      $profiler->setErrorHandler(function (Throwable $e) {
          Log::error("Rowcast query failed: " . $e->getMessage());
      });
      
  5. Query Plan Missing

    • Issue: The profiler doesn’t capture execution plans (unlike Blackfire/Xdebug).
    • Workaround: Use EXPLAIN queries or integrate with pdo_profiler for deeper insights.
  6. Symfony/RowcastBundle Quirks

    • Issue: If using RowcastBundle, ensure the profiler is enabled in the correct config file (config/packages/rowcast.yaml).
    • Fix: Verify the bundle’s documentation for profiler-specific settings.

Debugging Tips

  1. Log Raw Profiles Dump raw profiles to debug issues:

    $store = app(InMemoryQueryProfileStore::class);
    dd($store->getProfiles());
    
  2. Isolate Profiling Test profiling on a single connection to avoid noise:

    $testConnection = new ConnectionProfiler(
        Connection::create('sqlite::memory:'),
        new RowcastProfiler(new InMemoryQueryProfileStore(), new DefaultParameterSanitizer())
    );
    
  3. Benchmark Overhead Measure the impact of profiling:

    $start = microtime(true);
    $connection->fetchAll('SELECT 1');
    $duration = microtime(true) - $start;
    // Compare with/without profiling
    
  4. Custom Thresholds Adjust slowQueryThresholdMs based on your environment:

    // Stricter in production
    $profiler = new RowcastProfiler($store, $sanitizer, slowQueryThresholdMs: 10.0);
    

Extension Points

  1. 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(),
                ]
            ]);
        }
    }
    
  2. Dynamic Thresholds Make thresholds dynamic (e.g., based on environment):

    $threshold = config('rowcast.profiler.slow_query_threshold
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
emuniq/filament-browser-notifications
syriable/filament-translator
hungnm28/livewire-form
wenprise/eloquent
crudly/encrypted
fadion/bouncy
cuci/prototurk-sdk
gos/pubsub-router-bundle
cuci/prototurk-sdk-symfony
clementtalleu/easyadmin-markdown-bundle
codeflextech/permission-manager
karnoweb/livewire-datepicker
sayedenam/sayed-dashboard
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui