File: /home/xedaptot/ai.naniguide.com/app/Jobs/Concerns/JobControlAdapter.php
<?php
namespace App\Jobs\Concerns;
use App\Library\Sending\JobControl;
use Illuminate\Bus\Batch;
/**
* Bridges a Laravel queueable Job (`SendMessage` / `SendSingleMessage`) to the
* pipeline's `JobControl` contract.
*
* ## Why this class exists
*
* The pipeline + every exception handler needs to call three job-side actions:
* `release(int)`, `batch(): ?Batch`, `addToBatch(object)`. We declare those on
* a small `JobControl` interface so the pipeline doesn't have to import
* Laravel's `Batchable` / `InteractsWithQueue` traits, and so test stubs can
* implement it in a few lines.
*
* The interface declares STRICT return types (`: void`, `: ?Batch`). Laravel's
* underlying trait methods are intentionally LOOSE:
*
* - `Illuminate\Queue\InteractsWithQueue::release($delay = 0)` ← no return type
* - `Illuminate\Bus\Batchable::batch()` ← no return type
*
* If a Job class did `implements JobControl` directly while also `use Batchable`,
* PHP enforces LSP between the trait method and the interface method and bails
* with "Declaration must be compatible" — the trait's untyped return is wider
* than the interface's `?Batch` / `void`.
*
* Two ways out:
* (a) rename interface methods to dodge the collision (`releaseJob`,
* `currentBatch`) and add proxy methods on every Job class, OR
* (b) **stop having Job classes implement the interface**; let an adapter do
* it. This file is option (b).
*
* Option (b) keeps the interface natural-named, contains the proxy logic in one
* place, and lets new Job classes (e.g. a future `SendTestMessage` for the
* test-email path) plug in by passing `new JobControlAdapter($this)` in their
* `handle()` — no boilerplate copy.
*
* ## How it works
*
* The adapter holds a reference to the wrapping job and forwards each call to
* the public trait method on it. `release()` and `batch()` on the wrapped job
* are public (Laravel's traits expose them); we don't touch protected state.
*
* @internal Constructed by `SendMessage::handle()` / `SendSingleMessage::handle()`
* — never instantiated outside Job classes.
*/
final class JobControlAdapter implements JobControl
{
public function __construct(private object $job)
{
}
public function release(int $seconds): void
{
// Forwards to Illuminate\Queue\InteractsWithQueue::release on the wrapped job.
$this->job->release($seconds);
}
public function batch(): ?Batch
{
// Forwards to Illuminate\Bus\Batchable::batch on the wrapped job.
// Returns null when the job is dispatched outside a Bus::batch context
// (e.g. SendSingleMessage automation single-dispatch).
return $this->job->batch();
}
public function addToBatch(object $job): void
{
$batch = $this->job->batch();
if ($batch === null) {
// Handlers should call currentBatch() === null first; this guard is
// a last-resort safety net for caller bugs.
throw new \LogicException(
'JobControlAdapter::addToBatch called outside batch context for '.$this->job::class
);
}
$batch->add([$job]);
}
}