TenantAtlas/apps/platform/app/Services/TenantConfiguration/SymfonyExchangePowerShellProcessExecutor.php
ahmido f4e342121a feat: add Exchange PowerShell production runner gate (#499)
Spec 432: Exchange PowerShell production runner boundary and runtime gate. Validation: php artisan test --filter=Spec432 --compact; ./vendor/bin/pint --dirty --test --format agent; git diff --check.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #499
2026-07-07 18:34:18 +00:00

49 lines
1.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\TenantConfiguration;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Process;
use Throwable;
final class SymfonyExchangePowerShellProcessExecutor implements ExchangePowerShellProcessExecutor
{
public function available(): bool
{
return class_exists(Process::class);
}
public function run(ExchangePowerShellProcessCommand $command): ExchangePowerShellProcessResult
{
$startedAt = microtime(true);
$process = null;
try {
$process = new Process($command->arguments, timeout: $command->timeoutSeconds);
$process->run();
return ExchangePowerShellProcessResult::completed(
exitCode: $process->getExitCode() ?? 1,
stdout: $process->getOutput(),
stderr: $process->getErrorOutput(),
durationMs: $this->durationMs($startedAt),
);
} catch (ProcessTimedOutException) {
if ($process instanceof Process && $process->isRunning()) {
$process->stop(0);
}
return ExchangePowerShellProcessResult::timedOut($this->durationMs($startedAt));
} catch (Throwable $exception) {
return ExchangePowerShellProcessResult::exception(class_basename($exception), $this->durationMs($startedAt));
}
}
private function durationMs(float $startedAt): int
{
return (int) max(0, round((microtime(true) - $startedAt) * 1000));
}
}