gepur-it/sic-bundle
gepur-it/sic-bundle is a PHP bundle for integrating SIC functionality into Symfony applications. It packages configuration and service wiring so projects can add SIC-related features with minimal setup and keep integration code organized.
gepur-it/sic-bundle ("Single Instance Console Command Bundle") is a Symfony bundle — not a Laravel package. It exists to stop a console command from running twice at the same time (the classic "cron fires every minute, the import takes three" problem). Before you install anything, decide whether you actually need it: Laravel's scheduler already ships withoutOverlapping() on the Schedule object, which solves 90% of the use case for free.
If you still want it (e.g., you're running a Symfony-console-based command inside a Laravel app via a bridge, or you want to see the pattern):
composer require gepur-it/sic-bundle — check the constraint graph first. The new v6.0.0 release bumps composer constraints to PHP 8 / Symfony 5. That makes it installable on Laravel 8.x (which uses Symfony 5 components) and on PHP 8.0–8.1. It is not compatible with Laravel 9+ out of the box, because Laravel 9/10 use Symfony 6.x and Laravel 11 uses Symfony 7.x — so on those versions you'll still need --ignore-platform-reqs or a replace/fork. The previous release was April 2021, so treat v6.0.0 as a long-overdue version-bump rather than a sign of active maintenance.config/bundles.php and read the DependencyInjection/Configuration.php to see the lock options.ConsoleEvents::COMMAND subscriber that acquires a lock named after the command) and port the ~30–60 lines into your own Artisan::starting() hook or command base class.php artisan reports:rebuild) that must self-skip when the previous invocation is still running. The behaviour you want is: second invocation logs "already running", exits 0, and never touches the data.The core pattern the bundle encodes is a per-command lock acquired before execute() and released after. Two variants exist in the wild:
kill -9. Safest.Check which one this bundle uses before trusting it in production.
Symfony usage (if you're in a Symfony 5 / Laravel 8 app):
// config/bundles.php
GepurIt\SicBundle\SicBundle::class => ['all' => true],
Then any command registered in the container is wrapped by the subscriber. Configure per-command lock names/TTLs in config/packages/sic.yaml if the bundle's Configuration exposes them.
Laravel-native equivalents (preferred):
// 1. Scheduler — the default answer
$schedule->command('reports:rebuild')->everyMinute()->withoutOverlapping(10);
// 2. Manual lock inside the command — when it's also invoked by hand or by a job
public function handle(): int
{
$lock = Cache::lock('cmd:reports:rebuild', 600);
if (! $lock->get()) {
$this->warn('Already running, skipping.');
return self::SUCCESS; // exit 0 so cron/monitoring stays quiet
}
try {
$this->doWork();
} finally {
$lock->release();
}
return self::SUCCESS;
}
Porting the bundle's listener to Laravel — if you want the "every command is single-instance" behaviour without touching each command:
// AppServiceProvider::boot()
Artisan::starting(function ($artisan) {
// resolve command name from argv, acquire Cache::lock, abort if held
});
Or decorate: extract an abstract SingleInstanceCommand base class and extend it.
Integration tips:
0 (not 1) for the "skipped, already running" case, otherwise cron mailers and Sentry will scream every minute.info/debug for skips, error only for real failures.flock on a local path is per-container and gives you zero protection in Kubernetes.php 8, symfony 5), not a feature or bugfix release. One star, zero dependents, and a multi-year gap between versions means: assume no active maintainer. Vendor it, fork it, or (better) copy the pattern — it's small enough that owning the code is cheaper than owning the dependency.composer require it and get autoconfigured behaviour (no bundle registration in Laravel). Most of its value here is as a reading exercise. The new Symfony 5 constraint lands it squarely in Laravel 8.x territory — modern Laravel (9/10/11) pulls Symfony 6/7, so composer will still refuse the install without --ignore-platform-reqs.withoutOverlapping() uses the cache lock, so it silently degrades to "no protection" if CACHE_DRIVER=array or your cache store doesn't support atomic locks. Verify your store (redis, database, memcached, dynamodb — not file on NFS, not array).flock clears on process death; PID files and plain cache locks do not. If your jobs can be OOM-killed, prefer flock/semaphore or set a TTL and accept a window of double-execution.import:feed --tenant=5). Include the discriminating arguments in the lock key, or you'll silently serialise unrelated workloads.0 for "skipped", a distinct non-zero code for "failed", and document it.-vvv and check the log line where the listener decides to abort; inspect the lock store directly (redis-cli keys '*cmd:*') or the lock file directory to see who holds it.ConsoleEvents::COMMAND listener priority — you must run before the command's execute(), and other listeners may already have side effects.FlockStore for RedisStore (via symfony/lock, the maintained component) to get cross-host safety.SIGTERM handling or graceful shutdown; pair it with a signal handler if the task must finish cleanly.symfony/lock for the primitive, Laravel's Cache::lock() + withoutOverlapping() for the framework-native path, or a simple flock() wrapper — all three are better-supported than this bundle.How can I help you explore Laravel packages today?