@@ -82,6 +82,16 @@ class Terminus implements
8282
8383 private Application $ application ;
8484
85+ /**
86+ * @var callable[]
87+ */
88+ private $ cleanup_handlers = [];
89+
90+ /**
91+ * @var static
92+ */
93+ private static $ instance ;
94+
8595 /**
8696 * Object constructor
8797 *
@@ -91,6 +101,7 @@ class Terminus implements
91101 */
92102 public function __construct (Config $ config , InputInterface $ input , OutputInterface $ output )
93103 {
104+ self ::$ instance = $ this ;
94105 $ this ->setConfig ($ config );
95106 $ this ->setInput ($ input );
96107 $ this ->setOutput ($ output );
@@ -514,6 +525,10 @@ public function run(?InputInterface $input = null, ?OutputInterface $output = nu
514525 if ($ output === null ) {
515526 $ output = $ this ->output ();
516527 }
528+
529+ // Set up signal handlers for graceful shutdown
530+ $ this ->setupSignalHandlers ();
531+
517532 $ config = $ this ->getConfig ();
518533 if (!empty ($ cassette = $ config ->get ('vcr_cassette ' )) && !empty ($ mode = $ config ->get ('vcr_mode ' ))) {
519534 $ this ->startVCR (array_merge (compact ('cassette ' ), compact ('mode ' )));
@@ -525,6 +540,82 @@ public function run(?InputInterface $input = null, ?OutputInterface $output = nu
525540 return $ status_code ;
526541 }
527542
543+ /**
544+ * Set up signal handlers for graceful shutdown
545+ */
546+ private function setupSignalHandlers (): void
547+ {
548+ if (!function_exists ('pcntl_signal ' )) {
549+ // pcntl extension not available, signal handling not possible
550+ return ;
551+ }
552+
553+ // Handle SIGINT (Ctrl+C) and SIGTERM
554+ pcntl_signal (SIGINT , [$ this , 'handleSignal ' ]);
555+ pcntl_signal (SIGTERM , [$ this , 'handleSignal ' ]);
556+
557+ // Enable signal handling
558+ pcntl_async_signals (true );
559+ }
560+
561+ /**
562+ * Handle signals for graceful shutdown
563+ *
564+ * @param int $signal The signal number
565+ */
566+ public function handleSignal (int $ signal ): void
567+ {
568+ $ this ->logger ->notice ('Received signal {signal}, performing cleanup... ' , ['signal ' => $ signal ]);
569+
570+ $ this ->cleanup ();
571+
572+ // Restore default signal handler and re-raise the signal
573+ pcntl_signal ($ signal , SIG_DFL );
574+ posix_kill (posix_getpid (), $ signal );
575+ }
576+
577+ /**
578+ * Get the current Terminus instance
579+ *
580+ * @return static|null
581+ */
582+ public static function getInstance (): ?self
583+ {
584+ return self ::$ instance ;
585+ }
586+
587+ /**
588+ * Register a cleanup handler that will be called during signal handling
589+ *
590+ * @param callable $handler The cleanup handler function
591+ */
592+ public function registerCleanupHandler (callable $ handler ): void
593+ {
594+ $ this ->cleanup_handlers [] = $ handler ;
595+ }
596+
597+ /**
598+ * Perform cleanup operations
599+ */
600+ private function cleanup (): void
601+ {
602+ try {
603+ // Call all registered cleanup handlers
604+ foreach ($ this ->cleanup_handlers as $ handler ) {
605+ try {
606+ call_user_func ($ handler );
607+ } catch (\Exception $ e ) {
608+ $ this ->logger ->error ('Error in cleanup handler: {message} ' , ['message ' => $ e ->getMessage ()]);
609+ }
610+ }
611+
612+ // Clear any active locks or temporary files
613+ $ this ->logger ->notice ('Cleanup completed. ' );
614+ } catch (\Exception $ e ) {
615+ $ this ->logger ->error ('Error during cleanup: {message} ' , ['message ' => $ e ->getMessage ()]);
616+ }
617+ }
618+
528619 /**
529620 * Starts and configures PHP-VCR
530621 *
0 commit comments