Tutorials Logic, IN info@tutorialslogic.com

PHP CLI and Background Jobs: Arguments, Exit Codes, Locks, and Idempotency

PHP Process Jobs

PHP CLI commands run outside the web request model and need explicit argument grammar, stream separation, exit status, configuration, job units, concurrency control, cancellation, retries, and scheduler semantics.

Dependable background work assumes duplicate delivery and process death, commits idempotent units durably, checkpoints immutable sources, bounds every wait, exposes safe lifecycle telemetry, and is rehearsed under the real supervisor identity.

CLI Boundary

Guard scripts that are unsafe through a web SAPI. Load the same Composer autoloader and environment configuration as the web application, but keep HTTP-only globals out of the command.

Require CLI Execution

Require CLI Execution
<?php
if (PHP_SAPI !== 'cli') {
    fwrite(STDERR, "CLI execution required.\n");
    exit(64);
}

echo "Worker ready\n";
Output
Worker ready

Arguments and Options

Use $argv for simple positional arguments or getopt() for named options. Validate required values before starting work and print usage to STDERR for invalid invocation.

Read a Batch Limit

Read a Batch Limit
<?php
$options = getopt('', ['limit:']);
$limit = filter_var($options['limit'] ?? null, FILTER_VALIDATE_INT, [
    'options' => ['min_range' => 1, 'max_range' => 500],
]);

if ($limit === false) {
    fwrite(STDERR, "Usage: php worker.php --limit=1..500\n");
    exit(64);
}

A bounded limit prevents one invocation from claiming unbounded work.

Exit Codes

Document the codes your scheduler or monitoring system distinguishes. Do not print an error and still return zero.

Code Meaning
0 Completed successfully
Non-zero Failed or invoked incorrectly
64 Command usage or argument error

Overlap Lock

A scheduler can start a second copy before the first finishes. Use a lock with a stable absolute path and release it in finally. A distributed deployment needs a shared database or cache lock instead of a host-local file.

Acquire a Host Lock

Acquire a Host Lock
<?php
$lock = fopen(sys_get_temp_dir() . '/task-worker.lock', 'c');
if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
    fwrite(STDERR, "Worker already running.\n");
    exit(75);
}

try {
    echo "Processing batch\n";
} finally {
    flock($lock, LOCK_UN);
    fclose($lock);
}
Output
Processing batch

Retry-Safe Work

  • Give each work item a stable identifier and record completion atomically.
  • Claim bounded batches so another worker cannot process the same row simultaneously.
  • Make external side effects idempotent or store a unique request key.
  • Log job name, run identifier, counts, duration, and failure category without secrets.
  • Use the operating-system scheduler, a process supervisor, or a queue system rather than keeping an ad hoc browser request open.

CLI Entry Points

PHP CLI runs a script file, inline code, or standard-input code under the CLI SAPI. Production commands should use a versioned script entry point with the same bootstrap, configuration validation, autoloading, and error policy on every host.

`$argv[0]` identifies the invoked script form and later elements contain arguments; `$argc` counts all elements including index zero. Do not assume web-only `$_SERVER` keys, request headers, sessions, or document roots exist in CLI execution.

Parse arguments before performing side effects. Define required positional values, long and short options, defaults, repeatability, mutually exclusive choices, and a stable help response. Reject unknown options so misspellings do not silently run broad defaults.

Shell quoting differs across operating systems and shells. Receive each argument as a value and never reconstruct a command line by joining `$argv`. When launching another process, use a structured process API or strict escaping for the exact platform.

Read environment configuration through the same typed startup schema as web code, while allowing command-specific timeouts and batch sizes. Fail before work begins when required secrets, paths, extensions, or database schema are unavailable.

  • Use one explicit command bootstrap.
  • Count argv index zero correctly.
  • Reject unknown or conflicting options.
  • Keep shell syntax outside application arguments.
  • Validate runtime configuration before side effects.

Streams and Exit Status

CLI programs separate standard input, standard output, and standard error. Send machine-readable results or pipeline data to stdout and diagnostics to stderr so redirection and automation can consume them independently.

Detect whether interactive prompting is appropriate before waiting for input. Scheduled jobs and containers may have no terminal; require explicit flags or input and fail promptly rather than hanging for a response that cannot arrive.

Set a maximum line, byte, record, and processing limit when reading stdin. Stream large input instead of collecting it, validate encoding and format, and report the record location without echoing secrets.

Return zero only when the command fulfilled its documented contract. Use a small stable set of nonzero exit codes for usage, validation, dependency, partial-work, or internal failures and document which codes automation may retry.

Flush progress only when needed and avoid decorative output in machine mode. A `--json` or quiet mode should have a stable schema and still route unexpected diagnostics to stderr.

  • Keep data and diagnostics on separate streams.
  • Do not prompt non-interactive executions.
  • Bound streamed input.
  • Publish stable exit semantics.
  • Offer predictable human and machine output modes.

Job Boundaries

A background job should define one recoverable unit such as one message, account, page, or batch. Validate the unit before opening a transaction and record completion only after all required effects succeed.

Make repeated delivery safe through an idempotency key, unique database constraint, version check, or operation-specific deduplication. Queue systems and process supervisors can redeliver work after timeout, crash, or lost acknowledgement.

Choose transaction scope deliberately. One transaction per item permits partial progress; one per batch gives stronger atomicity but holds locks longer and makes retries repeat more work. External APIs cannot participate in a normal database transaction.

Use an outbox or equivalent handoff when a database change and later message publication must not be lost between systems. Do not mark a job complete merely because an in-memory callback was scheduled.

Checkpoint long imports with a stable source cursor and enough version context to resume safely. A line number alone is insufficient when the source file can change between attempts.

  • Define one recoverable unit of work.
  • Assume delivery can repeat.
  • Select transaction scope from recovery needs.
  • Use durable cross-system handoff.
  • Checkpoint against immutable source identity.

Concurrency and Locks

Prevent unintended overlap with a lock appropriate to the deployment: database lease, queue partition, distributed lock, or local file lock for one host. A process check by name is vulnerable to races and stale identifiers.

A lease needs an owner token, expiry, renewal policy, and compare-and-release behavior so one worker cannot release another worker lock. Size expiry beyond normal work but recover after a crashed process.

Parallel workers require partition-safe operations and database constraints. Do not depend on a prior existence check without an atomic insert, update condition, or lock because two workers can observe the same state.

Bound worker concurrency from database connections, remote rate limits, memory, CPU, and queue visibility time. More processes can reduce throughput when they amplify contention and retries.

Order locks consistently and keep critical sections short. Detect deadlocks and retry the complete idempotent transaction under a small budget rather than continuing after partial rollback.

  • Choose a lock with deployment-wide scope.
  • Give leases ownership and expiry semantics.
  • Enforce concurrency invariants atomically.
  • Tune workers to dependency capacity.
  • Retry complete deadlocked units only.

Signals and Shutdown

Long-running Unix-like workers can use supported process-control facilities to react to termination signals; platform support varies. Treat a signal as a request to stop accepting new work, finish or abandon the current unit safely, flush state, release resources, and exit.

Set supervisor grace periods longer than normal cleanup but shorter than an unacceptable deployment delay. Work that can exceed the grace window needs checkpoints or a cancellation-safe design.

Check cancellation at bounded points in loops and remote pagination. Do not interrupt code while an invariant is temporarily broken; complete or roll back the current atomic unit first.

Close database transactions, file handles, locks, and temporary files through finally blocks. Shutdown handlers are a last diagnostic boundary and cannot guarantee recovery from every fatal process condition.

Reload configuration by replacing workers through the supervisor rather than mutating many globals mid-job. Record the release and configuration version that processed each durable command.

  • Stop intake before process termination.
  • Fit cleanup within supervisor grace.
  • Observe cancellation at safe boundaries.
  • Release resources through normal cleanup.
  • Replace workers for configuration changes.

Retries and Scheduling

Classify failures as permanent, transient, throttled, or unknown. Retry only transient outcomes with a maximum attempt count, elapsed deadline, and delay strategy; validation and authorization failures should move directly to a terminal result.

Exponential backoff with jitter can reduce synchronized retry storms. Respect remote retry guidance and ensure the queue visibility or lease remains valid while waiting or return the message for later delivery.

A scheduler may trigger late, twice, or after downtime. Store the intended logical run period and enforce uniqueness when exactly one business run per period is required. Wall-clock cron expressions also need a timezone and daylight-saving policy.

Send repeatedly failing items to a review or dead-letter path with safe context and replay tooling. A dead-letter queue without ownership, retention, alerting, and a corrected replay process only hides failures.

Do not retry an arbitrary Throwable. Programming errors, schema mismatches, and exhausted resources need alerting and deployment correction, not repeated load.

  • Retry classified transient failures only.
  • Use bounded delayed attempts with jitter.
  • Deduplicate logical scheduled periods.
  • Own dead-letter review and replay.
  • Alert on defects instead of retrying them blindly.

Job Observability

Emit structured logs with command name, job identifier, attempt, release, duration, item counts, checkpoint, outcome, and safe failure category. Keep credentials, raw personal data, and full payloads out of default logs.

Metrics should distinguish started, completed, failed, retried, delayed, dead-lettered, and abandoned work. Monitor age of the oldest pending item and end-to-end completion latency, not only worker CPU or queue length.

Trace database and remote operations under one job correlation context. A CLI process has no browser request identifier unless the command or queue envelope provides one.

Test argument errors, stdin limits, output streams, exit codes, duplicate delivery, lock contention, retries, timeout, cancellation, partial batch failure, checkpoint resume, and graceful supervisor termination.

Run an operational rehearsal with the actual scheduler, service account, working directory, environment, and deployment binary. A command that passes from a developer terminal can still fail under a minimal non-interactive supervisor.

  • Log one safe lifecycle record per unit.
  • Measure queue age and completion latency.
  • Propagate correlation across dependencies.
  • Test recovery and shutdown paths.
  • Rehearse under the production execution identity.
Before you move on

Mastery Check

5 checks
  • Parse options and validate configuration before work.
  • Separate stdout data, stderr diagnostics, and exit codes.
  • Make each job unit idempotent and transactionally owned.
  • Bound locks, concurrency, retries, and shutdown.
  • Test and monitor complete scheduled and queued lifecycles.

Review Job Behavior

0 of 2 checked

Q1. What should a failed command return?

Q2. Is a local file lock enough across several servers?

Try this next

Make a Job Retry-Safe

0 of 2 completed

  1. Validate --limit, claim rows, record stable message IDs, and define success, temporary failure, and permanent failure states.
  2. Choose a lock suitable for one host or many hosts and document what happens after an unclean process exit.
Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.