1<?php declare(strict_types=1); 2/* 3 * This file is part of phpunit/php-invoker. 4 * 5 * (c) Sebastian Bergmann <sebastian@phpunit.de> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10namespace SebastianBergmann\Invoker; 11 12use const SIGALRM; 13use function call_user_func_array; 14use function function_exists; 15use function pcntl_alarm; 16use function pcntl_async_signals; 17use function pcntl_signal; 18use function sprintf; 19use Throwable; 20 21final class Invoker 22{ 23 /** 24 * @var int 25 */ 26 private $timeout; 27 28 /** 29 * @throws Throwable 30 */ 31 public function invoke(callable $callable, array $arguments, int $timeout) 32 { 33 if (!$this->canInvokeWithTimeout()) { 34 throw new ProcessControlExtensionNotLoadedException( 35 'The pcntl (process control) extension for PHP is required' 36 ); 37 } 38 39 pcntl_signal( 40 SIGALRM, 41 function (): void { 42 throw new TimeoutException( 43 sprintf( 44 'Execution aborted after %d second%s', 45 $this->timeout, 46 $this->timeout === 1 ? '' : 's' 47 ) 48 ); 49 }, 50 true 51 ); 52 53 $this->timeout = $timeout; 54 55 pcntl_async_signals(true); 56 pcntl_alarm($timeout); 57 58 try { 59 return call_user_func_array($callable, $arguments); 60 } finally { 61 pcntl_alarm(0); 62 } 63 } 64 65 public function canInvokeWithTimeout(): bool 66 { 67 return function_exists('pcntl_signal') && function_exists('pcntl_async_signals') && function_exists('pcntl_alarm'); 68 } 69} 70