Skip to content

Integration ​

The SignalHandler plugin provides multiple ways to integrate signal handling into your CakePHP console commands. Choose the approach that best fits your needs.

Basic Integration ​

For simple signal handling, implement the SignalableCommandInterface:

php
use SignalHandler\Command\SignalableCommandInterface;
use SignalHandler\Signal\Signal;

class MyCommand extends Command implements SignalableCommandInterface
{
    public function getSubscribedSignals(): array
    {
        return [Signal::SIGINT, Signal::SIGTERM];
    }

    public function onInterrupt(): int|false
    {
        // Handle Ctrl+C
        return self::CODE_SUCCESS;
    }

    public function onTerminateSignal(): int|false
    {
        // Handle SIGTERM
        return self::CODE_SUCCESS;
    }

    public function onTerminate(int $exitCode, ?int $interruptingSignal = null): void
    {
        // Cleanup before exit
    }
}

Callback-based Integration ​

Plugin provides a trait that allows you to register signal handlers using callbacks. It provides methods to subscribe and unsubscribe to signals.

Bind and Unbind Methods ​

The SignalHandlerTrait provides several methods for registering and managing signal handlers:

bindSignals(array $signals, callable $callback): void ​

Registers signal handlers for the specified signals. When any of the signals are received, the callback function is executed.

php
// Bind handlers for SIGINT and SIGTERM
$this->bindSignals([Signal::SIGINT, Signal::SIGTERM], function (int $signal) {
    // Handle signal
    $this->isRunning = false;
});

unbindSignals(): void ​

Removes all registered signal handlers and restores default signal behavior.

php
// Clean up signal handlers
$this->unbindSignals();

bindGracefulTermination(callable $callback): void ​

Convenience method that registers handlers for SIGINT and SIGTERM signals for graceful termination.

php
$this->bindGracefulTermination(function (int $signal) {
    // Graceful termination logic
    $this->isRunning = false;
});

bindDebugSignals(callable $callback): void ​

Convenience method that binds handlers for debug signals (SIGUSR1, SIGUSR2, CTRL_BREAK).

php
$this->bindDebugSignals(function (int $signal) {
    // Debug signal handling
    $this->dumpDebugInfo();
});

Example ​

For callback-based signal handling, use the SignalHandlerTrait:

php
use SignalHandler\Command\Trait\SignalHandlerTrait;
use SignalHandler\Signal\Signal;

class MyCommand extends Command
{
    use SignalHandlerTrait;

    public function execute(Arguments $args, ConsoleIo $io): ?int
    {
        $this->bindGracefulTermination(function (int $signal) use ($io): void {
            $this->handleGracefulTermination($signal, $io);
        });

        while ($this->isRunning) {
            sleep(1);
        }

        return self::CODE_SUCCESS;
    }

    protected function handleGracefulTermination(int $signal, ConsoleIo $io): void
    {
        $io->out('Gracefully terminating...');
        $this->isRunning = false;
    }
}

React Event Loop Integration ​

For React-based servers, implement SignalableCommandInterface and call loop->stop():

php
use SignalHandler\Command\SignalableCommandInterface;
use React\EventLoop\LoopInterface;

class ServerCommand extends Command implements SignalableCommandInterface
{
    protected ?LoopInterface $loop = null;

    public function getSubscribedSignals(): array
    {
        return [Signal::SIGINT, Signal::SIGTERM];
    }

    public function onInterrupt(): int|false
    {
        if ($this->loop) {
            $this->loop->stop();
        }
        return self::CODE_SUCCESS;
    }

    public function execute(Arguments $args, ConsoleIo $io): ?int
    {
        $server = $this->createServer();
        $this->loop = $server->getLoop();

        $server->start(); // This runs the React loop

        return self::CODE_SUCCESS;
    }
}

Event-Based Integration ​

The plugin automatically integrates with CakePHP's event system:

php
// SignalEventListener automatically handles:
// - Command.beforeExecute: Registers signal handlers
// - Command.afterExecute: Cleans up signal handlers

// You can also listen to signal events:
$eventManager->on('SignalHandler.signal', function ($event) {
    $signal = $event->getData('signal');
    $command = $event->getData('command');

    // Handle signal event
});

Service Container Integration ​

The plugin registers services in the container:

php
// Get the signal service
$signalService = $container->get(\SignalHandler\Service\SignalService::class);

// Check if signal handling is enabled
if ($signalService->isEnabled()) {
    // Signal handling is available
}

// Get the signal registry
$registry = $signalService->getSignalRegistry();

Cross-Platform Considerations ​

The plugin handles platform differences automatically:

php
// Linux/macOS signals
Signal::SIGINT   // Ctrl+C
Signal::SIGTERM  // Termination signal
Signal::SIGUSR1  // User signal 1
Signal::SIGUSR2  // User signal 2

// Windows signals
Signal::CTRL_C     // Ctrl+C
Signal::CTRL_BREAK // Ctrl+Break

Testing Signal Handling ​

Test your signal handling implementation:

php
use SignalHandler\Signal\Signal;
use SignalHandler\Command\Trait\SignalHandlerTrait;

class TestSignalCommand extends Command
{
    use SignalHandlerTrait;

    public function execute(Arguments $args, ConsoleIo $io): ?int
    {
        $sleepTime = (int)$args->getOption('time', 20);

        $this->bindGracefulTermination(function (int $signal) use ($io): void {
            $io->out('Signal received, terminating gracefully');
            $this->isRunning = false;
        });

        $this->isRunning = true;
        $remaining = $sleepTime;

        while ($this->isRunning && $remaining > 0) {
            usleep(100000); // 100ms
            $remaining -= 0.1;
        }

        return self::CODE_SUCCESS;
    }
}

Run the test command:

bash
bin/cake test_signal --time=30

Then press Ctrl+C to test signal handling.

Released under the MIT License.