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

Background Process Laravel Package

cocur/background-process

Run shell commands as detached background processes from PHP so they keep running after the request/script ends. Start jobs, optionally get PID, poll if running, and stop them (Unix). Basic Windows support for launching only.

View on GitHub
Deep Wiki
Context7

Getting Started

Start by installing the package via Composer:

composer require cocur/background-process

Minimal Usage

For a quick test, run a simple command in the background:

use Cocur\BackgroundProcess\BackgroundProcess;

$process = new BackgroundProcess('sleep 5');
$process->run(); // Runs instantly; command executes in background

First Use Case: Offloading Long-Running CLI Tasks

Replace a blocking exec() call in a Laravel route or Artisan command:

// Before (blocks HTTP request)
exec('php artisan optimize:images {storage_path}');

// After (non-blocking)
$process = new BackgroundProcess('php artisan optimize:images ' . storage_path());
$process->run();
return response()->json(['message' => 'Task started in background']);

Key First Steps

  1. Identify blocking tasks in your Laravel app (e.g., image processing, report generation).
  2. Test locally with simple commands (sleep, echo) to verify behavior.
  3. Log PIDs (e.g., to a database) for later management if needed.

Implementation Patterns

Core Workflow

  1. Instantiate and Run:
    $process = new BackgroundProcess('php script.php --long-running');
    $process->run(); // Detaches immediately
    
  2. Optional PID Management (Unix-only):
    $pid = $process->getPid();
    // Store $pid in DB for later reference
    
  3. Check Status (Unix-only):
    if ($process->isRunning()) {
        // Task still active
    }
    
  4. Graceful Termination (Unix-only):
    $process->stop(); // Sends SIGTERM
    

Laravel-Specific Patterns

Artisan Command Integration

use Cocur\BackgroundProcess\BackgroundProcess;

class OptimizeImagesCommand extends Command {
    protected $signature = 'images:optimize {path}';

    public function handle() {
        $process = new BackgroundProcess("php artisan optimize:images {$this->argument('path')}");
        $process->run();
        $this->info("Optimization started in background (PID: {$process->getPid()})");
    }
}

Route/Controller Integration

use Cocur\BackgroundProcess\BackgroundProcess;

Route::post('/generate-report', function (Request $request) {
    $process = new BackgroundProcess('php artisan reports:generate ' . $request->input('type'));
    $process->run();

    // Store PID in DB for tracking
    DB::table('background_jobs')->insert([
        'command' => $process->getCommand(),
        'pid' => $process->getPid(),
        'created_at' => now(),
    ]);

    return response()->json(['status' => 'queued']);
});

Event Listener Integration

use Cocur\BackgroundProcess\BackgroundProcess;

class SendWelcomeEmail implements ShouldQueue {
    public function handle() {
        $process = new BackgroundProcess('php artisan emails:send welcome ' . auth()->id());
        $process->run();
    }
}

Advanced Patterns

PID Persistence for Later Management

// Store PID in DB
DB::table('background_jobs')->insert([
    'command' => $process->getCommand(),
    'pid' => $process->getPid(),
    'user_id' => auth()->id(),
]);

// Later, retrieve and stop
$job = DB::table('background_jobs')->find($jobId);
$process = BackgroundProcess::createFromPID($job->pid);
$process->stop();

Output Redirection (Unix-only)

Append command output to a log file:

$process = new BackgroundProcess('php script.php', null, null, null, null, '/path/to/logfile');
$process->run();

Process Pooling (Manual)

For managing multiple background tasks:

class BackgroundTaskManager {
    private $processes = [];

    public function add(string $command) {
        $process = new BackgroundProcess($command);
        $process->run();
        $this->processes[$process->getPid()] = $process;
    }

    public function stopAll() {
        foreach ($this->processes as $process) {
            $process->stop();
        }
        $this->processes = [];
    }
}

Gotchas and Tips

Pitfalls

  1. Windows Limitations:

    • getPid(), isRunning(), and stop() throw exceptions on Windows.
    • Workaround: Use only for fire-and-forget tasks on Windows; avoid PID management.
  2. Zombie Processes:

    • If the parent PHP process crashes, child processes may linger.
    • Solution: Implement a cron job to clean up stale processes:
      # Example cron entry (Linux)
      * * * * * php /path/to/cleanup_stale_processes.php
      
      // cleanup_stale_processes.php
      $staleJobs = DB::table('background_jobs')
          ->where('updated_at', '<', now()->subHours(1))
          ->get();
      
      foreach ($staleJobs as $job) {
          $process = BackgroundProcess::createFromPid($job->pid);
          if ($process->isRunning()) {
              $process->stop();
          }
          DB::table('background_jobs')->where('id', $job->id)->delete();
      }
      
  3. Resource Exhaustion:

    • Spawning too many processes can overwhelm the system.
    • Tip: Limit concurrent processes (e.g., via a semaphore or database lock).
  4. Stale PID Handling:

    • createFromPid() may return a process that already terminated.
    • Validation:
      $process = BackgroundProcess::createFromPid($pid);
      if (!$process->isRunning()) {
          throw new \RuntimeException("Process {$pid} is not running");
      }
      
  5. Output Capture:

    • On Windows, stdout/stderr cannot be captured.
    • Workaround: Redirect output to files on Unix:
      $process = new BackgroundProcess('php script.php', null, null, null, null, '/var/log/script.log');
      

Debugging Tips

  1. Check Process Status:

    $process = BackgroundProcess::createFromPid($pid);
    if ($process->isRunning()) {
        echo "Process is running (PID: {$process->getPid()})";
    } else {
        echo "Process not found or terminated";
    }
    
  2. Manual Process Inspection:

    • Use ps aux | grep <command> (Linux) or Task Manager (Windows) to verify processes.
  3. Logging PID:

    • Always log PIDs to a database or file for auditing:
      file_put_contents(
          storage_path('logs/background_pids.log'),
          "{$process->getPid()}: {$process->getCommand()}\n",
          FILE_APPEND
      );
      

Configuration Quirks

  1. Command Construction:

    • Avoid shell metacharacters (e.g., ;, &, |) in commands to prevent injection.
    • Use absolute paths for scripts to avoid PATH issues:
      $process = new BackgroundProcess('/full/path/to/script.php arg1 arg2');
      
  2. Environment Variables:

    • Child processes do not inherit the parent’s environment by default.
    • Solution: Pass environment variables explicitly:
      putenv('APP_ENV=production');
      $process = new BackgroundProcess('php script.php');
      
  3. Working Directory:

    • Child processes run in the current working directory of the parent.
    • Change directory if needed:
      chdir('/custom/directory');
      $process = new BackgroundProcess('php script.php');
      

Extension Points

  1. Custom Process Wrapper:

    • Extend BackgroundProcess for additional features:
      class LaravelBackgroundProcess extends BackgroundProcess {
          public function __construct(string $command) {
              parent::__construct($command);
              $this->logPid();
          }
      
          protected function logPid() {
              DB::table('background_jobs')->insert([
                  'command' => $this->getCommand(),
                  'pid' => $this->getPid(),
                  'created_at' => now(),
              ]);
          }
      }
      
  2. Process Monitoring Middleware:

    • Create middleware to validate background processes in routes:
      class BackgroundProcessMiddleware {
          public function handle($request, Closure $next) {
              if ($request->has('stop_job')) {
                  $process = BackgroundProcess::createFromPid($request->input('pid'));
                  $process->stop();
              }
              return $next($request);
          }
      }
      
  3. Integration with Laravel Events:

    • Trigger background tasks on events:
      Event::listen('user.registered', function ($user) {
          $process = new BackgroundProcess("php artisan send-welcome-email {$user->id}");
          $process->run();
      });
      

Performance Consider

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.
testo/facade
headercat/phpstan-extension-ide-helper
yosymfony/parser-utils
innmind/black-box
babenkoivan/elastic-migrations
babenkoivan/elastic-adapter
sandermuller/package-boost-php
sandermuller/boost-core
depa/sulu-google-reviews-bundle
croct/plug-symfony
develia/commons
dmstr/symfony-system-resources-bundle
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
renatomarinho/laravel-page-speed
develia/geo-bundle
austinheap/laravel-database-encryption
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle